[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# IPython Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# dotenv\n.env\n\n# virtualenv\nvenv/\nENV/\n\n# Spyder project settings\n.spyderproject\n\n# Rope project settings\n.ropeproject\n\n# Robpol86\n*.rpm\n.idea/\nrequirements*.txt\n.DS_Store\n"
  },
  {
    "path": ".travis.yml",
    "content": "# Configure.\nlanguage: python\npython:\n  - 3.4\n  - 3.3\n  - pypy\n  - 2.7\n  - 2.6\nservices: [redis-server]\nsudo: false\n\n# Environment and matrix.\nenv:\n  - BROKER: sqlite\n  - BROKER: mysql\n  - BROKER: postgres\n  - BROKER: redis\n  - BROKER: redis_sock,/tmp/redis.sock\nmatrix:\n  include:\n    - python: 3.4\n      services: []\n      env: TOX_ENV=lint\n      before_script: []\n      after_success: []\n\n# Run.\ninstall: pip install tox\nbefore_script:\n  - if [[ $BROKER == redis_sock* ]]; then echo -e \"daemonize yes\\nunixsocket /tmp/redis.sock\\nport 0\" |redis-server -; fi\n  - if [ $BROKER == mysql ]; then mysql -u root -e 'CREATE DATABASE flask_celery_helper_test;'; fi\n  - if [ $BROKER == mysql ]; then mysql -u root -e 'GRANT ALL PRIVILEGES ON flask_celery_helper_test.* TO \"user\"@\"localhost\" IDENTIFIED BY \"pass\";'; fi\n  - if [ $BROKER == postgres ]; then psql -U postgres -c 'CREATE DATABASE flask_celery_helper_test;'; fi\n  - if [ $BROKER == postgres ]; then psql -U postgres -c \"CREATE USER user1 WITH PASSWORD 'pass';\"; fi\n  - if [ $BROKER == postgres ]; then psql -U postgres -c 'GRANT ALL PRIVILEGES ON DATABASE flask_celery_helper_test TO user1;'; fi\nscript: tox -e ${TOX_ENV:-py}\nafter_success:\n  - bash <(curl -s https://codecov.io/bash)\n\n# Deploy.\ndeploy:\n  provider: pypi\n  user: Robpol86\n  password:\n    secure:\n      \"liwn5bHqjAtW+gRX6r4VgWuc44OUwfGSne4fTxb6G2pnPNW/IneVspQ2bFXeuQDdXzyLoOe\\\n      bKa8bxjRurUEHedjV9UG9fVZwVsWU981aWOxeEl+6kLkpJ2fE9UVeK7T1O+RzzhkWhHq2/YL\\\n      4BjBqzOLuBSAGnXZAnwH55Z6HY2g=\"\n  on:\n    condition: $TRAVIS_PYTHON_VERSION = 3.4\n    tags: true\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nEveryone that wants to contribute to the project should read this document.\n\n## Getting Started\n\nYou may follow these steps if you wish to create a pull request. Fork the repo and clone it on your local machine. Then\nin the project's directory:\n\n```bash\nvirtualenv env  # Create a virtualenv for the project's dependencies.\nsource env/bin/activate  # Activate the virtualenv.\npip install tox  # Install tox, which runs linting and tests.\ntox  # This runs all tests on your local machine. Make sure they pass.\n```\n\nIf you don't have Python 2.7 or 3.4 installed you can manually run tests on one specific version by running\n`tox -e lint,py35` (for Python 3.5) instead.\n\n## Consistency and Style\n\nKeep code style consistent with the rest of the project. Some suggestions:\n\n1. **Write tests for your new features.** `if new_feature else` **Write tests for bug-causing scenarios.**\n2. Write docstrings for all classes, functions, methods, modules, etc.\n3. Document all function/method arguments and return values.\n4. Document all class variables instance variables.\n5. Documentation guidelines also apply to tests, though not as strict.\n6. Keep code style consistent, such as the kind of quotes to use and spacing.\n7. Don't use `except:` or `except Exception:` unless you have a `raise` in the block. Be specific about error handling.\n8. Don't use `isinstance()` (it breaks [duck typing](https://en.wikipedia.org/wiki/Duck_typing#In_Python)).\n\n## Thanks\n\nThanks for fixing bugs or adding features to the project!\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Robpol86\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.rst",
    "content": "===================\nFlask-Celery-Helper\n===================\n\nEven though the `Flask documentation <http://flask.pocoo.org/docs/patterns/celery/>`_ says Celery extensions are\nunnecessary now, I found that I still need an extension to properly use Celery in large Flask applications. Specifically\nI need an init_app() method to initialize Celery after I instantiate it.\n\nThis extension also comes with a ``single_instance`` method.\n\n* Python 2.6, 2.7, PyPy, 3.3, and 3.4 supported on Linux and OS X.\n* Python 2.7, 3.3, and 3.4 supported on Windows (both 32 and 64 bit versions of Python).\n\n.. image:: https://img.shields.io/appveyor/ci/Robpol86/Flask-Celery-Helper/master.svg?style=flat-square&label=AppVeyor%20CI\n    :target: https://ci.appveyor.com/project/Robpol86/Flask-Celery-Helper\n    :alt: Build Status Windows\n\n.. image:: https://img.shields.io/travis/Robpol86/Flask-Celery-Helper/master.svg?style=flat-square&label=Travis%20CI\n    :target: https://travis-ci.org/Robpol86/Flask-Celery-Helper\n    :alt: Build Status\n\n.. image:: https://img.shields.io/codecov/c/github/Robpol86/Flask-Celery-Helper/master.svg?style=flat-square&label=Codecov\n    :target: https://codecov.io/gh/Robpol86/Flask-Celery-Helper\n    :alt: Coverage Status\n\n.. image:: https://img.shields.io/pypi/v/Flask-Celery-Helper.svg?style=flat-square&label=Latest\n    :target: https://pypi.python.org/pypi/Flask-Celery-Helper\n    :alt: Latest Version\n\nAttribution\n===========\n\nSingle instance decorator inspired by\n`Ryan Roemer <http://loose-bits.com/2010/10/distributed-task-locking-in-celery.html>`_.\n\nSupported Libraries\n===================\n\n* `Flask <http://flask.pocoo.org/>`_ 0.12\n* `Redis <http://redis.io/>`_ 3.2.6\n* `Celery <http://www.celeryproject.org/>`_ 3.1.11\n\nQuickstart\n==========\n\nInstall:\n\n.. code:: bash\n\n    pip install Flask-Celery-Helper\n\nExamples\n========\n\nBasic Example\n-------------\n\n.. code:: python\n\n    # example.py\n    from flask import Flask\n    from flask_celery import Celery\n\n    app = Flask('example')\n    app.config['CELERY_BROKER_URL'] = 'redis://localhost'\n    app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost'\n    celery = Celery(app)\n\n    @celery.task()\n    def add_together(a, b):\n        return a + b\n\n    if __name__ == '__main__':\n        result = add_together.delay(23, 42)\n        print(result.get())\n\nRun these two commands in separate terminals:\n\n.. code:: bash\n\n    celery -A example.celery worker\n    python example.py\n\nFactory Example\n---------------\n\n.. code:: python\n\n    # extensions.py\n    from flask_celery import Celery\n\n    celery = Celery()\n\n.. code:: python\n\n    # application.py\n    from flask import Flask\n    from extensions import celery\n\n    def create_app():\n        app = Flask(__name__)\n        app.config['CELERY_IMPORTS'] = ('tasks.add_together', )\n        app.config['CELERY_BROKER_URL'] = 'redis://localhost'\n        app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost'\n        celery.init_app(app)\n        return app\n\n.. code:: python\n\n    # tasks.py\n    from extensions import celery\n\n    @celery.task()\n    def add_together(a, b):\n        return a + b\n\n.. code:: python\n\n    # manage.py\n    from application import create_app\n\n    app = create_app()\n    app.run()\n\nSingle Instance Example\n-----------------------\n\n.. code:: python\n\n    # example.py\n    import time\n    from flask import Flask\n    from flask_celery import Celery, single_instance\n    from flask_redis import Redis\n\n    app = Flask('example')\n    app.config['REDIS_URL'] = 'redis://localhost'\n    app.config['CELERY_BROKER_URL'] = 'redis://localhost'\n    app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost'\n    celery = Celery(app)\n    Redis(app)\n\n    @celery.task(bind=True)\n    @single_instance\n    def sleep_one_second(a, b):\n        time.sleep(1)\n        return a + b\n\n    if __name__ == '__main__':\n        task1 = sleep_one_second.delay(23, 42)\n        time.sleep(0.1)\n        task2 = sleep_one_second.delay(20, 40)\n        results1 = task1.get(propagate=False)\n        results2 = task2.get(propagate=False)\n        print(results1)  # 65\n        if isinstance(results2, Exception) and str(results2) == 'Failed to acquire lock.':\n            print('Another instance is already running.')\n        else:\n            print(results2)  # Should not happen.\n\n.. changelog-section-start\n\nChangelog\n=========\n\nThis project adheres to `Semantic Versioning <http://semver.org/>`_.\n\nUnreleased\n----------\n\nChanged\n    * Supporting Flask 0.12, switching from ``flask.ext.celery`` to ``flask_celery`` import recommendation.\n\n1.1.0 - 2014-12-28\n------------------\n\nAdded\n    * Windows support.\n    * ``single_instance`` supported on SQLite/MySQL/PostgreSQL in addition to Redis.\n\nChanged\n    * ``CELERY_RESULT_BACKEND`` no longer mandatory.\n    * Breaking changes: ``flask.ext.celery.CELERY_LOCK`` moved to ``flask.ext.celery._LockManagerRedis.CELERY_LOCK``.\n\n1.0.0 - 2014-11-01\n------------------\n\nAdded\n    * Support for non-Redis backends.\n\n0.2.2 - 2014-08-11\n------------------\n\nAdded\n    * Python 2.6 and 3.x support.\n\n0.2.1 - 2014-06-18\n------------------\n\nFixed\n    * ``single_instance`` arguments with functools.\n\n0.2.0 - 2014-06-18\n------------------\n\nAdded\n    * ``include_args`` argument to ``single_instance``.\n\n0.1.0 - 2014-06-01\n------------------\n\n* Initial release.\n\n.. changelog-section-end\n"
  },
  {
    "path": "appveyor.yml",
    "content": "# Configure.\nservices:\n  - mysql\n  - postgresql\n\n# Environment and matrix.\nenvironment:\n  PATH: C:\\%PYTHON%;C:\\%PYTHON%\\Scripts;C:\\Program Files\\MySQL\\MySQL Server 5.7\\bin;C:\\Program Files\\PostgreSQL\\9.5\\bin;%PATH%\n  PGPASSWORD: Password12!\n  PYTHON: Python34\n  matrix:\n    - TOX_ENV: lint\n      BROKER: sqlite\n    - TOX_ENV: py34\n      BROKER: sqlite\n    - TOX_ENV: py33\n      BROKER: sqlite\n    - TOX_ENV: py27\n      BROKER: sqlite\n    - TOX_ENV: py\n      PYTHON: Python34-x64\n      BROKER: sqlite\n    - TOX_ENV: py\n      PYTHON: Python33-x64\n      BROKER: sqlite\n    - TOX_ENV: py\n      PYTHON: Python27-x64\n      BROKER: sqlite\n\n    - TOX_ENV: lint\n      BROKER: mysql\n    - TOX_ENV: py34\n      BROKER: mysql\n    - TOX_ENV: py33\n      BROKER: mysql\n    - TOX_ENV: py27\n      BROKER: mysql\n    - TOX_ENV: py\n      PYTHON: Python34-x64\n      BROKER: mysql\n    - TOX_ENV: py\n      PYTHON: Python33-x64\n      BROKER: mysql\n    - TOX_ENV: py\n      PYTHON: Python27-x64\n      BROKER: mysql\n\n    - TOX_ENV: lint\n      BROKER: postgres\n    - TOX_ENV: py34\n      BROKER: postgres\n    - TOX_ENV: py33\n      BROKER: postgres\n    - TOX_ENV: py27\n      BROKER: postgres\n    - TOX_ENV: py\n      PYTHON: Python34-x64\n      BROKER: postgres\n    - TOX_ENV: py\n      PYTHON: Python33-x64\n      BROKER: postgres\n    - TOX_ENV: py\n      PYTHON: Python27-x64\n      BROKER: postgres\n\n    - TOX_ENV: lint\n      BROKER: redis\n    - TOX_ENV: py34\n      BROKER: redis\n    - TOX_ENV: py33\n      BROKER: redis\n    - TOX_ENV: py27\n      BROKER: redis\n    - TOX_ENV: py\n      PYTHON: Python34-x64\n      BROKER: redis\n    - TOX_ENV: py\n      PYTHON: Python33-x64\n      BROKER: redis\n    - TOX_ENV: py\n      PYTHON: Python27-x64\n      BROKER: redis\n\n# Run.\nbuild_script: pip install tox\nafter_build:\n  - IF %BROKER% EQU redis cinst redis-64\n  - IF %BROKER% EQU redis redis-server --service-install\n  - IF %BROKER% EQU redis redis-server --service-start\n  - IF %BROKER% EQU mysql mysql -u root -p\"Password12!\" -e \"CREATE DATABASE flask_celery_helper_test;\"\n  - IF %BROKER% EQU mysql mysql -u root -p\"Password12!\" -e \"GRANT ALL PRIVILEGES ON flask_celery_helper_test.* TO 'user'@'localhost' IDENTIFIED BY 'pass';\"\n  - IF %BROKER% EQU postgres psql -U postgres -c \"CREATE DATABASE flask_celery_helper_test;\"\n  - IF %BROKER% EQU postgres psql -U postgres -c \"CREATE USER user1 WITH PASSWORD 'pass';\"\n  - IF %BROKER% EQU postgres psql -U postgres -c \"GRANT ALL PRIVILEGES ON DATABASE flask_celery_helper_test TO user1;\"\ntest_script: tox -e %TOX_ENV%\non_success: IF %TOX_ENV% NEQ lint pip install codecov & codecov\n"
  },
  {
    "path": "flask_celery.py",
    "content": "\"\"\"Celery support for Flask without breaking PyCharm inspections.\n\nhttps://github.com/Robpol86/Flask-Celery-Helper\nhttps://pypi.python.org/pypi/Flask-Celery-Helper\n\"\"\"\n\nimport hashlib\nfrom datetime import datetime, timedelta\nfrom functools import partial, wraps\nfrom logging import getLogger\n\nfrom celery import _state, Celery as CeleryClass\n\n__author__ = '@Robpol86'\n__license__ = 'MIT'\n__version__ = '1.1.0'\n\n\nclass OtherInstanceError(Exception):\n    \"\"\"Raised when Celery task is already running, when lock exists and has not timed out.\"\"\"\n\n    pass\n\n\nclass _LockManager(object):\n    \"\"\"Base class for other lock managers.\"\"\"\n\n    def __init__(self, celery_self, timeout, include_args, args, kwargs):\n        \"\"\"May raise NotImplementedError if the Celery backend is not supported.\n\n        :param celery_self: From wrapped() within single_instance(). It is the `self` object specified in a binded\n            Celery task definition (implicit first argument of the Celery task when @celery.task(bind=True) is used).\n        :param int timeout: Lock's timeout value in seconds.\n        :param bool include_args: If single instance should take arguments into account.\n        :param iter args: The task instance's args.\n        :param dict kwargs: The task instance's kwargs.\n        \"\"\"\n        self.celery_self = celery_self\n        self.timeout = timeout\n        self.include_args = include_args\n        self.args = args\n        self.kwargs = kwargs\n        self.log = getLogger('{0}:{1}'.format(self.__class__.__name__, self.task_identifier))\n\n    @property\n    def task_identifier(self):\n        \"\"\"Return the unique identifier (string) of a task instance.\"\"\"\n        task_id = self.celery_self.name\n        if self.include_args:\n            merged_args = str(self.args) + str([(k, self.kwargs[k]) for k in sorted(self.kwargs)])\n            task_id += '.args.{0}'.format(hashlib.md5(merged_args.encode('utf-8')).hexdigest())\n        return task_id\n\n\nclass _LockManagerRedis(_LockManager):\n    \"\"\"Handle locking/unlocking for Redis backends.\"\"\"\n\n    CELERY_LOCK = '_celery.single_instance.{task_id}'\n\n    def __init__(self, celery_self, timeout, include_args, args, kwargs):\n        super(_LockManagerRedis, self).__init__(celery_self, timeout, include_args, args, kwargs)\n        self.lock = None\n\n    def __enter__(self):\n        redis_key = self.CELERY_LOCK.format(task_id=self.task_identifier)\n        self.lock = self.celery_self.backend.client.lock(redis_key, timeout=self.timeout)\n        self.log.debug('Timeout %ds | Redis key %s', self.timeout, redis_key)\n        if not self.lock.acquire(blocking=False):\n            self.log.debug('Another instance is running.')\n            raise OtherInstanceError('Failed to acquire lock, {0} already running.'.format(self.task_identifier))\n        else:\n            self.log.debug('Got lock, running.')\n\n    def __exit__(self, exc_type, *_):\n        if exc_type == OtherInstanceError:\n            # Failed to get lock last time, not releasing.\n            return\n        self.log.debug('Releasing lock.')\n        self.lock.release()\n\n    @property\n    def is_already_running(self):\n        \"\"\"Return True if lock exists and has not timed out.\"\"\"\n        redis_key = self.CELERY_LOCK.format(task_id=self.task_identifier)\n        return self.celery_self.backend.client.exists(redis_key)\n\n    def reset_lock(self):\n        \"\"\"Removed the lock regardless of timeout.\"\"\"\n        redis_key = self.CELERY_LOCK.format(task_id=self.task_identifier)\n        self.celery_self.backend.client.delete(redis_key)\n\n\nclass _LockManagerDB(_LockManager):\n    \"\"\"Handle locking/unlocking for SQLite/MySQL/PostgreSQL/etc backends.\"\"\"\n\n    def __init__(self, celery_self, timeout, include_args, args, kwargs):\n        super(_LockManagerDB, self).__init__(celery_self, timeout, include_args, args, kwargs)\n        self.save_group = getattr(self.celery_self.backend, '_save_group')\n        self.restore_group = getattr(self.celery_self.backend, '_restore_group')\n        self.delete_group = getattr(self.celery_self.backend, '_delete_group')\n\n    def __enter__(self):\n        self.log.debug('Timeout %ds', self.timeout)\n        try:\n            self.save_group(self.task_identifier, None)\n        except Exception as exc:  # pylint: disable=broad-except\n            if 'IntegrityError' not in str(exc) and 'ProgrammingError' not in str(exc):\n                raise\n            difference = datetime.utcnow() - self.restore_group(self.task_identifier)['date_done']\n            if difference < timedelta(seconds=self.timeout):\n                self.log.debug('Another instance is running.')\n                raise OtherInstanceError('Failed to acquire lock, {0} already running.'.format(self.task_identifier))\n            self.log.debug('Timeout expired, stale lock found, releasing lock.')\n            self.delete_group(self.task_identifier)\n            self.save_group(self.task_identifier, None)\n            self.log.debug('Got lock, running.')\n\n    def __exit__(self, exc_type, *_):\n        if exc_type == OtherInstanceError:\n            # Failed to get lock last time, not releasing.\n            return\n        self.log.debug('Releasing lock.')\n        self.delete_group(self.task_identifier)\n\n    @property\n    def is_already_running(self):\n        \"\"\"Return True if lock exists and has not timed out.\"\"\"\n        date_done = (self.restore_group(self.task_identifier) or dict()).get('date_done')\n        if not date_done:\n            return False\n        difference = datetime.utcnow() - date_done\n        return difference < timedelta(seconds=self.timeout)\n\n    def reset_lock(self):\n        \"\"\"Removed the lock regardless of timeout.\"\"\"\n        self.delete_group(self.task_identifier)\n\n\ndef _select_manager(backend_name):\n    \"\"\"Select the proper LockManager based on the current backend used by Celery.\n\n    :raise NotImplementedError: If Celery is using an unsupported backend.\n\n    :param str backend_name: Class name of the current Celery backend. Usually value of\n        current_app.extensions['celery'].celery.backend.__class__.__name__.\n\n    :return: Class definition object (not instance). One of the _LockManager* classes.\n    \"\"\"\n    if backend_name == 'RedisBackend':\n        lock_manager = _LockManagerRedis\n    elif backend_name == 'DatabaseBackend':\n        lock_manager = _LockManagerDB\n    else:\n        raise NotImplementedError\n    return lock_manager\n\n\nclass _CeleryState(object):\n    \"\"\"Remember the configuration for the (celery, app) tuple. Modeled from SQLAlchemy.\"\"\"\n\n    def __init__(self, celery, app):\n        self.celery = celery\n        self.app = app\n\n\n# noinspection PyProtectedMember\nclass Celery(CeleryClass):\n    \"\"\"Celery extension for Flask applications.\n\n    Involves a hack to allow views and tests importing the celery instance from extensions.py to access the regular\n    Celery instance methods. This is done by subclassing celery.Celery and overwriting celery._state._register_app()\n    with a lambda/function that does nothing at all.\n\n    That way, on the first super() in this class' __init__(), all of the required instance objects are initialized, but\n    the Celery application is not registered. This class will be initialized in extensions.py but at that moment the\n    Flask application is not yet available.\n\n    Then, once the Flask application is available, this class' init_app() method will be called, with the Flask\n    application as an argument. init_app() will again call celery.Celery.__init__() but this time with the\n    celery._state._register_app() restored to its original functionality. in init_app() the actual Celery application is\n    initialized like normal.\n    \"\"\"\n\n    def __init__(self, app=None):\n        \"\"\"If app argument provided then initialize celery using application config values.\n\n        If no app argument provided you should do initialization later with init_app method.\n\n        :param app: Flask application instance.\n        \"\"\"\n        self.original_register_app = _state._register_app  # Backup Celery app registration function.\n        _state._register_app = lambda _: None  # Upon Celery app registration attempt, do nothing.\n        super(Celery, self).__init__()\n        if app is not None:\n            self.init_app(app)\n\n    def init_app(self, app):\n        \"\"\"Actual method to read celery settings from app configuration and initialize the celery instance.\n\n        :param app: Flask application instance.\n        \"\"\"\n        _state._register_app = self.original_register_app  # Restore Celery app registration function.\n        if not hasattr(app, 'extensions'):\n            app.extensions = dict()\n        if 'celery' in app.extensions:\n            raise ValueError('Already registered extension CELERY.')\n        app.extensions['celery'] = _CeleryState(self, app)\n\n        # Instantiate celery and read config.\n        super(Celery, self).__init__(app.import_name, broker=app.config['CELERY_BROKER_URL'])\n\n        # Set result backend default.\n        if 'CELERY_RESULT_BACKEND' in app.config:\n            self._preconf['CELERY_RESULT_BACKEND'] = app.config['CELERY_RESULT_BACKEND']\n\n        self.conf.update(app.config)\n        task_base = self.Task\n\n        # Add Flask app context to celery instance.\n        class ContextTask(task_base):\n            def __call__(self, *_args, **_kwargs):\n                with app.app_context():\n                    return task_base.__call__(self, *_args, **_kwargs)\n        setattr(ContextTask, 'abstract', True)\n        setattr(self, 'Task', ContextTask)\n\n\ndef single_instance(func=None, lock_timeout=None, include_args=False):\n    \"\"\"Celery task decorator. Forces the task to have only one running instance at a time.\n\n    Use with binded tasks (@celery.task(bind=True)).\n\n    Modeled after:\n    http://loose-bits.com/2010/10/distributed-task-locking-in-celery.html\n    http://blogs.it.ox.ac.uk/inapickle/2012/01/05/python-decorators-with-optional-arguments/\n\n    Written by @Robpol86.\n\n    :raise OtherInstanceError: If another instance is already running.\n\n    :param function func: The function to decorate, must be also decorated by @celery.task.\n    :param int lock_timeout: Lock timeout in seconds plus five more seconds, in-case the task crashes and fails to\n        release the lock. If not specified, the values of the task's soft/hard limits are used. If all else fails,\n        timeout will be 5 minutes.\n    :param bool include_args: Include the md5 checksum of the arguments passed to the task in the Redis key. This allows\n        the same task to run with different arguments, only stopping a task from running if another instance of it is\n        running with the same arguments.\n    \"\"\"\n    if func is None:\n        return partial(single_instance, lock_timeout=lock_timeout, include_args=include_args)\n\n    @wraps(func)\n    def wrapped(celery_self, *args, **kwargs):\n        \"\"\"Wrapped Celery task, for single_instance().\"\"\"\n        # Select the manager and get timeout.\n        timeout = (\n            lock_timeout or celery_self.soft_time_limit or celery_self.time_limit\n            or celery_self.app.conf.get('CELERYD_TASK_SOFT_TIME_LIMIT')\n            or celery_self.app.conf.get('CELERYD_TASK_TIME_LIMIT')\n            or (60 * 5)\n        )\n        manager_class = _select_manager(celery_self.backend.__class__.__name__)\n        lock_manager = manager_class(celery_self, timeout, include_args, args, kwargs)\n\n        # Lock and execute.\n        with lock_manager:\n            ret_value = func(*args, **kwargs)\n        return ret_value\n    return wrapped\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\n\"\"\"Setup script for the project.\"\"\"\n\nimport codecs\nimport os\nimport re\n\nfrom setuptools import Command, setup\n\nIMPORT = 'flask_celery'\nINSTALL_REQUIRES = ['flask', 'celery']\nLICENSE = 'MIT'\nNAME = 'Flask-Celery-Helper'\nVERSION = '1.1.0'\n\n\ndef readme(path='README.rst'):\n    \"\"\"Try to read README.rst or return empty string if failed.\n\n    :param str path: Path to README file.\n\n    :return: File contents.\n    :rtype: str\n    \"\"\"\n    path = os.path.realpath(os.path.join(os.path.dirname(__file__), path))\n    handle = None\n    url_prefix = 'https://raw.githubusercontent.com/Robpol86/{name}/v{version}/'.format(name=NAME, version=VERSION)\n    try:\n        handle = codecs.open(path, encoding='utf-8')\n        return handle.read(131072).replace('.. image:: docs', '.. image:: {0}docs'.format(url_prefix))\n    except IOError:\n        return ''\n    finally:\n        getattr(handle, 'close', lambda: None)()\n\n\nclass CheckVersion(Command):\n    \"\"\"Make sure version strings and other metadata match here, in module/package, tox, and other places.\"\"\"\n\n    description = 'verify consistent version/etc strings in project'\n    user_options = []\n\n    @classmethod\n    def initialize_options(cls):\n        \"\"\"Required by distutils.\"\"\"\n        pass\n\n    @classmethod\n    def finalize_options(cls):\n        \"\"\"Required by distutils.\"\"\"\n        pass\n\n    @classmethod\n    def run(cls):\n        \"\"\"Check variables.\"\"\"\n        project = __import__(IMPORT, fromlist=[''])\n        for expected, var in [('@Robpol86', '__author__'), (LICENSE, '__license__'), (VERSION, '__version__')]:\n            if getattr(project, var) != expected:\n                raise SystemExit('Mismatch: {0}'.format(var))\n        # Check changelog.\n        if not re.compile(r'^%s - \\d{4}-\\d{2}-\\d{2}[\\r\\n]' % VERSION, re.MULTILINE).search(readme()):\n            raise SystemExit('Version not found in readme/changelog file.')\n        # Check tox.\n        if INSTALL_REQUIRES:\n            contents = readme('tox.ini')\n            section = re.compile(r'[\\r\\n]+install_requires =[\\r\\n]+(.+?)[\\r\\n]+\\w', re.DOTALL).findall(contents)\n            if not section:\n                raise SystemExit('Missing install_requires section in tox.ini.')\n            in_tox = re.findall(r'    ([^=]+)==[\\w\\d.-]+', section[0])\n            if INSTALL_REQUIRES != in_tox:\n                raise SystemExit('Missing/unordered pinned dependencies in tox.ini.')\n\n\nif __name__ == '__main__':\n    setup(\n        author='@Robpol86',\n        author_email='robpol86@gmail.com',\n        classifiers=[\n            'Development Status :: 5 - Production/Stable',\n            'Environment :: Web Environment',\n            'Environment :: MacOS X',\n            'Environment :: Win32 (MS Windows)',\n            'Framework :: Flask',\n            'Intended Audience :: Developers',\n            'License :: OSI Approved :: MIT License',\n            'Operating System :: MacOS :: MacOS X',\n            'Operating System :: Microsoft :: Windows',\n            'Operating System :: POSIX',\n            'Operating System :: POSIX :: Linux',\n            'Programming Language :: Python :: 2.6',\n            'Programming Language :: Python :: 2.7',\n            'Programming Language :: Python :: 3.3',\n            'Programming Language :: Python :: 3.4',\n            'Programming Language :: Python :: Implementation :: PyPy',\n            'Topic :: Software Development :: Libraries',\n        ],\n        cmdclass=dict(check_version=CheckVersion),\n        description='Celery support for Flask without breaking PyCharm inspections.',\n        install_requires=INSTALL_REQUIRES,\n        keywords='flask celery redis',\n        license=LICENSE,\n        long_description=readme(),\n        name=NAME,\n        py_modules=[IMPORT],\n        url='https://github.com/Robpol86/' + NAME,\n        version=VERSION,\n        zip_safe=False,\n    )\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "\"\"\"Allows importing.\"\"\"\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "\"\"\"Configure tests.\"\"\"\n\nimport threading\nimport time\n\nimport pytest\nfrom celery.signals import worker_ready\n\nfrom tests.instances import app, celery\n\nWORKER_READY = list()\n\n\nclass Worker(threading.Thread):\n    \"\"\"Run the Celery worker in a background thread.\"\"\"\n\n    def run(self):\n        \"\"\"Run the thread.\"\"\"\n        celery_args = ['-C', '-q', '-c', '1', '-P', 'solo', '--without-gossip']\n        with app.app_context():\n            celery.worker_main(celery_args)\n\n\n@worker_ready.connect\ndef on_worker_ready(**_):\n    \"\"\"Called when the Celery worker thread is ready to do work.\n\n    This is to avoid race conditions since everything is in one python process.\n    \"\"\"\n    WORKER_READY.append(True)\n\n\n@pytest.fixture(autouse=True, scope='session')\ndef celery_worker():\n    \"\"\"Start the Celery worker in a background thread.\"\"\"\n    thread = Worker()\n    thread.daemon = True\n    thread.start()\n    for i in range(10):  # Wait for worker to finish initializing to avoid a race condition I've been experiencing.\n        if WORKER_READY:\n            break\n        time.sleep(1)\n"
  },
  {
    "path": "tests/instances.py",
    "content": "\"\"\"Handle Flask and Celery application global-instances.\"\"\"\n\nimport os\n\nfrom flask import Flask\nfrom flask_redis import Redis\nfrom flask_sqlalchemy import SQLAlchemy\n\nfrom flask_celery import Celery, single_instance\n\n\ndef generate_config():\n    \"\"\"Generate a Flask config dict with settings for a specific broker based on an environment variable.\n\n    To be merged into app.config.\n\n    :return: Flask config to be fed into app.config.update().\n    :rtype: dict\n    \"\"\"\n    config = dict()\n\n    if os.environ.get('BROKER') == 'rabbit':\n        config['CELERY_BROKER_URL'] = 'amqp://user:pass@localhost//'\n    elif os.environ.get('BROKER') == 'redis':\n        config['REDIS_URL'] = 'redis://localhost/1'\n        config['CELERY_BROKER_URL'] = config['REDIS_URL']\n    elif os.environ.get('BROKER', '').startswith('redis_sock,'):\n        config['REDIS_URL'] = 'redis+socket://' + os.environ['BROKER'].split(',', 1)[1]\n        config['CELERY_BROKER_URL'] = config['REDIS_URL']\n    elif os.environ.get('BROKER') == 'mongo':\n        config['CELERY_BROKER_URL'] = 'mongodb://user:pass@localhost/test'\n    elif os.environ.get('BROKER') == 'couch':\n        config['CELERY_BROKER_URL'] = 'couchdb://user:pass@localhost/test'\n    elif os.environ.get('BROKER') == 'beanstalk':\n        config['CELERY_BROKER_URL'] = 'beanstalk://user:pass@localhost/test'\n    elif os.environ.get('BROKER') == 'iron':\n        config['CELERY_BROKER_URL'] = 'ironmq://project:token@/test'\n    else:\n        if os.environ.get('BROKER') == 'mysql':\n            config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://user:pass@localhost/flask_celery_helper_test'\n        elif os.environ.get('BROKER') == 'postgres':\n            config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+pg8000://user1:pass@localhost/flask_celery_helper_test'\n        else:\n            file_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'test_database.sqlite')\n            config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + file_path\n        config['CELERY_BROKER_URL'] = 'sqla+' + config['SQLALCHEMY_DATABASE_URI']\n        config['CELERY_RESULT_BACKEND'] = 'db+' + config['SQLALCHEMY_DATABASE_URI']\n\n    if 'CELERY_BROKER_URL' in config and 'CELERY_RESULT_BACKEND' not in config:\n        config['CELERY_RESULT_BACKEND'] = config['CELERY_BROKER_URL']\n\n    return config\n\n\ndef generate_context(config):\n    \"\"\"Create the Flask app context and initializes any extensions such as Celery, Redis, SQLAlchemy, etc.\n\n    :param dict config: Partial Flask config dict from generate_config().\n\n    :return: The Flask app instance.\n    \"\"\"\n    flask_app = Flask(__name__)\n    flask_app.config.update(config)\n    flask_app.config['TESTING'] = True\n    flask_app.config['CELERY_ACCEPT_CONTENT'] = ['pickle']\n\n    if 'SQLALCHEMY_DATABASE_URI' in flask_app.config:\n        db = SQLAlchemy(flask_app)\n        db.engine.execute('DROP TABLE IF EXISTS celery_tasksetmeta;')\n    elif 'REDIS_URL' in flask_app.config:\n        redis = Redis(flask_app)\n        redis.flushdb()\n\n    Celery(flask_app)\n    return flask_app\n\n\ndef get_flask_celery_apps():\n    \"\"\"Call generate_context() and generate_config().\n\n    :return: First item is the Flask app instance, second is the Celery app instance.\n    :rtype: tuple\n    \"\"\"\n    config = generate_config()\n    flask_app = generate_context(config=config)\n    celery_app = flask_app.extensions['celery'].celery\n    return flask_app, celery_app\n\n\napp, celery = get_flask_celery_apps()\n\n\n@celery.task(bind=True)\n@single_instance\ndef add(x, y):\n    \"\"\"Celery task: add numbers.\"\"\"\n    return x + y\n\n\n@celery.task(bind=True)\n@single_instance(include_args=True, lock_timeout=20)\ndef mul(x, y):\n    \"\"\"Celery task: multiply numbers.\"\"\"\n    return x * y\n\n\n@celery.task(bind=True)\n@single_instance()\ndef sub(x, y):\n    \"\"\"Celery task: subtract numbers.\"\"\"\n    return x - y\n\n\n@celery.task(bind=True, time_limit=70)\n@single_instance\ndef add2(x, y):\n    \"\"\"Celery task: add numbers.\"\"\"\n    return x + y\n\n\n@celery.task(bind=True, soft_time_limit=80)\n@single_instance\ndef add3(x, y):\n    \"\"\"Celery task: add numbers.\"\"\"\n    return x + y\n"
  },
  {
    "path": "tests/test_class.py",
    "content": "\"\"\"Test the Celery class.\"\"\"\n\nimport pytest\n\nfrom flask_celery import Celery\nfrom tests.instances import app\n\n\nclass FakeApp(object):\n    \"\"\"Mock Flask application.\"\"\"\n\n    config = dict(CELERY_BROKER_URL='redis://localhost', CELERY_RESULT_BACKEND='redis://localhost')\n    static_url_path = ''\n    import_name = ''\n\n    def register_blueprint(self, _):\n        \"\"\"Mock register_blueprint method.\"\"\"\n        pass\n\n\ndef test_multiple():\n    \"\"\"Test attempted re-initialization of extension.\"\"\"\n    assert 'celery' in app.extensions\n\n    with pytest.raises(ValueError):\n        Celery(app)\n\n\ndef test_one_dumb_line():\n    \"\"\"For test coverage.\"\"\"\n    flask_app = FakeApp()\n    Celery(flask_app)\n    assert 'celery' in flask_app.extensions\n"
  },
  {
    "path": "tests/test_collision.py",
    "content": "\"\"\"Test single-instance collision.\"\"\"\n\nimport pytest\n\nfrom flask_celery import _select_manager, OtherInstanceError\nfrom tests.instances import celery\n\nPARAMS = [('tests.instances.add', 8), ('tests.instances.mul', 16), ('tests.instances.sub', 0)]\n\n\n@pytest.mark.parametrize('task_name,expected', PARAMS)\ndef test_basic(task_name, expected):\n    \"\"\"Test no collision.\"\"\"\n    task = celery.tasks[task_name]\n    assert expected == task.apply_async(args=(4, 4)).get()\n\n\n@pytest.mark.parametrize('task_name,expected', PARAMS)\ndef test_collision(task_name, expected):\n    \"\"\"Test single-instance collision.\"\"\"\n    manager_class = _select_manager(celery.backend.__class__.__name__)\n    manager_instance = list()\n    task = celery.tasks[task_name]\n\n    # First run the task and prevent it from removing the lock.\n    def new_exit(self, *_):\n        manager_instance.append(self)\n        return None\n    original_exit = manager_class.__exit__\n    setattr(manager_class, '__exit__', new_exit)\n    assert expected == task.apply_async(args=(4, 4)).get()\n    setattr(manager_class, '__exit__', original_exit)\n    assert manager_instance[0].is_already_running is True\n\n    # Now run it again.\n    with pytest.raises(OtherInstanceError) as e:\n        task.apply_async(args=(4, 4)).get()\n    if manager_instance[0].include_args:\n        assert str(e.value).startswith('Failed to acquire lock, {0}.args.'.format(task_name))\n    else:\n        assert 'Failed to acquire lock, {0} already running.'.format(task_name) == str(e.value)\n    assert manager_instance[0].is_already_running is True\n\n    # Clean up.\n    manager_instance[0].reset_lock()\n    assert manager_instance[0].is_already_running is False\n\n    # Once more.\n    assert expected == task.apply_async(args=(4, 4)).get()\n\n\ndef test_include_args():\n    \"\"\"Test single-instance collision with task arguments taken into account.\"\"\"\n    manager_class = _select_manager(celery.backend.__class__.__name__)\n    manager_instance = list()\n    task = celery.tasks['tests.instances.mul']\n\n    # First run the tasks and prevent them from removing the locks.\n    def new_exit(self, *_):\n        \"\"\"Expected to be run twice.\"\"\"\n        manager_instance.append(self)\n        return None\n    original_exit = manager_class.__exit__\n    setattr(manager_class, '__exit__', new_exit)\n    assert 16 == task.apply_async(args=(4, 4)).get()\n    assert 20 == task.apply_async(args=(5, 4)).get()\n    setattr(manager_class, '__exit__', original_exit)\n    assert manager_instance[0].is_already_running is True\n    assert manager_instance[1].is_already_running is True\n\n    # Now run them again.\n    with pytest.raises(OtherInstanceError) as e:\n        task.apply_async(args=(4, 4)).get()\n    assert str(e.value).startswith('Failed to acquire lock, tests.instances.mul.args.')\n    assert manager_instance[0].is_already_running is True\n    with pytest.raises(OtherInstanceError) as e:\n        task.apply_async(args=(5, 4)).get()\n    assert str(e.value).startswith('Failed to acquire lock, tests.instances.mul.args.')\n    assert manager_instance[1].is_already_running is True\n\n    # Clean up.\n    manager_instance[0].reset_lock()\n    assert manager_instance[0].is_already_running is False\n    manager_instance[1].reset_lock()\n    assert manager_instance[1].is_already_running is False\n\n    # Once more.\n    assert 16 == task.apply_async(args=(4, 4)).get()\n    assert 20 == task.apply_async(args=(5, 4)).get()\n"
  },
  {
    "path": "tests/test_timeout.py",
    "content": "\"\"\"Test single-instance lock timeout.\"\"\"\n\nimport time\n\nimport pytest\n\nfrom flask_celery import _select_manager, OtherInstanceError\nfrom tests.instances import celery\n\n\n@pytest.mark.parametrize('task_name,timeout', [\n    ('tests.instances.mul', 20), ('tests.instances.add', 300), ('tests.instances.add2', 70),\n    ('tests.instances.add3', 80)\n])\ndef test_instances(task_name, timeout):\n    \"\"\"Test task instances.\"\"\"\n    manager_class = _select_manager(celery.backend.__class__.__name__)\n    manager_instance = list()\n    task = celery.tasks[task_name]\n    original_exit = manager_class.__exit__\n\n    def new_exit(self, *_):\n        manager_instance.append(self)\n        return original_exit(self, *_)\n    setattr(manager_class, '__exit__', new_exit)\n    task.apply_async(args=(4, 4)).get()\n    setattr(manager_class, '__exit__', original_exit)\n    assert timeout == manager_instance[0].timeout\n\n\n@pytest.mark.parametrize('key,value', [('CELERYD_TASK_TIME_LIMIT', 200), ('CELERYD_TASK_SOFT_TIME_LIMIT', 100)])\ndef test_settings(key, value):\n    \"\"\"Test different Celery time limit settings.\"\"\"\n    celery.conf.update({key: value})\n    manager_class = _select_manager(celery.backend.__class__.__name__)\n    manager_instance = list()\n    original_exit = manager_class.__exit__\n\n    def new_exit(self, *_):\n        manager_instance.append(self)\n        return original_exit(self, *_)\n    setattr(manager_class, '__exit__', new_exit)\n    tasks = [\n        ('tests.instances.mul', 20), ('tests.instances.add', value), ('tests.instances.add2', 70),\n        ('tests.instances.add3', 80)\n    ]\n\n    for task_name, timeout in tasks:\n        task = celery.tasks[task_name]\n        task.apply_async(args=(4, 4)).get()\n        assert timeout == manager_instance.pop().timeout\n    setattr(manager_class, '__exit__', original_exit)\n\n    celery.conf.update({key: None})\n\n\ndef test_expired():\n    \"\"\"Test timeout expired task instances.\"\"\"\n    celery.conf.update({'CELERYD_TASK_TIME_LIMIT': 5})\n    manager_class = _select_manager(celery.backend.__class__.__name__)\n    manager_instance = list()\n    task = celery.tasks['tests.instances.add']\n    original_exit = manager_class.__exit__\n\n    def new_exit(self, *_):\n        manager_instance.append(self)\n        return None\n    setattr(manager_class, '__exit__', new_exit)\n\n    # Run the task and don't remove the lock after a successful run.\n    assert 8 == task.apply_async(args=(4, 4)).get()\n    setattr(manager_class, '__exit__', original_exit)\n\n    # Run again, lock is still active so this should fail.\n    with pytest.raises(OtherInstanceError):\n        task.apply_async(args=(4, 4)).get()\n\n    # Wait 5 seconds (per CELERYD_TASK_TIME_LIMIT), then re-run, should work.\n    time.sleep(5)\n    assert 8 == task.apply_async(args=(4, 4)).get()\n    celery.conf.update({'CELERYD_TASK_TIME_LIMIT': None})\n"
  },
  {
    "path": "tox.ini",
    "content": "[general]\ninstall_requires =\n    flask==0.12\n    celery==3.1.11\nname = flask_celery\n\n[tox]\nenvlist = lint,py{34,27}\n\n[testenv]\ncommands =\n    py.test --cov-report term-missing --cov-report xml --cov {[general]name} --cov-config tox.ini {posargs:tests}\ndeps =\n    {[general]install_requires}\n    Flask-Redis-Helper==1.0.0\n    Flask-SQLAlchemy==2.1\n    pg8000==1.10.6\n    PyMySQL==0.7.9\n    pytest-cov==2.4.0\npassenv =\n    BROKER\nusedevelop = True\n\n[testenv:lint]\ncommands =\n    python setup.py check --strict\n    python setup.py check --strict -m\n    python setup.py check --strict -s\n    python setup.py check_version\n    flake8 --application-import-names={[general]name},tests\n    pylint --rcfile=tox.ini setup.py {[general]name}\ndeps =\n    {[general]install_requires}\n    flake8-docstrings==1.0.3\n    flake8-import-order==0.11\n    flake8==3.2.1\n    pep8-naming==0.4.1\n    pylint==1.6.5\n\n[flake8]\nexclude = .tox/*,build/*,docs/*,env/*,get-pip.py\nimport-order-style = smarkets\nmax-line-length = 120\nstatistics = True\n\n[pylint]\ndisable =\n    locally-disabled,\n    missing-docstring,\n    protected-access,\n    too-few-public-methods,\nignore = .tox/*,build/*,docs/*,env/*,get-pip.py\nmax-args = 7\nmax-line-length = 120\nreports = no\n\n[run]\nbranch = True\n"
  }
]