| @ -0,0 +1,89 @@ | |||
| # Byte-compiled / optimized / DLL files | |||
| __pycache__/ | |||
| *.py[cod] | |||
| *$py.class | |||
| # C extensions | |||
| *.so | |||
| # Distribution / packaging | |||
| .Python | |||
| env/ | |||
| build/ | |||
| develop-eggs/ | |||
| dist/ | |||
| downloads/ | |||
| eggs/ | |||
| .eggs/ | |||
| lib/ | |||
| lib64/ | |||
| parts/ | |||
| sdist/ | |||
| var/ | |||
| *.egg-info/ | |||
| .installed.cfg | |||
| *.egg | |||
| # PyInstaller | |||
| # Usually these files are written by a python script from a template | |||
| # before PyInstaller builds the exe, so as to inject date/other infos into it. | |||
| *.manifest | |||
| *.spec | |||
| # Installer logs | |||
| pip-log.txt | |||
| pip-delete-this-directory.txt | |||
| # Unit test / coverage reports | |||
| htmlcov/ | |||
| .tox/ | |||
| .coverage | |||
| .coverage.* | |||
| .cache | |||
| nosetests.xml | |||
| coverage.xml | |||
| *,cover | |||
| .hypothesis/ | |||
| # Translations | |||
| *.mo | |||
| *.pot | |||
| # Django stuff: | |||
| *.log | |||
| local_settings.py | |||
| # Flask stuff: | |||
| instance/ | |||
| .webassets-cache | |||
| # Scrapy stuff: | |||
| .scrapy | |||
| # Sphinx documentation | |||
| docs/_build/ | |||
| # PyBuilder | |||
| target/ | |||
| # IPython Notebook | |||
| .ipynb_checkpoints | |||
| # pyenv | |||
| .python-version | |||
| # celery beat schedule file | |||
| celerybeat-schedule | |||
| # dotenv | |||
| .env | |||
| # virtualenv | |||
| venv/ | |||
| ENV/ | |||
| # Spyder project settings | |||
| .spyderproject | |||
| # Rope project settings | |||
| .ropeproject | |||
| @ -0,0 +1,17 @@ | |||
| language: python | |||
| python: | |||
| - "2.6" | |||
| - "2.7" | |||
| - "3.3" | |||
| - "3.4" | |||
| - "3.5" | |||
| - "pypy" | |||
| sudo: false | |||
| install: | |||
| - pip install flake8 | |||
| script: | |||
| - flake8 --max-line-length=120 flask_defer.py && python test_flask_defer.py | |||
| @ -0,0 +1,10 @@ | |||
| Flask-Defer Changelog | |||
| =================== | |||
| Version 1.0.0 | |||
| ------------- | |||
| :Released: 11-28-2016 | |||
| :Changes: | |||
| Initial release of package | |||
| @ -0,0 +1,21 @@ | |||
| MIT License | |||
| Copyright (c) 2016 Brett Langdon | |||
| 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. | |||
| @ -0,0 +1 @@ | |||
| include CHANGELOG LICENSE README.rst flask_defer.py test_flask_defer.py | |||
| @ -0,0 +1,59 @@ | |||
| Flask-Defer | |||
| ========= | |||
| .. image:: https://badge.fury.io/py/flask-defer.svg | |||
| :target: https://badge.fury.io/py/flask-defer | |||
| .. image:: https://travis-ci.org/brettlangdon/flask-defer.svg?branch=master | |||
| :target: https://travis-ci.org/brettlangdon/flask-defer | |||
| Easily register a function to execute at the end of the current request. | |||
| Installation | |||
| ~~~~~~~~~~~~ | |||
| .. code:: bash | |||
| pip install Flask-Defer | |||
| Usage | |||
| ~~~~~ | |||
| .. code:: python | |||
| from flask import Flask | |||
| from flask_defer import FlaskDefer, after_request | |||
| app = Flask(__name__) | |||
| FlaskDefer(app) | |||
| def defer_me(name, say_hello=False): | |||
| if say_hello: | |||
| print 'Saying hello to, {name}'.format(name=name) | |||
| @app.route('/') | |||
| def index(): | |||
| print 'Start of request method' | |||
| # Defer `defer_me` until after the current request has finished | |||
| after_request(defer_me, 'name', say_hello=True) | |||
| print 'Ending request method' | |||
| return 'Thanks!' | |||
| if __name__ == '__main__': | |||
| app.run() | |||
| .. code:: bash | |||
| $ python example.py | |||
| * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) | |||
| Start of request method | |||
| Ending request method | |||
| Saying hello to, name | |||
| 127.0.0.1 - - [28/Nov/2016 15:41:39] "GET / HTTP/1.1" 200 - | |||
| @ -0,0 +1,26 @@ | |||
| from flask import Flask | |||
| from flask_defer import FlaskDefer, after_request | |||
| app = Flask(__name__) | |||
| FlaskDefer(app) | |||
| def defer_me(name, say_hello=False): | |||
| if say_hello: | |||
| print 'Saying hello to, {name}'.format(name=name) | |||
| @app.route('/') | |||
| def index(): | |||
| print 'Start of request method' | |||
| # Defer `defer_me` until after the current request has finished | |||
| after_request(defer_me, 'name', say_hello=True) | |||
| print 'Ending request method' | |||
| return 'Thanks!' | |||
| if __name__ == '__main__': | |||
| app.run() | |||
| @ -0,0 +1,37 @@ | |||
| try: | |||
| from flask import _app_ctx_stack as stack | |||
| except ImportError: | |||
| from flask import _request_ctx_stack as stack | |||
| __all__ = ['after_request', 'defer', 'FlaskDefer'] | |||
| def defer(func, *args, **kwargs): | |||
| params = dict(func=func, args=args, kwargs=kwargs) | |||
| ctx = stack.top | |||
| if not hasattr(ctx, 'deferred_tasks'): | |||
| setattr(ctx, 'deferred_tasks', []) | |||
| ctx.deferred_tasks.append(params) | |||
| # Alias `defer` as `after_request` | |||
| after_request = defer | |||
| class FlaskDefer(object): | |||
| def __init__(self, app=None): | |||
| if app is not None: | |||
| self.init_app(app) | |||
| def init_app(self, app): | |||
| if hasattr(app, 'teardown_appcontext'): | |||
| app.teardown_appcontext(self._execute_deferred) | |||
| else: | |||
| app.teardown_request(self._execute_deferred) | |||
| def _execute_deferred(self, exception): | |||
| ctx = stack.top | |||
| if hasattr(ctx, 'deferred_tasks'): | |||
| for params in ctx.deferred_tasks: | |||
| # DEV: Do not try/except, let these function calls fail | |||
| params['func'](*params['args'], **params['kwargs']) | |||
| @ -0,0 +1,36 @@ | |||
| """ | |||
| Flask-Defer | |||
| """ | |||
| from setuptools import setup | |||
| def get_long_description(): | |||
| with open('README.rst') as f: | |||
| rv = f.read() | |||
| return rv | |||
| setup( | |||
| name='Flask-Defer', | |||
| version='1.0.0', | |||
| url='https://github.com/brettlangdon/flask-defer.git', | |||
| license='MIT', | |||
| author='Brett Langdon', | |||
| author_email='me@brett.is', | |||
| description='Flask extension to defer task execution under after request teardown', | |||
| long_description=get_long_description(), | |||
| py_modules=['flask_defer'], | |||
| zip_safe=False, | |||
| include_package_data=True, | |||
| platforms='any', | |||
| install_requires=['Flask'], | |||
| classifiers=[ | |||
| 'Environment :: Web Environment', | |||
| 'Intended Audience :: Developers', | |||
| 'License :: OSI Approved :: MIT License', | |||
| 'Operating System :: OS Independent', | |||
| 'Programming Language :: Python', | |||
| 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', | |||
| 'Topic :: Software Development :: Libraries :: Python Modules' | |||
| ], | |||
| ) | |||
| @ -0,0 +1,119 @@ | |||
| import unittest | |||
| from flask import Flask | |||
| from flask_defer import FlaskDefer, defer, stack | |||
| def deferred_task(name, with_keyword=False): | |||
| pass | |||
| class TestFlaskDefer(unittest.TestCase): | |||
| def setUp(self): | |||
| self.app = Flask(__name__) | |||
| self.defer = FlaskDefer(app=self.app) | |||
| @self.app.route('/') | |||
| def test_endpoint(): | |||
| defer(deferred_task, 'name', with_keyword=True) | |||
| return 'test_endpoint' | |||
| @self.app.route('/multiple') | |||
| def test_multiple(): | |||
| defer(deferred_task, 'first', with_keyword=True) | |||
| defer(deferred_task, 'second', with_keyword=False) | |||
| defer(deferred_task, 'third', with_keyword=True) | |||
| return 'test_multiple' | |||
| @self.app.route('/extra') | |||
| def test_extra_params(): | |||
| defer(deferred_task, 'name', 'extra', with_keyword=True, extra_param='param') | |||
| return 'test_extra_params' | |||
| @self.app.route('/no-defer') | |||
| def test_no_defer(): | |||
| return 'test_no_defer' | |||
| def test_deferring_task(self): | |||
| with self.app.test_client() as client: | |||
| # Make the request so we register the task | |||
| client.get('/') | |||
| ctx = stack.top | |||
| self.assertTrue(hasattr(ctx, 'deferred_tasks')) | |||
| self.assertEqual(len(ctx.deferred_tasks), 1) | |||
| task = ctx.deferred_tasks[0] | |||
| self.assertDictEqual(task, dict( | |||
| args=('name', ), | |||
| func=deferred_task, | |||
| kwargs=dict(with_keyword=True), | |||
| )) | |||
| # Assert that the deferred tasks aren't shared between requests | |||
| with self.app.test_client() as client: | |||
| client.get('/no-defer') | |||
| ctx = stack.top | |||
| self.assertFalse(hasattr(ctx, 'deferred_tasks')) | |||
| def test_deferring_task_multiple(self): | |||
| with self.app.test_client() as client: | |||
| # Make the request so we register the task | |||
| client.get('/multiple') | |||
| ctx = stack.top | |||
| self.assertTrue(hasattr(ctx, 'deferred_tasks')) | |||
| self.assertEqual(len(ctx.deferred_tasks), 3) | |||
| task = ctx.deferred_tasks[0] | |||
| self.assertDictEqual(task, dict( | |||
| args=('first', ), | |||
| func=deferred_task, | |||
| kwargs=dict(with_keyword=True), | |||
| )) | |||
| task = ctx.deferred_tasks[1] | |||
| self.assertDictEqual(task, dict( | |||
| args=('second', ), | |||
| func=deferred_task, | |||
| kwargs=dict(with_keyword=False), | |||
| )) | |||
| task = ctx.deferred_tasks[2] | |||
| self.assertDictEqual(task, dict( | |||
| args=('third', ), | |||
| func=deferred_task, | |||
| kwargs=dict(with_keyword=True), | |||
| )) | |||
| def test_deferring_task_no_defer(self): | |||
| with self.app.test_client() as client: | |||
| # Make the request | |||
| client.get('/no-defer') | |||
| ctx = stack.top | |||
| self.assertFalse(hasattr(ctx, 'deferred_tasks')) | |||
| def test_deferring_task_extra(self): | |||
| # We get a TypeError from the invalid function call | |||
| # TODO: There has to be a better/more concise way to test this | |||
| with self.assertRaises(TypeError): | |||
| with self.app.test_client() as client: | |||
| # Make the request so we register the task | |||
| client.get('/extra') | |||
| ctx = stack.top | |||
| self.assertTrue(hasattr(ctx, 'deferred_tasks')) | |||
| self.assertEqual(len(ctx.deferred_tasks), 1) | |||
| task = ctx.deferred_tasks[0] | |||
| self.assertDictEqual(task, dict( | |||
| args=('name', 'extra'), | |||
| func=deferred_task, | |||
| kwargs=dict(with_keyword=True, extra_param='param'), | |||
| )) | |||
| if __name__ == '__main__': | |||
| unittest.main() | |||