Skip to content

Commit

Permalink
Merge branch 'release/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
romaryd committed Dec 31, 2017
2 parents b1f14d5 + 1128bd1 commit cbd2993
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.pyc
*.swp
build
dist
*.egg-info
.tox/

17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
sudo: false
language: python
python:
- "2.7"
- "3.6"
install:
- pip install tox-travis
- pip install coveralls
script: tox
after_script: coveralls
notifications:
email:
recipients:
- romary@me.com
on_success: change
on_failure: always

21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2017 Romary Dupuis, https://github.com/romaryd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include LICENSE README.md
recursive-include tests *.py
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Awesome decorators

[![Build Status](https://travis-ci.org/romaryd/python-awesome-decorators.svg?branch=master)](https://travis-ci.org/romaryd/python-awesome-decorators)
[![Coverage Status](https://coveralls.io/repos/github/romaryd/python-awesome-decorators/badge.svg?branch=master)](https://coveralls.io/github/romaryd/python-awesome-decorators?branch=master)
[![Maintainability](https://api.codeclimate.com/v1/badges/b03f759c2a1d62011a6d/maintainability)](https://codeclimate.com/github/romaryd/python-awesome-decorators/maintainability)
[![Code Health](https://landscape.io/github/romaryd/python-awesome-decorators/master/landscape.svg?style=flat)](https://landscape.io/github/romaryd/python-awesome-decorators/master)

List of awesome decorators I have found so far.

6 changes: 6 additions & 0 deletions awesomedecorators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf8 -*-
from .memoized import memoized
from .timez import timeit, timeout, TimeoutError
from .timer import Timer

__version__ = '0.1.0'
24 changes: 24 additions & 0 deletions awesomedecorators/memoized.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf8 -*-
"""
Memoized
Credits: Benjamin Bengfort <benjamin@bengfort.com>
"""
from functools import wraps


def memoized(fget):
"""
Return a property attribute for new-style classes that only calls its
getter on the first access. The result is stored and on subsequent
accesses is returned, preventing the need to call the getter any more.
https://github.com/estebistec/python-memoized-property
"""
attr_name = '_{0}'.format(fget.__name__)

@wraps(fget)
def fget_memoized(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fget(self))
return getattr(self, attr_name)

return property(fget_memoized)
40 changes: 40 additions & 0 deletions awesomedecorators/timer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -*- coding: utf8 -*-
"""
Timer
Credit: Benjamin Bengfort <benjamin@bengfort.com>
"""
import time


class Timer(object):
"""
A context object timer. Usage:
>>> with Timer() as timer:
... do_something()
>>> print timer.elapsed
"""

def __init__(self, wall_clock=True):
"""
If wall_clock is True then use time.time() to get the number of
actually elapsed seconds. If wall_clock is False, use time.clock to
get the process time instead.
"""
self.wall_clock = wall_clock
self.time = time.time if wall_clock else time.clock

# Stubs for serializing an empty timer.
self.started = None
self.finished = None
self.elapsed = 0.0

def __enter__(self):
self.started = self.time()
return self

def __exit__(self, typ, value, tb):
self.finished = self.time()
self.elapsed = self.finished - self.started

def __str__(self):
return '{} second(s)'.format(self.elapsed)
58 changes: 58 additions & 0 deletions awesomedecorators/timez.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -*- coding: utf8 -*-
"""
Timeit and timeout are about tracking and controlling performance issues.
Credits: Benjamin Bengfort <benjamin@bengfort.com>
"""
from functools import wraps
import signal
from .timer import Timer


class TimeoutError(Exception):
"""
An operation timed out
"""
pass


def timeit(func):
"""
Returns the number of seconds that a function took along with the result
"""

@wraps(func)
def timer_wrapper(*args, **kwargs):
"""
Inner function that uses the Timer context object
"""
with Timer() as timer:
result = func(*args, **kwargs)

return result, timer

return timer_wrapper


def timeout(seconds):
"""
Raises a TimeoutError if a function does not terminate within
specified seconds.
"""
def _timeout_error(signal, frame):
raise TimeoutError("Operation did not finish within \
{} seconds".format(seconds))

def timeout_decorator(func):

@wraps(func)
def timeout_wrapper(*args, **kwargs):
signal.signal(signal.SIGALRM, _timeout_error)
signal.alarm(seconds)
try:
return func(*args, **kwargs)
finally:
signal.alarm(0)

return timeout_wrapper

return timeout_decorator
Empty file added setup.cfg
Empty file.
81 changes: 81 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python

import os
import re
try:
from setuptools import setup
from setuptools import find_packages
except ImportError:
raise ImportError("Could not import \"setuptools\"."
"Please install the setuptools package.")


def text_of(relpath):
"""
Return string containing the contents of the file at *relpath* relative to
this file.
"""
thisdir = os.path.dirname(__file__)
file_path = os.path.join(thisdir, os.path.normpath(relpath))
with open(file_path) as f:
text = f.read()
return text


# Read the version without importing the package
# (and thus attempting to import packages it depends on that may not be
# installed yet)
version = re.search(
"__version__ = '([^']+)'", text_of('awesomedecorators/__init__.py')
).group(1)


NAME = 'python-awesome-decorators'
VERSION = version
DESCRIPTION = 'List of nice decorators in Python.'
KEYWORDS = 'decorators'
AUTHOR = 'Romary Dupuis'
AUTHOR_EMAIL = 'romary@me.com'
URL = 'https://github.com/romaryd/python-awesome-decorators'
LICENSE = text_of('LICENSE')
PACKAGES = find_packages(exclude=['tests', 'tests.*'])

INSTALL_REQUIRES = ['python-dateutil']
TEST_SUITE = 'tests'
TESTS_REQUIRE = ['pytest']

CLASSIFIERS = [
'Development Status :: 1 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.6',
'Topic :: Software Development',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Utilities',
]

LONG_DESCRIPTION = text_of('README.md')


params = {
'name': NAME,
'version': VERSION,
'description': DESCRIPTION,
'keywords': KEYWORDS,
'long_description': LONG_DESCRIPTION,
'author': AUTHOR,
'author_email': AUTHOR_EMAIL,
'url': URL,
'license': LICENSE,
'packages': PACKAGES,
'install_requires': INSTALL_REQUIRES,
'tests_require': TESTS_REQUIRE,
'test_suite': TEST_SUITE,
'classifiers': CLASSIFIERS,
}

if __name__ == '__main__':
setup(**params)
Empty file added tests/__init__.py
Empty file.
74 changes: 74 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Testing the decorators utility package.
"""

import unittest
import time
from awesomedecorators import memoized, timeit, timeout, Timer, TimeoutError


class DecoratorsTests(unittest.TestCase):
"""
Basic decorators utility tests.
"""

def test_memoized(self):
"""
Test the memoized property
"""

class Thing(object):

@memoized
def attr(self):
return 42

thing = Thing()
self.assertFalse(hasattr(thing, '_attr'))
self.assertEqual(thing.attr, 42)
self.assertTrue(hasattr(thing, '_attr'))

def test_timeit(self):
"""
Test the timeit decorator
"""

@timeit
def myfunc():
return 42

output = myfunc()
self.assertEqual(len(output), 2)
result, timer = output
self.assertEqual(result, 42)
self.assertTrue(isinstance(timer, Timer))

def test_timeout(self):
"""
Test the timeout decorator
"""

@timeout(1)
def myfunc():
# Some function that should take more than 1 second
time.sleep(2)

with self.assertRaises(TimeoutError) as context:
myfunc()
self.assertTrue('Operation did not finish within'
in str(context.exception))

def test_timeout_elapsed(self):
"""
Test the timeout decorator: elapsed value
"""

@timeout(2)
def myfunc():
# Some function that should take more than 1 second
time.sleep(1)

with Timer() as timer:
myfunc()
self.assertGreater(timer.elapsed, 1.0)
print(timer)
18 changes: 18 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[flake8]
exclude = dist,docs,*.egg-info,.git,ref,_scratch,.tox
max-line-length = 80
ignore=F401

[tox]
envlist = py27, py36, flake8

[testenv]
deps=
nose
coverage
commands=nosetests -v --with-coverage --cover-package=awesomedecorators --cover-inclusive --cover-erase tests

[testenv:flake8]
basepython = python2.7
deps = flake8
commands = flake8 awesomedecorators ./tests

0 comments on commit cbd2993

Please sign in to comment.