diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index db0fe4d..9f353f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,8 @@ jobs: - name: build docs run: | - pip install sphinx Pygments furo sphinx-copybutton sphinx-inline-tabs myst-parser colorama sphinx-panels - cd docs - sphinx-build -E -b html . _build + pip install tox + tox -e docs # Runs a set of commands using the runners shell - name: publish diff --git a/COMMON-ISSUES.md b/COMMON-ISSUES.md deleted file mode 100644 index 79946b1..0000000 --- a/COMMON-ISSUES.md +++ /dev/null @@ -1,91 +0,0 @@ -

Common Issues

- -

Context Issues

- -On each function registered to the scheduler that requires Flask app context, assuming your `APScheduler` object is called `scheduler`, include: - -```python -with scheduler.app.app_context(): - # your code -``` - -

Mixing Persistent Jobstores with Tasks from Config

- -When using a persistent jobstore, do not register jobs from a configuration file. They should be registered by decorators [(example)](https://github.com/viniciuschiele/flask-apscheduler/blob/master/examples/decorated.py) or by using the `add_job` method. - -

Mixing Persistent Jobstores with Tasks in __init__.py

- -Tasks registered via decorator or the `add_job` method should not be loaded in your `app/__init__.py` if you are using a persistent job store. If they must be loaded upon app creation, a workaround would be as follows: - -```python -# app/__init__.py - -scheduler = APScheduler() -db = SQLAlchemy() - - - -def create_app(config_class=Config): - app = Flask(__name__) - app.config.from_object(config_class) - db.init_app(app) - scheduler.init_app(app) - scheduler.start() - - @app.before_first_request - def load_tasks(): - from app import tasks - - return app -``` -```python -# app/tasks.py - -@scheduler.task('cron', id='do_renewals', hour=9, minute=5) -def scheduled_function(): - # your scheduled task code here -``` - -Your task will then be registered the first time that someone makes any request to the Flask app. - -

Trying to Load Tasks Outside Module-Level Import

- -If your task was loading correctly with the default memory jobstore, but does not load correctly from a persistent jobstore, this is because functions to be loaded as jobs must be available as module-level imports when used with persistent jobstores. They cannot be nested within other functions or classes. - -So this function could be added using the `add_job` method: -```python -# app/tasks.py - -def your_function(): - # your scheduled task code here -``` -```python -# other_module.py - -scheduler.add_job(
) -``` - -You could accomplish the same by importing modules that contain decorated functions (un-nested, at the module level): -```python -# app/tasks.py - -@scheduler.task('cron', id='do_renewals', hour=9, minute=5) -def scheduled_function(): - # your scheduled task code here -``` -```python -# other_module.py - -from app import tasks -``` - -But this would not work: -```python -# some_module.py - -def do_stuff(): - # do some stuff before registering a task - # then attempt to register a task, which will fail due to nesting - @scheduler.task('cron', id='do_renewals', hour=9, minute=5) - def scheduled_function(): - # your scheduled task code here diff --git a/README.rst b/README.rst index c05f530..8a14d16 100644 --- a/README.rst +++ b/README.rst @@ -19,163 +19,12 @@ You can install Flask-APScheduler via Python Package Index (PyPI_),:: pip install Flask-APScheduler -Documentation -=============== - -Setup ------ - -* Create a flask application. For an example, see `this tutorial `_ -* Import and initialize ``Flask-APScheduler`` -* Set any configuration needed - -A basic example will looks like this. - -.. code-block:: python - - from flask import Flask - # import Flask-APScheduler - from flask_apscheduler import APScheduler - - # set configuration values - class Config(object): - SCHEDULER_API_ENABLED = True - - # create app - app = Flask(__name__) - app.config.from_object(Config()) - - # initialize scheduler - scheduler = APScheduler() - # if you don't wanna use a config, you can set options here: - # scheduler.api_enabled = True - scheduler.init_app(app) - scheduler.start() - - - if __name__ == '__main__': - app.run() - -Adding Jobs ------------ - -Jobs can be added to the scheduler when the app starts. They are created in decorated functions, which should be imported before ``app.run()`` is called. - -.. code-block:: python - - # interval example - @scheduler.task('interval', id='do_job_1', seconds=30, misfire_grace_time=900) - def job1(): - print('Job 1 executed') - - - # cron examples - @scheduler.task('cron', id='do_job_2', minute='*') - def job2(): - print('Job 2 executed') - - - @scheduler.task('cron', id='do_job_3', week='*', day_of_week='sun') - def job3(): - print('Job 3 executed') - - -Jobs can also be added after you app is running - -.. code-block:: python - - scheduler.add_job(**args) - -If you wish to use anything from your Flask app context inside the job you can use something like this - -.. code-block:: python - - def blah(): - with scheduler.app.app_context(): - # do stuff - -Logging -------- -All scheduler events can be used to trigger logging functions. See `APScheduler `_ for a list of available events. -If you are using your Flask app context inside of a function triggered by a scheduler event can include something like this - -.. code-block:: python - - def blah(): - with scheduler.app.app_context(): - # do stuff - - scheduler.add_listener(blah, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR) - - -API ---- - -Flask-APScheduler comes with a build-in API. This can be enabled/disabled in your flask configuration. - -.. code-block:: python - - SCHEDULER_API_ENABLED: True - - -- /scheduler [GET] > returns basic information about the webapp -- /scheduler/jobs [POST json job data] > adds a job to the scheduler -- /scheduler/jobs/ [GET] > returns json of job details -- /scheduler/jobs [GET] > returns json with details of all jobs -- /scheduler/jobs/ [DELETE] > deletes job from scheduler -- /scheduler/jobs/ [PATCH json job data] > updates an already existing job -- /scheduler/jobs//pause [POST] > pauses a job, returns json of job details -- /scheduler/jobs//resume [POST] > resumes a job, returns json of job details -- /scheduler/jobs//run [POST] > runs a job now, returns json of job details - - -Scheduler ---------- - -Other commands can be passed to the scheduler and are rather self explainatory: - -- scheduler.start() -- scheduler.shutdown() -- scheduler.pause() > stops any job from starting. Already running jobs not affected. -- scheduler.resume() > allows scheduled jobs to begin running. -- scheduler.add_listener(,) -- scheduler.remove_listener() -- scheduler.add_job(,, **kwargs) -- scheduler.remove_job(, **) -- scheduler.remove_all_jobs(**) -- scheduler.get_job(,**) -- scheduler.modify_job(,**, **kwargs) -- scheduler.pause_job(, **) -- scheduler.resume_job(, **) -- scheduler.run_job(, **) -- scheduler.authenticate() - - -Configuration -------------- - -Configuration options specific to ``Flask-APScheduler``: - -.. code-block:: python - - SCHEDULER_API_ENABLED: - -Other configuration options are included from `APScheduler `_ - - -Tips ----- - -When running Flask-APScheduler on a wsgi process only **1** worker should be enabled. APScheduler 3.0 will only work with a single worker process. Jobstores cannot be shared among multiple schedulers. - -See `APScheduler's `_ documentation for further help. - -Take a look at the examples_ to see how it works. - -Also take a look at `COMMON-ISSUES.md `_ for help. +Documentation +=============== +`See Flask APSchedulers Documentation. `_ Feedback diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..ce0d807 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,6 @@ +reformat +flake8 +flake8-bandit +pygments +black +pylint \ No newline at end of file diff --git a/docs-requirements.txt b/docs-requirements.txt new file mode 100644 index 0000000..ccf2f9e --- /dev/null +++ b/docs-requirements.txt @@ -0,0 +1,8 @@ +sphinx +Pygments +furo +sphinx-copybutton +sphinx-inline-tabs +myst-parser +colorama +sphinx-panels \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..259988a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,56 @@ +# flake8: noqa +# pylint: skip-file +# Configuration file for the Sphinx documentation builder. + +# +# -- Path setup -------------------------------------------------------------- +# + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent)) + + +# +# -- Project information ----------------------------------------------------- +# + +project = "Flask APScheduler" +copyright = "2015, Vinicius Chiele" + + +# +# -- General configuration --------------------------------------------------- +# + +extensions = [ + "sphinx.ext.extlinks", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "sphinx.ext.todo", + "sphinx_copybutton", + "sphinx_inline_tabs", + "sphinx_panels", + "myst_parser", +] + +templates_path = ["_templates"] + +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "venv"] + +pygments_style = "colorful" + +# +# -- Options for HTML output ------------------------------------------------- +# + +html_theme = "furo" +html_title = "Flask APScheduler" +#html_logo = "images/icon-512x512.png" +#html_favicon = "images/favicon.ico" + +html_theme_options = { + #"sidebar_hide_name": True, +} diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..50dd468 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,22 @@ +Flask APScheduler Docs +=========================== + + +.. toctree:: + :maxdepth: 2 + :hidden: + :titlesonly: + + Overview + rst/install.rst + rst/configuration.rst + rst/usage.rst + rst/logging.rst + rst/api.rst + rst/examples.rst + rst/tips.rst + + + +.. include:: rst/readme.rst + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..2119f51 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/placeholder b/docs/placeholder deleted file mode 100644 index 8b13789..0000000 --- a/docs/placeholder +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/rst/api.rst b/docs/rst/api.rst new file mode 100644 index 0000000..47a1244 --- /dev/null +++ b/docs/rst/api.rst @@ -0,0 +1,42 @@ +*** +API +*** + +Flask-APScheduler comes with a build-in API. This can be enabled/disabled in your flask configuration. + +.. code-block:: python + + SCHEDULER_API_ENABLED: True + + +- /scheduler [GET] > returns basic information about the webapp +- /scheduler/jobs [POST json job data] > adds a job to the scheduler +- /scheduler/jobs/ [GET] > returns json of job details +- /scheduler/jobs [GET] > returns json with details of all jobs +- /scheduler/jobs/ [DELETE] > deletes job from scheduler +- /scheduler/jobs/ [PATCH json job data] > updates an already existing job +- /scheduler/jobs//pause [POST] > pauses a job, returns json of job details +- /scheduler/jobs//resume [POST] > resumes a job, returns json of job details +- /scheduler/jobs//run [POST] > runs a job now, returns json of job details + + +Scheduler +--------- + +Other commands can be passed to the scheduler and are rather self explanatory: + +- scheduler.start() +- scheduler.shutdown() +- scheduler.pause() > stops any job from starting. Already running jobs not affected. +- scheduler.resume() > allows scheduled jobs to begin running. +- scheduler.add_listener(,) +- scheduler.remove_listener() +- scheduler.add_job(,, \*\*kwargs) +- scheduler.remove_job(, \*\*) +- scheduler.remove_all_jobs(\*\*) +- scheduler.get_job(,\*\*) +- scheduler.modify_job(,\*\*, \*\*kwargs) +- scheduler.pause_job(, \*\*) +- scheduler.resume_job(, \*\*) +- scheduler.run_job(, \*\*) +- scheduler.authenticate() \ No newline at end of file diff --git a/docs/rst/configuration.rst b/docs/rst/configuration.rst new file mode 100644 index 0000000..dd5d7e4 --- /dev/null +++ b/docs/rst/configuration.rst @@ -0,0 +1,12 @@ +************* +Configuration +************* + + +Configuration options specific to ``Flask-APScheduler``: + +.. code-block:: python + + SCHEDULER_API_ENABLED: + +Other configuration options are included from `APScheduler `_ diff --git a/docs/rst/examples.rst b/docs/rst/examples.rst new file mode 100644 index 0000000..ed738ad --- /dev/null +++ b/docs/rst/examples.rst @@ -0,0 +1,14 @@ +******** +Examples +******** + + +See the examples of how to use Flask-APScheduler + +- `Application Factory `_ +- `Advanced Job Schedules `_ +- `Allowed Hosts `_ +- `Authentication `_ +- `Decorator Usage `_ +- `Flask Context `_ +- `Jobs from Config `_ \ No newline at end of file diff --git a/docs/rst/install.rst b/docs/rst/install.rst new file mode 100644 index 0000000..9073ac4 --- /dev/null +++ b/docs/rst/install.rst @@ -0,0 +1,9 @@ +******* +Install +******* + +Installation +=============== +You can install Flask-APScheduler via Python Package Index (`PyPi `_),:: + + pip install Flask-APScheduler \ No newline at end of file diff --git a/docs/rst/logging.rst b/docs/rst/logging.rst new file mode 100644 index 0000000..6562af7 --- /dev/null +++ b/docs/rst/logging.rst @@ -0,0 +1,23 @@ +******* +Logging +******* + + +Logging +------- + +All scheduler events can be used to trigger logging functions. See `APScheduler `_ for a list of available events. + +If you are using your Flask app context inside of a function triggered by a scheduler event can include something like this + +.. code-block:: python + + def blah(): + with scheduler.app.app_context(): + # do stuff + + scheduler.add_listener(blah, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR) + + + + diff --git a/docs/rst/readme.rst b/docs/rst/readme.rst new file mode 100644 index 0000000..408520b --- /dev/null +++ b/docs/rst/readme.rst @@ -0,0 +1,55 @@ +================================= +Flask-APScheduler +================================= +Flask-APScheduler is a Flask extension which adds support for the APScheduler. + +|Version| |Coverage| |CodeClimate| |Travis| + +Features +=============== +- Loads scheduler configuration from Flask configuration. +- Loads job definitions from Flask configuration. +- Allows to specify the hostname which the scheduler will run on. +- Provides a REST API to manage the scheduled jobs. +- Provides authentication for the REST API. + +Installation +=============== +You can install Flask-APScheduler via Python Package Index (PyPI_) + +.. code:: python + + pip install Flask-APScheduler + + + +Documentation +=============== + +`See Flask APSchedulers Documentation. `_ + + +Feedback +=============== +Please use the Issues_ for feature requests and troubleshooting usage. + +.. |Version| image:: https://img.shields.io/pypi/v/flask-apscheduler.svg + :target: https://pypi.python.org/pypi/Flask-APScheduler + +.. |Coverage| image:: https://codecov.io/github/viniciuschiele/flask-apscheduler/coverage.svg + :target: https://codecov.io/github/viniciuschiele/flask-apscheduler + +.. |Travis| image:: https://travis-ci.org/viniciuschiele/flask-apscheduler.svg + :target: https://travis-ci.org/viniciuschiele/flask-apscheduler + +.. |CodeClimate| image:: https://codeclimate.com/github/viniciuschiele/flask-apscheduler/badges/gpa.svg + :target: https://codeclimate.com/github/viniciuschiele/flask-apscheduler + +.. _examples: https://github.com/viniciuschiele/flask-apscheduler/tree/master/examples + +.. _PyPi: https://pypi.python.org/pypi/Flask-APScheduler + +.. _Issues: https://github.com/viniciuschiele/flask-apscheduler/issues + +.. _CommonIssues: + diff --git a/docs/rst/tips.rst b/docs/rst/tips.rst new file mode 100644 index 0000000..9bb70d3 --- /dev/null +++ b/docs/rst/tips.rst @@ -0,0 +1,101 @@ +**** +Tips +**** + + +When running Flask-APScheduler on a wsgi process only **1** worker should be enabled. APScheduler 3.0 will only work with a single worker process. Jobstores cannot be shared among multiple schedulers. + +See `APScheduler's `_ documentation for further help. + +Take a look at the :doc:`examples` to see how it works. + + +Mixing Persistent Jobstores with Tasks from Config +###################################################### + +When using a persistent jobstore, do not register jobs from a configuration file. They should be registered by decorators (`see example `_), or by using the `add_job` method. + + +Mixing Persistent Jobstores with Tasks in __init__.py +###################################################### + +Tasks registered via decorator or the `add_job` method should not be loaded in your `app/__init__.py` if you are using a persistent job store. If they must be loaded upon app creation, a workaround would be as follows: + +.. code:: python + + # app/__init__.py + + scheduler = APScheduler() + db = SQLAlchemy() + + + + def create_app(config_class=Config): + app = Flask(__name__) + app.config.from_object(config_class) + db.init_app(app) + scheduler.init_app(app) + scheduler.start() + + @app.before_first_request + def load_tasks(): + from app import tasks + + return app + + + # app/tasks.py + + @scheduler.task('cron', id='do_renewals', hour=9, minute=5) + def scheduled_function(): + # your scheduled task code here + + +Your task will then be registered the first time that someone makes any request to the Flask app. + +Trying to Load Tasks Outside Module-Level Import +################################################ + +If your task was loading correctly with the default memory jobstore, but does not load correctly from a persistent jobstore, this is because functions to be loaded as jobs must be available as module-level imports when used with persistent jobstores. They cannot be nested within other functions or classes. + +So this function could be added using the `add_job` method: + +.. code:: python + + # app/tasks.py + + def your_function(): + # your scheduled task code here + + # other_module.py + + scheduler.add_job(
) + +You could accomplish the same by importing modules that contain decorated functions (un-nested, at the module level): + +.. code:: python + + # app/tasks.py + + @scheduler.task('cron', id='do_renewals', hour=9, minute=5) + def scheduled_function(): + # your scheduled task code here + + + # other_module.py + + from app import tasks + + +But this would not work: + +.. code:: python + + # some_module.py + + def do_stuff(): + # do some stuff before registering a task + # then attempt to register a task, which will fail due to nesting + @scheduler.task('cron', id='do_renewals', hour=9, minute=5) + def scheduled_function(): + # your scheduled task code here diff --git a/docs/rst/usage.rst b/docs/rst/usage.rst new file mode 100644 index 0000000..9274fdb --- /dev/null +++ b/docs/rst/usage.rst @@ -0,0 +1,83 @@ +***************** +Basic Application +***************** + +Setup +----- + +* Create a flask application. For an example, see `this tutorial `_ +* Import and initialize ``Flask-APScheduler`` +* Set any configuration needed + +A basic example will looks like this. + +.. code-block:: python + + from flask import Flask + from flask_apscheduler import APScheduler + + # set configuration values + class Config: + SCHEDULER_API_ENABLED = True + + # create app + app = Flask(__name__) + app.config.from_object(Config()) + + # initialize scheduler + scheduler = APScheduler() + # if you don't wanna use a config, you can set options here: + # scheduler.api_enabled = True + scheduler.init_app(app) + scheduler.start() + + + if __name__ == '__main__': + app.run() + + +Adding Jobs +----------- + +Jobs can be added to the scheduler when the app starts. They are created in decorated functions, which should be imported before ``app.run()`` is called. + +.. code-block:: python + + # interval example + @scheduler.task('interval', id='do_job_1', seconds=30, misfire_grace_time=900) + def job1(): + print('Job 1 executed') + + + # cron examples + @scheduler.task('cron', id='do_job_2', minute='*') + def job2(): + print('Job 2 executed') + + + @scheduler.task('cron', id='do_job_3', week='*', day_of_week='sun') + def job3(): + print('Job 3 executed') + + + scheduler.start() + + +Jobs can also be added after you app is running + +.. code-block:: python + + scheduler.start() + scheduler.add_job(**args) + + +Flask Context +------------- + +If you wish to use anything from your Flask app context inside the job you can use something like this + +.. code-block:: python + + def blah(): + with scheduler.app.app_context(): + # do stuff diff --git a/examples/advanced.py b/examples/advanced.py index 3d80d6c..13b7a93 100644 --- a/examples/advanced.py +++ b/examples/advanced.py @@ -1,40 +1,43 @@ +"""Advanced example using other configuration options.""" + from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from flask import Flask + from flask_apscheduler import APScheduler -class Config(object): +class Config: + """App configuration.""" + JOBS = [ { - 'id': 'job1', - 'func': 'advanced:job1', - 'args': (1, 2), - 'trigger': 'interval', - 'seconds': 10 + "id": "job1", + "func": "advanced:job1", + "args": (1, 2), + "trigger": "interval", + "seconds": 10, } ] - SCHEDULER_JOBSTORES = { - 'default': SQLAlchemyJobStore(url='sqlite://') - } + SCHEDULER_JOBSTORES = {"default": SQLAlchemyJobStore(url="sqlite://")} - SCHEDULER_EXECUTORS = { - 'default': {'type': 'threadpool', 'max_workers': 20} - } + SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 20}} - SCHEDULER_JOB_DEFAULTS = { - 'coalesce': False, - 'max_instances': 3 - } + SCHEDULER_JOB_DEFAULTS = {"coalesce": False, "max_instances": 3} SCHEDULER_API_ENABLED = True -def job1(a, b): - print(str(a) + ' ' + str(b)) +def job1(var_one, var_two): + """Demo job function. + + :param var_two: + :param var_two: + """ + print(str(var_one) + " " + str(var_two)) -if __name__ == '__main__': +if __name__ == "__main__": app = Flask(__name__) app.config.from_object(Config()) diff --git a/examples/allowed_host.py b/examples/allowed_host.py index 8e3138d..b4f7171 100644 --- a/examples/allowed_host.py +++ b/examples/allowed_host.py @@ -1,33 +1,43 @@ +"""Allowed hosts example.""" + from flask import Flask + from flask_apscheduler import APScheduler -class Config(object): +class Config: + """App configuration.""" + JOBS = [ { - 'id': 'job1', - 'func': 'allowed_host:job1', - 'args': (1, 2), - 'trigger': 'interval', - 'seconds': 10 + "id": "job1", + "func": "allowed_host:job1", + "args": (1, 2), + "trigger": "interval", + "seconds": 10, } ] - SCHEDULER_ALLOWED_HOSTS = ['my_servers_name'] + SCHEDULER_ALLOWED_HOSTS = ["my_servers_name"] SCHEDULER_API_ENABLED = True -def job1(a, b): - print(str(a) + ' ' + str(b)) +def job1(var_one, var_two): + """Demo job function. + + :param var_two: + :param var_two: + """ + print(str(var_one) + " " + str(var_two)) -if __name__ == '__main__': +if __name__ == "__main__": app = Flask(__name__) app.config.from_object(Config()) scheduler = APScheduler() # it is also possible to set the list of servers directly - # scheduler.allowed_hosts = ['my_servers_name'] + # scheduler.allowed_hosts = ['my_servers_name'] # noqa: E800 scheduler.init_app(app) scheduler.start() diff --git a/examples/application_factory/__init__.py b/examples/application_factory/__init__.py new file mode 100644 index 0000000..ec23e54 --- /dev/null +++ b/examples/application_factory/__init__.py @@ -0,0 +1,64 @@ +"""Flask APScheduler example using Flask's Application Factory setup. + +Run using: + +.. code ::python + + pip install flask-apscheduler + export FLASK_ENV=development && export FLASK_DEBUG=1 && export FLASK_APP=__init__ && flask run + +""" + +import logging +import os + +from flask import Flask + +from .extensions import scheduler +from .settings import DevelopmentConfig + + +def create_app(): + """Create a new app instance.""" + + def is_debug_mode(): + """Get app debug status.""" + debug = os.environ.get("FLASK_DEBUG") + if not debug: + return os.environ.get("FLASK_ENV") == "development" + return debug.lower() not in ("0", "false", "no") + + def is_werkzeug_reloader_process(): + """Get werkzeug status.""" + return os.environ.get("WERKZEUG_RUN_MAIN") == "true" + + # pylint: disable=W0621 + app = Flask(__name__) + + app.config.from_object(DevelopmentConfig) + scheduler.init_app(app) + + logging.getLogger("apscheduler").setLevel(logging.INFO) + + # pylint: disable=C0415, W0611 + with app.app_context(): + + # pylint: disable=W0611 + if is_debug_mode() and not is_werkzeug_reloader_process(): + pass + else: + from . import tasks # noqa: F401 + + scheduler.start() + + from . import events, web # noqa: F401 + + app.register_blueprint(web.web_bp) + + return app + + +app = create_app() + +if __name__ == "__main__": + app.run() diff --git a/examples/application_factory/events.py b/examples/application_factory/events.py new file mode 100644 index 0000000..b820177 --- /dev/null +++ b/examples/application_factory/events.py @@ -0,0 +1,57 @@ +"""Log scheduler events.""" + + +from apscheduler.events import ( + EVENT_JOB_ADDED, + EVENT_JOB_ERROR, + EVENT_JOB_EXECUTED, + EVENT_JOB_MISSED, + EVENT_JOB_REMOVED, + EVENT_JOB_SUBMITTED, +) + +from .extensions import scheduler + + +def job_missed(event): + """Job missed event.""" + with scheduler.app.app_context(): + print(event) # noqa: T001 + + +def job_error(event): + """Job error event.""" + with scheduler.app.app_context(): + print(event) # noqa: T001 + + +def job_executed(event): + """Job executed event.""" + with scheduler.app.app_context(): + print(event) # noqa: T001 + + +def job_added(event): + """Job added event.""" + with scheduler.app.app_context(): + print(event) # noqa: T001 + + +def job_removed(event): + """Job removed event.""" + with scheduler.app.app_context(): + print(event) # noqa: T001 + + +def job_submitted(event): + """Job scheduled to run event.""" + with scheduler.app.app_context(): + print(event) # noqa: T001 + + +scheduler.add_listener(job_missed, EVENT_JOB_MISSED) +scheduler.add_listener(job_error, EVENT_JOB_ERROR) +scheduler.add_listener(job_executed, EVENT_JOB_EXECUTED) +scheduler.add_listener(job_added, EVENT_JOB_ADDED) +scheduler.add_listener(job_removed, EVENT_JOB_REMOVED) +scheduler.add_listener(job_submitted, EVENT_JOB_SUBMITTED) diff --git a/examples/application_factory/extensions.py b/examples/application_factory/extensions.py new file mode 100644 index 0000000..1a0ab06 --- /dev/null +++ b/examples/application_factory/extensions.py @@ -0,0 +1,7 @@ +"""Initialize any app extensions.""" + +from flask_apscheduler import APScheduler + +scheduler = APScheduler() + +# ... any other stuff.. db, caching, sessions, etc. diff --git a/examples/application_factory/settings.py b/examples/application_factory/settings.py new file mode 100644 index 0000000..9e11a05 --- /dev/null +++ b/examples/application_factory/settings.py @@ -0,0 +1,14 @@ +"""App configuration.""" + + +class Config: + """Prod config.""" + + DEBUG = False + TESTING = False + + +class DevelopmentConfig(Config): + """Dev config.""" + + DEBUG = True diff --git a/examples/application_factory/tasks.py b/examples/application_factory/tasks.py new file mode 100644 index 0000000..f940876 --- /dev/null +++ b/examples/application_factory/tasks.py @@ -0,0 +1,30 @@ +"""Example of adding tasks on app startup.""" + +from .extensions import scheduler + + +@scheduler.task( + "interval", + id="job_sync", + seconds=10, + max_instances=1, + start_date="2000-01-01 12:19:00", +) +def task1(): + """Sample task 1. + + Added when app starts. + """ + print("running task 1!") # noqa: T001 + + # oh, do you need something from config? + with scheduler.app.app_context(): + print(scheduler.app.config) # noqa: T001 + + +def task2(): + """Sample task 2. + + Added when /add url is visited. + """ + print("running task 2!") # noqa: T001 diff --git a/examples/application_factory/web.py b/examples/application_factory/web.py new file mode 100644 index 0000000..d1e28a8 --- /dev/null +++ b/examples/application_factory/web.py @@ -0,0 +1,37 @@ +"""Example web view for application factory.""" + + +from flask import Blueprint + +from .extensions import scheduler +from .tasks import task2 + +web_bp = Blueprint("web_bp", __name__) + + +@web_bp.route("/") +def index(): + """Say hi!. + + :url: / + :returns: hi! + """ + return "hi!" + + +@web_bp.route("/add") +def add(): + """Add a task. + + :url: /add/ + :returns: job + """ + job = scheduler.add_job( + func=task2, + trigger="interval", + seconds=10, + id="test job 2", + name="test job 2", + replace_existing=True, + ) + return "%s added!" % job.name diff --git a/examples/auth.py b/examples/auth.py index bab8532..fa5ef05 100644 --- a/examples/auth.py +++ b/examples/auth.py @@ -1,16 +1,21 @@ +"""Authorization example.""" + from flask import Flask + from flask_apscheduler import APScheduler from flask_apscheduler.auth import HTTPBasicAuth -class Config(object): +class Config: + """App configuration.""" + JOBS = [ { - 'id': 'job1', - 'func': '__main__:job1', - 'args': (1, 2), - 'trigger': 'interval', - 'seconds': 10 + "id": "job1", + "func": "__main__:job1", + "args": (1, 2), + "trigger": "interval", + "seconds": 10, } ] @@ -18,22 +23,28 @@ class Config(object): SCHEDULER_AUTH = HTTPBasicAuth() -def job1(a, b): - print(str(a) + ' ' + str(b)) +def job1(var_one, var_two): + """Demo job function. + + :param var_two: + :param var_two: + """ + print(str(var_one) + " " + str(var_two)) -if __name__ == '__main__': +if __name__ == "__main__": app = Flask(__name__) app.config.from_object(Config()) scheduler = APScheduler() # it is also possible to set the authentication directly - # scheduler.auth = HTTPBasicAuth() + # scheduler.auth = HTTPBasicAuth() # noqa: E800 scheduler.init_app(app) scheduler.start() @scheduler.authenticate def authenticate(auth): - return auth['username'] == 'guest' and auth['password'] == 'guest' + """Check auth.""" + return auth["username"] == "guest" and auth["password"] == "guest" app.run() diff --git a/examples/decorated.py b/examples/decorated.py index b54ee7c..5fb003b 100644 --- a/examples/decorated.py +++ b/examples/decorated.py @@ -1,8 +1,12 @@ +"""Example of decorators.""" from flask import Flask + from flask_apscheduler import APScheduler -class Config(object): +class Config: + """App configuration.""" + SCHEDULER_API_ENABLED = True @@ -10,28 +14,31 @@ class Config(object): # interval examples -@scheduler.task('interval', id='do_job_1', seconds=30, misfire_grace_time=900) +@scheduler.task("interval", id="do_job_1", seconds=30, misfire_grace_time=900) def job1(): - print('Job 1 executed') + """Sample job 1.""" + print("Job 1 executed") # cron examples -@scheduler.task('cron', id='do_job_2', minute='*') +@scheduler.task("cron", id="do_job_2", minute="*") def job2(): - print('Job 2 executed') + """Sample job 2.""" + print("Job 2 executed") -@scheduler.task('cron', id='do_job_3', week='*', day_of_week='sun') +@scheduler.task("cron", id="do_job_3", week="*", day_of_week="sun") def job3(): - print('Job 3 executed') + """Sample job 3.""" + print("Job 3 executed") -if __name__ == '__main__': +if __name__ == "__main__": app = Flask(__name__) app.config.from_object(Config()) # it is also possible to enable the API directly - # scheduler.api_enabled = True + # scheduler.api_enabled = True # noqa: E800 scheduler.init_app(app) scheduler.start() diff --git a/examples/flask_context.py b/examples/flask_context.py index a404fc4..dda80d0 100644 --- a/examples/flask_context.py +++ b/examples/flask_context.py @@ -1,41 +1,41 @@ +"""Example using flask context.""" + from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from flask import Flask -from flask_apscheduler import APScheduler from flask_sqlalchemy import SQLAlchemy +from flask_apscheduler import APScheduler db = SQLAlchemy() class User(db.Model): - id = db.Column(db.Integer, primary_key=True) + """User model.""" + + id = db.Column(db.Integer, primary_key=True) # noqa: A003, VNE003 username = db.Column(db.String(80), unique=True) email = db.Column(db.String(120), unique=True) def show_users(): + """Print all users.""" with db.app.app_context(): print(User.query.all()) -class Config(object): - JOBS = [ - { - 'id': 'job1', - 'func': show_users, - 'trigger': 'interval', - 'seconds': 2 - } - ] +class Config: + """App configuration.""" + + JOBS = [{"id": "job1", "func": show_users, "trigger": "interval", "seconds": 2}] SCHEDULER_JOBSTORES = { - 'default': SQLAlchemyJobStore(url='sqlite:///flask_context.db') + "default": SQLAlchemyJobStore(url="sqlite:///flask_context.db") } SCHEDULER_API_ENABLED = True -if __name__ == '__main__': +if __name__ == "__main__": app = Flask(__name__) app.config.from_object(Config()) diff --git a/examples/jobs.py b/examples/jobs.py index f718417..fc8a864 100644 --- a/examples/jobs.py +++ b/examples/jobs.py @@ -1,32 +1,43 @@ +"""Basic Flask Example.""" + + from flask import Flask + from flask_apscheduler import APScheduler -class Config(object): +class Config: + """App configuration.""" + JOBS = [ { - 'id': 'job1', - 'func': 'jobs:job1', - 'args': (1, 2), - 'trigger': 'interval', - 'seconds': 10 + "id": "job1", + "func": "jobs:job1", + "args": (1, 2), + "trigger": "interval", + "seconds": 10, } ] SCHEDULER_API_ENABLED = True -def job1(a, b): - print(str(a) + ' ' + str(b)) +def job1(var_one, var_two): + """Demo job function. + + :param var_two: + :param var_two: + """ + print(str(var_one) + " " + str(var_two)) -if __name__ == '__main__': +if __name__ == "__main__": app = Flask(__name__) app.config.from_object(Config()) scheduler = APScheduler() # it is also possible to enable the API directly - # scheduler.api_enabled = True + # scheduler.api_enabled = True # noqa: E800 scheduler.init_app(app) scheduler.start() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3b10d77 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,14 @@ +[flake8] +max-line-length = 99 +exclude= + temp, + .git, + .gitignore, + *.pot, + *.py[co], + __pycache__, + venv, + .env, + .venv + flask-apscheduler + tests \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..325a231 --- /dev/null +++ b/tox.ini @@ -0,0 +1,45 @@ + +[tox] +envlist = + py37, + lint, + docs, +skip_missing_interpreters = True +isolated_build = True + + +[testenv] +description = test +basepython = python3.7 +deps = + -rrequirements.txt + -rtest-requirements.txt +commands = + nosetests -v -l DEBUG --logging-level=DEBUG --with-coverage --cover-package=flask_apscheduler +skip_install = true + +[testenv:lint] +basepython = python3.7 +description = check code style +deps = + -rdev-requirements.txt +commands = + black examples + isort examples --profile="black" + flake8 examples + pylint examples +skip_install = true + + +[testenv:docs] +basepython = python3.7 +description = update documentation +changedir = docs +deps = + -rdocs-requirements.txt +commands = sphinx-build -E -b html . _build +skip_install = true +setenv = + PYTHONDONTWRITEBYTECODE=1 + DEBUG=False +