From e4a926f5ec113be711dad94773c45b34aebd0ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 27 Jul 2022 13:01:55 +0300 Subject: Updated the examples for v4 --- examples/README.rst | 123 +++++++++++++++++++++++++++++ examples/executors/processpool.py | 26 ------ examples/misc/reference.py | 19 ----- examples/rpc/client.py | 20 ----- examples/rpc/server.py | 62 --------------- examples/schedulers/async_.py | 26 ------ examples/schedulers/sync.py | 22 ------ examples/separate_worker/example_tasks.py | 12 +++ examples/separate_worker/sync_scheduler.py | 29 +++++++ examples/separate_worker/sync_worker.py | 26 ++++++ examples/standalone/async_memory.py | 28 +++++++ examples/standalone/async_mysql.py | 38 +++++++++ examples/standalone/async_postgres.py | 38 +++++++++ examples/standalone/sync_memory.py | 23 ++++++ examples/web/asgi_fastapi.py | 63 +++++++++++++++ examples/web/asgi_noframework.py | 73 +++++++++++++++++ examples/web/asgi_starlette.py | 64 +++++++++++++++ examples/web/wsgi_flask.py | 41 ++++++++++ examples/web/wsgi_noframework.py | 41 ++++++++++ 19 files changed, 599 insertions(+), 175 deletions(-) create mode 100644 examples/README.rst delete mode 100644 examples/executors/processpool.py delete mode 100644 examples/misc/reference.py delete mode 100644 examples/rpc/client.py delete mode 100644 examples/rpc/server.py delete mode 100644 examples/schedulers/async_.py delete mode 100644 examples/schedulers/sync.py create mode 100644 examples/separate_worker/example_tasks.py create mode 100644 examples/separate_worker/sync_scheduler.py create mode 100644 examples/separate_worker/sync_worker.py create mode 100644 examples/standalone/async_memory.py create mode 100644 examples/standalone/async_mysql.py create mode 100644 examples/standalone/async_postgres.py create mode 100644 examples/standalone/sync_memory.py create mode 100644 examples/web/asgi_fastapi.py create mode 100644 examples/web/asgi_noframework.py create mode 100644 examples/web/asgi_starlette.py create mode 100644 examples/web/wsgi_flask.py create mode 100644 examples/web/wsgi_noframework.py (limited to 'examples') diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 0000000..0edc0e5 --- /dev/null +++ b/examples/README.rst @@ -0,0 +1,123 @@ +APScheduler practical examples +============================== + +.. highlight:: bash + +This directory contains a number of practical examples for running APScheduler in a +variety of configurations. + +Prerequisites +------------- + +Most examples use one or more external services for data sharing and synchronization. +To start these services, you need Docker_ installed. Each example lists the services +it needs (if any) in the module file, so you can start these services selectively. + +On Linux, if you're using the vendor provided system package for Docker instead of +Docker Desktop, you may need to install the compose (v2) plugin (named +``docker-compose-plugin``, or similar) separately. + +.. note:: If you're still using the Python-based docker-compose tool (aka compose v1), + replace ``docker compose`` with ``docker-compose``. + +To start all the services, run this command anywhere within the project directory:: + + docker compose up -d + +To start just a specific service, you can pass its name as an argument:: + + docker compose up -d postgresql + +To shut down the services and delete all their data:: + + docker compose down -v + +In addition to having these background services running, you may need to install +specific extra dependencies, like database drivers. Each example module has its required +dependencies listed in the module comment at the top. + +.. _Docker: https://docs.docker.com/desktop/#download-and-install + +Standalone examples +------------------- + +The examples in the ``standalone`` directory demonstrate how to run the scheduler in the +foreground, without anything else going on in the same process. + +The directory contains four modules: + +- ``async_memory.py``: Basic asynchronous scheduler using the default memory-based data + store +- ``async_postgres.py``: Basic asynchronous scheduler using the asynchronous SQLAlchemy + data store with a PostgreSQL back-end +- ``async_mysql.py``: Basic asynchronous scheduler using the asynchronous SQLAlchemy + data store with a MySQL back-end +- ``sync_mysql.py``: Basic synchronous scheduler using the default memory-based data + store + +Schedulers in web apps +---------------------- + +The examples in the ``web`` directory demonstrate how to run the scheduler inside a web +application (ASGI_ or WSGI_). + +The directory contains five modules: + +- ``asgi_noframework.py``: Trivial ASGI_ application, with middleware that starts and + stops the scheduler as part of the ASGI lifecycle +- ``asgi_fastapi.py``: Trivial FastAPI_ application, with middleware that starts and + stops the scheduler as part of the ASGI_ lifecycle +- ``asgi_starlette.py``: Trivial Starlette_ application, with middleware that starts and + stops the scheduler as part of the ASGI_ lifecycle +- ``wsgi_noframework.py``: Trivial WSGI_ application where the scheduler is started in a + background thread +- ``wsgi_flask.py``: Trivial Flask_ application where the scheduler is started in a + background thread + +.. note:: There is no Django example available yet. + +To run any of the ASGI_ examples:: + + uvicorn :app + +To run any of the WSGI_ examples:: + + uwsgi -T --http :8000 --wsgi-file + +.. _ASGI: https://asgi.readthedocs.io/en/latest/introduction.html +.. _WSGI: https://wsgi.readthedocs.io/en/latest/what.html +.. _FastAPI: https://fastapi.tiangolo.com/ +.. _Starlette: https://www.starlette.io/ +.. _Flask: https://flask.palletsprojects.com/ + +Separate scheduler and worker +----------------------------- + +The example in the ``separate_worker`` directory demonstrates the ability to run +schedulers and workers separately. The directory contains three modules: + +- ``sync_scheduler.py``: Runs a scheduler (without an internal worker) and adds/updates + a schedule +- ``sync_worker.py``: Runs a worker only +- ``tasks.py``: Contains the task code (don't try to run this directly; it does nothing) + +The reason for the task function being in a separate module is because when you run +either the ``sync_scheduler`` or ``sync_worker`` script, that script is imported as the +``__main__`` module, so if the scheduler schedules ``__main__:tick`` as the task, then +the worker would not be able to find it because its own script would also be named +``__main__``. + +To run the example, you need to have both the worker and scheduler scripts running at +the same time. To run the worker:: + + python sync_worker.py + +To run the scheduler:: + + python sync_scheduler.py + +You can run multiple schedulers and workers at the same time within this example. If you +run multiple workers, the message might be printed on the console of a different worker +each time the job is run. Running multiple schedulers should have no visible effect, and +as long as at least one scheduler is running, the scheduled task should keep running +periodically on one of the workers. diff --git a/examples/executors/processpool.py b/examples/executors/processpool.py deleted file mode 100644 index f8538e1..0000000 --- a/examples/executors/processpool.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Demonstrates how to schedule a job to be run in a process pool on 3 second intervals. -""" - -from __future__ import annotations - -import os -from datetime import datetime - -from apscheduler.schedulers.blocking import BlockingScheduler - - -def tick(): - print("Tick! The time is: %s" % datetime.now()) - - -if __name__ == "__main__": - scheduler = BlockingScheduler() - scheduler.add_executor("processpool") - scheduler.add_job(tick, "interval", seconds=3) - print("Press Ctrl+{} to exit".format("Break" if os.name == "nt" else "C")) - - try: - scheduler.initialize() - except (KeyboardInterrupt, SystemExit): - pass diff --git a/examples/misc/reference.py b/examples/misc/reference.py deleted file mode 100644 index 75bfb1c..0000000 --- a/examples/misc/reference.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Basic example showing how to schedule a callable using a textual reference. -""" - -from __future__ import annotations - -import os - -from apscheduler.schedulers.blocking import BlockingScheduler - -if __name__ == "__main__": - scheduler = BlockingScheduler() - scheduler.add_job("sys:stdout.write", "interval", seconds=3, args=["tick\n"]) - print("Press Ctrl+{} to exit".format("Break" if os.name == "nt" else "C")) - - try: - scheduler.initialize() - except (KeyboardInterrupt, SystemExit): - pass diff --git a/examples/rpc/client.py b/examples/rpc/client.py deleted file mode 100644 index 2a3a52a..0000000 --- a/examples/rpc/client.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -This is an example RPC client that connects to the RPyC based scheduler service. - -It first connects to the RPyC server on localhost:12345. -Then it schedules a job to run on 2 second intervals and sleeps for 10 seconds. -After that, it unschedules the job and exits. -""" - -from __future__ import annotations - -from time import sleep - -import rpyc - -conn = rpyc.connect("localhost", 12345) -job = conn.root.add_job( - "server:print_text", "interval", args=["Hello, World"], seconds=2 -) -sleep(10) -conn.root.remove_job(job.id) diff --git a/examples/rpc/server.py b/examples/rpc/server.py deleted file mode 100644 index fc40c04..0000000 --- a/examples/rpc/server.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -This is an example showing how to make the scheduler into a remotely accessible service. -It uses RPyC to set up a service through which the scheduler can be made to add, modify and remove -jobs. - -To run, first install RPyC using pip. Then change the working directory to the ``rpc`` directory -and run it with ``python -m server``. -""" - -from __future__ import annotations - -import rpyc -from rpyc.utils.server import ThreadedServer - -from apscheduler.schedulers.background import BackgroundScheduler - - -def print_text(text): - print(text) - - -class SchedulerService(rpyc.Service): - def exposed_add_job(self, func, *args, **kwargs): - return scheduler.add_job(func, *args, **kwargs) - - def exposed_modify_job(self, job_id, jobstore=None, **changes): - return scheduler.modify_job(job_id, jobstore, **changes) - - def exposed_reschedule_job( - self, job_id, jobstore=None, trigger=None, **trigger_args - ): - return scheduler.reschedule_job(job_id, jobstore, trigger, **trigger_args) - - def exposed_pause_job(self, job_id, jobstore=None): - return scheduler.pause_job(job_id, jobstore) - - def exposed_resume_job(self, job_id, jobstore=None): - return scheduler.resume_job(job_id, jobstore) - - def exposed_remove_job(self, job_id, jobstore=None): - scheduler.remove_job(job_id, jobstore) - - def exposed_get_job(self, job_id): - return scheduler.get_job(job_id) - - def exposed_get_jobs(self, jobstore=None): - return scheduler.get_jobs(jobstore) - - -if __name__ == "__main__": - scheduler = BackgroundScheduler() - scheduler.initialize() - protocol_config = {"allow_public_attrs": True} - server = ThreadedServer( - SchedulerService, port=12345, protocol_config=protocol_config - ) - try: - server.initialize() - except (KeyboardInterrupt, SystemExit): - pass - finally: - scheduler.stop() diff --git a/examples/schedulers/async_.py b/examples/schedulers/async_.py deleted file mode 100644 index e4eac0c..0000000 --- a/examples/schedulers/async_.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import annotations - -import logging - -import anyio - -from apscheduler.schedulers.async_ import AsyncScheduler -from apscheduler.triggers.interval import IntervalTrigger -from apscheduler.workers.async_ import AsyncWorker - - -def say_hello(): - print("Hello!") - - -async def main(): - async with AsyncScheduler() as scheduler, AsyncWorker(scheduler.data_store): - await scheduler.add_schedule(say_hello, IntervalTrigger(seconds=1)) - await scheduler.wait_until_stopped() - - -logging.basicConfig(level=logging.DEBUG) -try: - anyio.run(main) -except (KeyboardInterrupt, SystemExit): - pass diff --git a/examples/schedulers/sync.py b/examples/schedulers/sync.py deleted file mode 100644 index 12ae17f..0000000 --- a/examples/schedulers/sync.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import logging - -from apscheduler.schedulers.sync import Scheduler -from apscheduler.triggers.interval import IntervalTrigger -from apscheduler.workers.sync import Worker - - -def say_hello(): - print("Hello!") - - -logging.basicConfig(level=logging.DEBUG) -try: - with Scheduler() as scheduler, Worker( - scheduler.data_store, portal=scheduler.portal - ): - scheduler.add_schedule(say_hello, IntervalTrigger(seconds=1)) - scheduler.wait_until_stopped() -except (KeyboardInterrupt, SystemExit): - pass diff --git a/examples/separate_worker/example_tasks.py b/examples/separate_worker/example_tasks.py new file mode 100644 index 0000000..568b77c --- /dev/null +++ b/examples/separate_worker/example_tasks.py @@ -0,0 +1,12 @@ +""" +This module contains just the code for the scheduled task. +It should not be run directly. +""" + +from __future__ import annotations + +from datetime import datetime + + +def tick(): + print("Hello, the time is", datetime.now()) diff --git a/examples/separate_worker/sync_scheduler.py b/examples/separate_worker/sync_scheduler.py new file mode 100644 index 0000000..aeba93c --- /dev/null +++ b/examples/separate_worker/sync_scheduler.py @@ -0,0 +1,29 @@ +""" +Example demonstrating the separation of scheduler and worker. +This script runs the scheduler part. You need to be running both this and the worker +script simultaneously in order for the scheduled task to be run. + +Requires the "postgresql" and "redis" services to be running. +To install prerequisites: pip install sqlalchemy psycopg2 redis +To run: python sync_scheduler.py + +When run together with sync_worker.py, it should print a line on the console +on a one-second interval. +""" + +from __future__ import annotations + +from example_tasks import tick +from sqlalchemy.future import create_engine + +from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore +from apscheduler.eventbrokers.redis import RedisEventBroker +from apscheduler.schedulers.sync import Scheduler +from apscheduler.triggers.interval import IntervalTrigger + +engine = create_engine("postgresql+psycopg2://postgres:secret@localhost/testdb") +data_store = SQLAlchemyDataStore(engine) +event_broker = RedisEventBroker.from_url("redis://localhost") +with Scheduler(data_store, event_broker, start_worker=False) as scheduler: + scheduler.add_schedule(tick, IntervalTrigger(seconds=1), id="tick") + scheduler.wait_until_stopped() diff --git a/examples/separate_worker/sync_worker.py b/examples/separate_worker/sync_worker.py new file mode 100644 index 0000000..30bdb78 --- /dev/null +++ b/examples/separate_worker/sync_worker.py @@ -0,0 +1,26 @@ +""" +Example demonstrating the separation of scheduler and worker. +This script runs the worker part. You need to be running both this and the scheduler +script simultaneously in order for the scheduled task to be run. + +Requires the "postgresql" and "redis" services to be running. +To install prerequisites: pip install sqlalchemy psycopg2 redis +To run: python sync_worker.py + +When run together with sync_scheduler.py, it should print a line on the +console on a one-second interval. +""" + +from __future__ import annotations + +from sqlalchemy.future import create_engine + +from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore +from apscheduler.eventbrokers.redis import RedisEventBroker +from apscheduler.workers.sync import Worker + +engine = create_engine("postgresql+psycopg2://postgres:secret@localhost/testdb") +data_store = SQLAlchemyDataStore(engine) +event_broker = RedisEventBroker.from_url("redis://localhost") +worker = Worker(data_store, event_broker) +worker.run_until_stopped() diff --git a/examples/standalone/async_memory.py b/examples/standalone/async_memory.py new file mode 100644 index 0000000..b3db775 --- /dev/null +++ b/examples/standalone/async_memory.py @@ -0,0 +1,28 @@ +""" +Example demonstrating use of the asynchronous scheduler in a simple asyncio app. + +To run: python async_memory.py + +It should print a line on the console on a one-second interval. +""" + +from __future__ import annotations + +from asyncio import run +from datetime import datetime + +from apscheduler.schedulers.async_ import AsyncScheduler +from apscheduler.triggers.interval import IntervalTrigger + + +def tick(): + print("Hello, the time is", datetime.now()) + + +async def main(): + async with AsyncScheduler() as scheduler: + await scheduler.add_schedule(tick, IntervalTrigger(seconds=1)) + await scheduler.wait_until_stopped() + + +run(main()) diff --git a/examples/standalone/async_mysql.py b/examples/standalone/async_mysql.py new file mode 100644 index 0000000..3f6f95f --- /dev/null +++ b/examples/standalone/async_mysql.py @@ -0,0 +1,38 @@ +""" +Example demonstrating use of the asynchronous scheduler with persistence via MySQL or +MariaDB in a simple asyncio app. + +Requires the "mysql" service to be running. +To install prerequisites: pip install sqlalchemy asyncmy +To run: python async_postgres.py + +It should print a line on the console on a one-second interval. +""" + +from __future__ import annotations + +from asyncio import run +from datetime import datetime + +from sqlalchemy.ext.asyncio import create_async_engine + +from apscheduler.datastores.async_sqlalchemy import AsyncSQLAlchemyDataStore +from apscheduler.schedulers.async_ import AsyncScheduler +from apscheduler.triggers.interval import IntervalTrigger + + +def tick(): + print("Hello, the time is", datetime.now()) + + +async def main(): + engine = create_async_engine( + "mysql+asyncmy://root:secret@localhost/testdb?charset=utf8mb4" + ) + data_store = AsyncSQLAlchemyDataStore(engine) + async with AsyncScheduler(data_store) as scheduler: + await scheduler.add_schedule(tick, IntervalTrigger(seconds=1), id="tick") + await scheduler.wait_until_stopped() + + +run(main()) diff --git a/examples/standalone/async_postgres.py b/examples/standalone/async_postgres.py new file mode 100644 index 0000000..e6083f7 --- /dev/null +++ b/examples/standalone/async_postgres.py @@ -0,0 +1,38 @@ +""" +Example demonstrating use of the asynchronous scheduler with persistence via PostgreSQL +in a simple asyncio app. + +Requires the "postgresql" service to be running. +To install prerequisites: pip install sqlalchemy asyncpg +To run: python async_postgres.py + +It should print a line on the console on a one-second interval. +""" + +from __future__ import annotations + +from asyncio import run +from datetime import datetime + +from sqlalchemy.ext.asyncio import create_async_engine + +from apscheduler.datastores.async_sqlalchemy import AsyncSQLAlchemyDataStore +from apscheduler.schedulers.async_ import AsyncScheduler +from apscheduler.triggers.interval import IntervalTrigger + + +def tick(): + print("Hello, the time is", datetime.now()) + + +async def main(): + engine = create_async_engine( + "postgresql+asyncpg://postgres:secret@localhost/testdb" + ) + data_store = AsyncSQLAlchemyDataStore(engine) + async with AsyncScheduler(data_store) as scheduler: + await scheduler.add_schedule(tick, IntervalTrigger(seconds=1), id="tick") + await scheduler.wait_until_stopped() + + +run(main()) diff --git a/examples/standalone/sync_memory.py b/examples/standalone/sync_memory.py new file mode 100644 index 0000000..6d804f7 --- /dev/null +++ b/examples/standalone/sync_memory.py @@ -0,0 +1,23 @@ +""" +Example demonstrating use of the synchronous scheduler. + +To run: python sync_memory.py + +It should print a line on the console on a one-second interval. +""" + +from __future__ import annotations + +from datetime import datetime + +from apscheduler.schedulers.sync import Scheduler +from apscheduler.triggers.interval import IntervalTrigger + + +def tick(): + print("Hello, the time is", datetime.now()) + + +with Scheduler() as scheduler: + scheduler.add_schedule(tick, IntervalTrigger(seconds=1)) + scheduler.wait_until_stopped() diff --git a/examples/web/asgi_fastapi.py b/examples/web/asgi_fastapi.py new file mode 100644 index 0000000..2cfa5c4 --- /dev/null +++ b/examples/web/asgi_fastapi.py @@ -0,0 +1,63 @@ +""" +Example demonstrating use with the FastAPI web framework. + +Requires the "postgresql" service to be running. +To install prerequisites: pip install sqlalchemy asycnpg fastapi uvicorn +To run: uvicorn asgi_fastapi:app + +It should print a line on the console on a one-second interval while running a +basic web app at http://localhost:8000. +""" + +from __future__ import annotations + +from datetime import datetime + +from fastapi import FastAPI +from fastapi.middleware import Middleware +from fastapi.requests import Request +from fastapi.responses import PlainTextResponse, Response +from sqlalchemy.ext.asyncio import create_async_engine +from starlette.types import ASGIApp, Receive, Scope, Send + +from apscheduler.datastores.async_sqlalchemy import AsyncSQLAlchemyDataStore +from apscheduler.eventbrokers.asyncpg import AsyncpgEventBroker +from apscheduler.schedulers.async_ import AsyncScheduler +from apscheduler.triggers.interval import IntervalTrigger + + +def tick(): + print("Hello, the time is", datetime.now()) + + +class SchedulerMiddleware: + def __init__( + self, + app: ASGIApp, + scheduler: AsyncScheduler, + ) -> None: + self.app = app + self.scheduler = scheduler + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] == "lifespan": + async with self.scheduler: + await self.scheduler.add_schedule( + tick, IntervalTrigger(seconds=1), id="tick" + ) + await self.app(scope, receive, send) + else: + await self.app(scope, receive, send) + + +async def root(request: Request) -> Response: + return PlainTextResponse("Hello, world!") + + +engine = create_async_engine("postgresql+asyncpg://postgres:secret@localhost/testdb") +data_store = AsyncSQLAlchemyDataStore(engine) +event_broker = AsyncpgEventBroker.from_async_sqla_engine(engine) +scheduler = AsyncScheduler() +middleware = [Middleware(SchedulerMiddleware, scheduler=scheduler)] +app = FastAPI(middleware=middleware) +app.add_api_route("/", root) diff --git a/examples/web/asgi_noframework.py b/examples/web/asgi_noframework.py new file mode 100644 index 0000000..c3ac184 --- /dev/null +++ b/examples/web/asgi_noframework.py @@ -0,0 +1,73 @@ +""" +Example demonstrating use with ASGI (raw ASGI application, no framework). + +Requires the "postgresql" service to be running. +To install prerequisites: pip install sqlalchemy asyncpg uvicorn +To run: uvicorn asgi_noframework:app + +It should print a line on the console on a one-second interval while running a +basic web app at http://localhost:8000. +""" + +from __future__ import annotations + +from datetime import datetime + +from sqlalchemy.ext.asyncio import create_async_engine + +from apscheduler.datastores.async_sqlalchemy import AsyncSQLAlchemyDataStore +from apscheduler.eventbrokers.asyncpg import AsyncpgEventBroker +from apscheduler.schedulers.async_ import AsyncScheduler +from apscheduler.triggers.interval import IntervalTrigger + + +def tick(): + print("Hello, the time is", datetime.now()) + + +async def original_app(scope, receive, send): + """Trivial example of an ASGI application.""" + if scope["type"] == "http": + await receive() + await send( + { + "type": "http.response.start", + "status": 200, + "headers": [ + [b"content-type", b"text/plain"], + ], + } + ) + await send( + { + "type": "http.response.body", + "body": b"Hello, world!", + "more_body": False, + } + ) + elif scope["type"] == "lifespan": + while True: + message = await receive() + if message["type"] == "lifespan.startup": + await send({"type": "lifespan.startup.complete"}) + elif message["type"] == "lifespan.shutdown": + await send({"type": "lifespan.shutdown.complete"}) + return + + +async def scheduler_middleware(scope, receive, send): + if scope["type"] == "lifespan": + engine = create_async_engine( + "postgresql+asyncpg://postgres:secret@localhost/testdb" + ) + data_store = AsyncSQLAlchemyDataStore(engine) + event_broker = AsyncpgEventBroker.from_async_sqla_engine(engine) + async with AsyncScheduler(data_store, event_broker) as scheduler: + await scheduler.add_schedule(tick, IntervalTrigger(seconds=1), id="tick") + await original_app(scope, receive, send) + else: + await original_app(scope, receive, send) + + +# This is just for consistency with the other ASGI examples +app = scheduler_middleware diff --git a/examples/web/asgi_starlette.py b/examples/web/asgi_starlette.py new file mode 100644 index 0000000..edc070c --- /dev/null +++ b/examples/web/asgi_starlette.py @@ -0,0 +1,64 @@ +""" +Example demonstrating use with the Starlette web framework. + +Requires the "postgresql" service to be running. +To install prerequisites: pip install sqlalchemy asycnpg starlette uvicorn +To run: uvicorn asgi_starlette:app + +It should print a line on the console on a one-second interval while running a +basic web app at http://localhost:8000. +""" + +from __future__ import annotations + +from datetime import datetime + +from sqlalchemy.ext.asyncio import create_async_engine +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.requests import Request +from starlette.responses import PlainTextResponse, Response +from starlette.routing import Route +from starlette.types import ASGIApp, Receive, Scope, Send + +from apscheduler.datastores.async_sqlalchemy import AsyncSQLAlchemyDataStore +from apscheduler.eventbrokers.asyncpg import AsyncpgEventBroker +from apscheduler.schedulers.async_ import AsyncScheduler +from apscheduler.triggers.interval import IntervalTrigger + + +def tick(): + print("Hello, the time is", datetime.now()) + + +class SchedulerMiddleware: + def __init__( + self, + app: ASGIApp, + scheduler: AsyncScheduler, + ) -> None: + self.app = app + self.scheduler = scheduler + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] == "lifespan": + async with self.scheduler: + await self.scheduler.add_schedule( + tick, IntervalTrigger(seconds=1), id="tick" + ) + await self.app(scope, receive, send) + else: + await self.app(scope, receive, send) + + +async def root(request: Request) -> Response: + return PlainTextResponse("Hello, world!") + + +engine = create_async_engine("postgresql+asyncpg://postgres:secret@localhost/testdb") +data_store = AsyncSQLAlchemyDataStore(engine) +event_broker = AsyncpgEventBroker.from_async_sqla_engine(engine) +scheduler = AsyncScheduler() +routes = [Route("/", root)] +middleware = [Middleware(SchedulerMiddleware, scheduler=scheduler)] +app = Starlette(routes=routes, middleware=middleware) diff --git a/examples/web/wsgi_flask.py b/examples/web/wsgi_flask.py new file mode 100644 index 0000000..cc74c7b --- /dev/null +++ b/examples/web/wsgi_flask.py @@ -0,0 +1,41 @@ +""" +Example demonstrating use with WSGI (raw WSGI application, no framework). + +Requires the "postgresql" service to be running. +To install prerequisites: pip install sqlalchemy psycopg2 flask uwsgi +To run: uwsgi -T --http :8000 --wsgi-file wsgi_flask.py + +It should print a line on the console on a one-second interval while running a +basic web app at http://localhost:8000. +""" + +from __future__ import annotations + +from datetime import datetime + +from flask import Flask +from sqlalchemy.future import create_engine + +from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore +from apscheduler.eventbrokers.redis import RedisEventBroker +from apscheduler.schedulers.sync import Scheduler +from apscheduler.triggers.interval import IntervalTrigger + +app = Flask(__name__) + + +def tick(): + print("Hello, the time is", datetime.now()) + + +@app.route("/") +def hello_world(): + return "

