| @ -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() | |||||