From c83d68575e455d1d673a7580a16d2232c1e61ba4 Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 14:56:48 +0100 Subject: [PATCH 01/10] Initialization --- .gitignore | 7 +++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 4 ++++ 3 files changed, 32 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0694c0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.pyc +*.swp +build +dist +*.egg-info +.tox/ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cbff6fb --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..529860c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Awesome decorators + +List of awesome decorators I have found so far. + From 2118881f9006e0927c7ab62384a83d5f11944a79 Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 15:49:14 +0100 Subject: [PATCH 02/10] Code and tests initialization --- MANIFEST.in | 2 + awesomedecorators/__init__.py | 6 +++ awesomedecorators/memoized.py | 24 +++++++++++ awesomedecorators/timer.py | 40 +++++++++++++++++ awesomedecorators/timez.py | 58 +++++++++++++++++++++++++ setup.cfg | 0 setup.py | 81 +++++++++++++++++++++++++++++++++++ tests/__init__.py | 0 tests/tests.py | 74 ++++++++++++++++++++++++++++++++ tox.ini | 18 ++++++++ 10 files changed, 303 insertions(+) create mode 100644 MANIFEST.in create mode 100644 awesomedecorators/__init__.py create mode 100644 awesomedecorators/memoized.py create mode 100644 awesomedecorators/timer.py create mode 100644 awesomedecorators/timez.py create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/tests.py create mode 100644 tox.ini diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..b975e67 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE README.md +recursive-include tests *.py diff --git a/awesomedecorators/__init__.py b/awesomedecorators/__init__.py new file mode 100644 index 0000000..cb7e4eb --- /dev/null +++ b/awesomedecorators/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf8 -*- +from .memoized import memoized +from .timez import timeit, timeout, TimeoutError +from .timer import Timer + +__version__ = '0.1.0' diff --git a/awesomedecorators/memoized.py b/awesomedecorators/memoized.py new file mode 100644 index 0000000..441017e --- /dev/null +++ b/awesomedecorators/memoized.py @@ -0,0 +1,24 @@ +# -*- coding: utf8 -*- +""" +Memoized +Credits: Benjamin Bengfort +""" +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) diff --git a/awesomedecorators/timer.py b/awesomedecorators/timer.py new file mode 100644 index 0000000..b874b0c --- /dev/null +++ b/awesomedecorators/timer.py @@ -0,0 +1,40 @@ +# -*- coding: utf8 -*- +""" +Timer +Credit: Benjamin Bengfort +""" +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) diff --git a/awesomedecorators/timez.py b/awesomedecorators/timez.py new file mode 100644 index 0000000..47ad962 --- /dev/null +++ b/awesomedecorators/timez.py @@ -0,0 +1,58 @@ +# -*- coding: utf8 -*- +""" +Timeit and timeout are about tracking and controlling performance issues. +Credits: Benjamin Bengfort +""" +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 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f5fa177 --- /dev/null +++ b/setup.py @@ -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) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tests.py b/tests/tests.py new file mode 100644 index 0000000..a121eaf --- /dev/null +++ b/tests/tests.py @@ -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) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..a79d209 --- /dev/null +++ b/tox.ini @@ -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 From e7c86d9612572515202b866da423fba7a206f9ca Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 15:50:41 +0100 Subject: [PATCH 03/10] Add TravisCI configuration --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c54886b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +sudo: false +language: python +python: + - "2.7" + - "3.6" +install: pip install tox-travis +script: tox + From 29c49fa6718bf7e57f1b71de61d92b063f444bed Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 15:59:05 +0100 Subject: [PATCH 04/10] Add TravisCI Image URL --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 529860c..659dd40 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Awesome decorators +[![Build Status](https://travis-ci.org/romaryd/python-awesome-decorators.svg?branch=develop)](https://travis-ci.org/romaryd/python-awesome-decorators) + List of awesome decorators I have found so far. From 5e22f6f2674cfffd5d4a54c5ff04f712aebf6ccb Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 16:08:41 +0100 Subject: [PATCH 05/10] Add Coveralls --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 659dd40..b75cf83 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Awesome decorators [![Build Status](https://travis-ci.org/romaryd/python-awesome-decorators.svg?branch=develop)](https://travis-ci.org/romaryd/python-awesome-decorators) +[![Coverage Status](https://coveralls.io/repos/github/romaryd/python-awesome-decorators/badge.svg)](https://coveralls.io/github/romaryd/python-awesome-decorators) + List of awesome decorators I have found so far. From 2aca97cb058f1b2b763fd0e4e42b9aa9bf08fdbc Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 16:12:04 +0100 Subject: [PATCH 06/10] Update TravisCI for Coveralls --- .travis.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c54886b..671f6e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,15 @@ language: python python: - "2.7" - "3.6" -install: pip install tox-travis +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 From c190a00d14a503d47951af2304f23bba2fc9ca83 Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 16:25:41 +0100 Subject: [PATCH 07/10] Update Coveralls URL on develop branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b75cf83..cd9f950 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Awesome decorators [![Build Status](https://travis-ci.org/romaryd/python-awesome-decorators.svg?branch=develop)](https://travis-ci.org/romaryd/python-awesome-decorators) -[![Coverage Status](https://coveralls.io/repos/github/romaryd/python-awesome-decorators/badge.svg)](https://coveralls.io/github/romaryd/python-awesome-decorators) +[![Coverage Status](https://coveralls.io/repos/github/romaryd/python-awesome-decorators/badge.svg?branch=develop)](https://coveralls.io/github/romaryd/python-awesome-decorators?branch=develop) List of awesome decorators I have found so far. From 3cc578413ea61b6cfd0dc05b7bff397b0317a3c7 Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 16:43:16 +0100 Subject: [PATCH 08/10] Add Code climate --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd9f950..ee4c313 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![Build Status](https://travis-ci.org/romaryd/python-awesome-decorators.svg?branch=develop)](https://travis-ci.org/romaryd/python-awesome-decorators) [![Coverage Status](https://coveralls.io/repos/github/romaryd/python-awesome-decorators/badge.svg?branch=develop)](https://coveralls.io/github/romaryd/python-awesome-decorators?branch=develop) - +[![Maintainability](https://api.codeclimate.com/v1/badges/b03f759c2a1d62011a6d/maintainability)](https://codeclimate.com/github/romaryd/python-awesome-decorators/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/b03f759c2a1d62011a6d/test_coverage)](https://codeclimate.com/github/romaryd/python-awesome-decorators/test_coverage) List of awesome decorators I have found so far. From 0ae89c4e94c7696504982a539b66a11c2acb5f0b Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 16:48:41 +0100 Subject: [PATCH 09/10] Add code health with Landscape --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ee4c313..317988d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Coverage Status](https://coveralls.io/repos/github/romaryd/python-awesome-decorators/badge.svg?branch=develop)](https://coveralls.io/github/romaryd/python-awesome-decorators?branch=develop) [![Maintainability](https://api.codeclimate.com/v1/badges/b03f759c2a1d62011a6d/maintainability)](https://codeclimate.com/github/romaryd/python-awesome-decorators/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/b03f759c2a1d62011a6d/test_coverage)](https://codeclimate.com/github/romaryd/python-awesome-decorators/test_coverage) +[![Code Health](https://landscape.io/github/romaryd/python-awesome-decorators/develop/landscape.svg?style=flat)](https://landscape.io/github/romaryd/python-awesome-decorators/develop) List of awesome decorators I have found so far. From 1128bd14da9580703500a261d517be7289ab8f3f Mon Sep 17 00:00:00 2001 From: Romary Dupuis Date: Sun, 31 Dec 2017 17:06:30 +0100 Subject: [PATCH 10/10] Move badges on master branch --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 317988d..f194aa4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # Awesome decorators -[![Build Status](https://travis-ci.org/romaryd/python-awesome-decorators.svg?branch=develop)](https://travis-ci.org/romaryd/python-awesome-decorators) -[![Coverage Status](https://coveralls.io/repos/github/romaryd/python-awesome-decorators/badge.svg?branch=develop)](https://coveralls.io/github/romaryd/python-awesome-decorators?branch=develop) +[![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) -[![Test Coverage](https://api.codeclimate.com/v1/badges/b03f759c2a1d62011a6d/test_coverage)](https://codeclimate.com/github/romaryd/python-awesome-decorators/test_coverage) -[![Code Health](https://landscape.io/github/romaryd/python-awesome-decorators/develop/landscape.svg?style=flat)](https://landscape.io/github/romaryd/python-awesome-decorators/develop) +[![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.