Hello, World!

" + + +engine = create_engine("postgresql+psycopg2://postgres:secret@localhost/testdb") +data_store = SQLAlchemyDataStore(engine) +event_broker = RedisEventBroker.from_url("redis://localhost") +scheduler = Scheduler(data_store, event_broker) +scheduler.add_schedule(tick, IntervalTrigger(seconds=1), id="tick") +scheduler.start_in_background() diff --git a/examples/web/wsgi_noframework.py b/examples/web/wsgi_noframework.py new file mode 100644 index 0000000..c143f3f --- /dev/null +++ b/examples/web/wsgi_noframework.py @@ -0,0 +1,41 @@ +""" +Example demonstrating use with WSGI (raw WSGI application, no framework). + +Requires the "postgresql" service to be running. +To install prerequisites: pip install sqlalchemy psycopg2 uwsgi +To run: uwsgi -T --http :8000 --wsgi-file wsgi_noframework.py + +It should print a line on the console on a one-second interval while running a +basic web app at http://localhost:8000. +""" + +from __future__ import annotations + +from datetime import datetime + +from sqlalchemy.future import create_engine + +from apscheduler.datastores.sqlalchemy import SQLAlchemyDataStore +from apscheduler.schedulers.sync import Scheduler +from apscheduler.triggers.interval import IntervalTrigger + + +def tick(): + print("Hello, the time is", datetime.now()) + + +def application(environ, start_response): + response_body = b"Hello, World!" + response_headers = [ + ("Content-Type", "text/plain"), + ("Content-Length", str(len(response_body))), + ] + start_response("200 OK", response_headers) + return [response_body] + + +engine = create_engine("postgresql+psycopg2://postgres:secret@localhost/testdb") +data_store = SQLAlchemyDataStore(engine) +scheduler = Scheduler(data_store) +scheduler.add_schedule(tick, IntervalTrigger(seconds=1), id="tick") +scheduler.start_in_background() -- cgit v1.2.1