From a1f0fcca82aad307cf677fe15e02d4ef0b590062 Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Wed, 29 Nov 2023 21:16:09 -0500 Subject: [PATCH 1/6] add fastapi dir and change dep to rocksdict --- examples/django/requirements/default.txt | 2 +- examples/fastapi/README.rst | 12 ++ examples/fastapi/accounts/__init__.py | 0 examples/fastapi/accounts/agents.py | 31 ++++ examples/fastapi/accounts/apps.py | 5 + .../accounts/migrations/0001_initial.py | 35 +++++ .../fastapi/accounts/migrations/__init__.py | 0 examples/fastapi/accounts/models.py | 13 ++ examples/fastapi/faustapp/__init__.py | 0 examples/fastapi/faustapp/app.py | 25 ++++ examples/fastapi/faustapp/apps.py | 5 + .../fastapi/faustapp/migrations/__init__.py | 0 examples/fastapi/manage.py | 21 +++ examples/fastapi/proj/__init__.py | 40 ++++++ examples/fastapi/proj/__main__.py | 5 + examples/fastapi/proj/settings.py | 135 ++++++++++++++++++ examples/fastapi/proj/urls.py | 21 +++ examples/fastapi/proj/wsgi.py | 16 +++ examples/fastapi/requirements/default.txt | 4 + examples/fastapi/requirements/test.txt | 2 + examples/fastapi/setup.py | 111 ++++++++++++++ 21 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 examples/fastapi/README.rst create mode 100644 examples/fastapi/accounts/__init__.py create mode 100644 examples/fastapi/accounts/agents.py create mode 100644 examples/fastapi/accounts/apps.py create mode 100644 examples/fastapi/accounts/migrations/0001_initial.py create mode 100644 examples/fastapi/accounts/migrations/__init__.py create mode 100644 examples/fastapi/accounts/models.py create mode 100644 examples/fastapi/faustapp/__init__.py create mode 100644 examples/fastapi/faustapp/app.py create mode 100644 examples/fastapi/faustapp/apps.py create mode 100644 examples/fastapi/faustapp/migrations/__init__.py create mode 100755 examples/fastapi/manage.py create mode 100644 examples/fastapi/proj/__init__.py create mode 100755 examples/fastapi/proj/__main__.py create mode 100644 examples/fastapi/proj/settings.py create mode 100644 examples/fastapi/proj/urls.py create mode 100644 examples/fastapi/proj/wsgi.py create mode 100644 examples/fastapi/requirements/default.txt create mode 100644 examples/fastapi/requirements/test.txt create mode 100644 examples/fastapi/setup.py diff --git a/examples/django/requirements/default.txt b/examples/django/requirements/default.txt index 042b4125b..e5aed2ea6 100644 --- a/examples/django/requirements/default.txt +++ b/examples/django/requirements/default.txt @@ -1,4 +1,4 @@ django -faust[rocksdb] +faust[rocksdict] eventlet faust-aioeventlet diff --git a/examples/fastapi/README.rst b/examples/fastapi/README.rst new file mode 100644 index 000000000..937c6d302 --- /dev/null +++ b/examples/fastapi/README.rst @@ -0,0 +1,12 @@ +Directory Layout + +- ``proj/`` + + + +- ``faustapp/`` + + +- ``accounts/`` + + This is an example FastAPI App with stream processors. diff --git a/examples/fastapi/accounts/__init__.py b/examples/fastapi/accounts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/fastapi/accounts/agents.py b/examples/fastapi/accounts/agents.py new file mode 100644 index 000000000..8c8279c2d --- /dev/null +++ b/examples/fastapi/accounts/agents.py @@ -0,0 +1,31 @@ +from decimal import Decimal +import faust +from faust.types import StreamT +from faustapp.app import app +from .models import Account + + +class AccountRecord(faust.Record): + name: str + score: float + active: bool + + +@app.agent() +async def add_account(accounts: StreamT[AccountRecord]): + async for account in accounts: + result = Account.objects.create( + name=account.name, + score=Decimal(str(account.score)), + active=account.active, + ) + yield result.pk + + +@app.agent() +async def disable_account(account_ids: StreamT[int]): + async for account_id in account_ids: + account = Account.objects.get(pk=account_id) + account.active = False + account.save() + yield account.active diff --git a/examples/fastapi/accounts/apps.py b/examples/fastapi/accounts/apps.py new file mode 100644 index 000000000..9b3fc5a44 --- /dev/null +++ b/examples/fastapi/accounts/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + name = 'accounts' diff --git a/examples/fastapi/accounts/migrations/0001_initial.py b/examples/fastapi/accounts/migrations/0001_initial.py new file mode 100644 index 000000000..b5797870a --- /dev/null +++ b/examples/fastapi/accounts/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 2.1.4 on 2018-12-24 12:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Account', + fields=[ + ('id', models.AutoField(auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID')), + ('name', models.CharField(max_length=100, + verbose_name='name')), + ('score', models.DecimalField(decimal_places=1000, + default=0.0, + max_digits=1000, + verbose_name='score')), + ('active', models.BooleanField(default=True, + verbose_name='active')), + ], + options={ + 'verbose_name': 'account', + 'verbose_name_plural': 'accounts', + }, + ), + ] diff --git a/examples/fastapi/accounts/migrations/__init__.py b/examples/fastapi/accounts/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/fastapi/accounts/models.py b/examples/fastapi/accounts/models.py new file mode 100644 index 000000000..fa2de63b9 --- /dev/null +++ b/examples/fastapi/accounts/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class Account(models.Model): + name = models.CharField(_('name'), max_length=100) + score = models.DecimalField(_('score'), default=0.0, + max_digits=1000, decimal_places=1000) + active = models.BooleanField(_('active'), default=True) + + class Meta: + verbose_name = _('account') + verbose_name_plural = _('accounts') diff --git a/examples/fastapi/faustapp/__init__.py b/examples/fastapi/faustapp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/fastapi/faustapp/app.py b/examples/fastapi/faustapp/app.py new file mode 100644 index 000000000..bebc5e67c --- /dev/null +++ b/examples/fastapi/faustapp/app.py @@ -0,0 +1,25 @@ +import os +import faust + +# make sure the event loop is used as early as possible. +os.environ.setdefault('FAUST_LOOP', 'eventlet') + +# set the default Django settings module for the 'faust' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') + +app = faust.App('django-proj', autodiscover=True, origin='faustapp') + + +@app.on_configured.connect +def configure_from_settings(app, conf, **kwargs): + from django.conf import settings + conf.broker = settings.FAUST_BROKER_URL + conf.store = settings.FAUST_STORE_URL + + +def main(): + app.main() + + +if __name__ == '__main__': + main() diff --git a/examples/fastapi/faustapp/apps.py b/examples/fastapi/faustapp/apps.py new file mode 100644 index 000000000..9df21b5d0 --- /dev/null +++ b/examples/fastapi/faustapp/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class FaustappConfig(AppConfig): + name = 'faustapp' diff --git a/examples/fastapi/faustapp/migrations/__init__.py b/examples/fastapi/faustapp/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/fastapi/manage.py b/examples/fastapi/manage.py new file mode 100755 index 000000000..decf36b36 --- /dev/null +++ b/examples/fastapi/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == '__main__': + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django # noqa + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + 'available on your PYTHONPATH environment variable? Did you ' + 'forget to activate a virtual environment?') + raise + execute_from_command_line(sys.argv) diff --git a/examples/fastapi/proj/__init__.py b/examples/fastapi/proj/__init__.py new file mode 100644 index 000000000..6f05102a4 --- /dev/null +++ b/examples/fastapi/proj/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +"""Example Django project using Faust.""" +# :copyright: (c) 2017-2020, Robinhood Markets, Inc. +# All rights reserved. +# :license: BSD (3 Clause), see LICENSE for more details. + +# -- Faust is a Python stream processing library +# mainly used with Kafka, but is generic enough to be used for general +# agent and channel based programming. + +import re +from typing import NamedTuple + +__version__ = '0.9.3' +__author__ = 'Robinhood Markets, Inc.' +__contact__ = 'opensource@robinhood.com' +__homepage__ = 'https://faust-streaming.github.io/faust/' +__docformat__ = 'restructuredtext' + +# -eof meta- + + +class version_info_t(NamedTuple): + major: int + minor: int + micro: int + releaselevel: str + serial: str + + +# bumpversion can only search for {current_version} +# so we have to parse the version here. +_temp = re.match( + r'(\d+)\.(\d+).(\d+)(.+)?', __version__).groups() +VERSION = version_info = version_info_t( + int(_temp[0]), int(_temp[1]), int(_temp[2]), _temp[3] or '', '') +del(_temp) +del(re) + +__all__ = [] diff --git a/examples/fastapi/proj/__main__.py b/examples/fastapi/proj/__main__.py new file mode 100755 index 000000000..6e72352ec --- /dev/null +++ b/examples/fastapi/proj/__main__.py @@ -0,0 +1,5 @@ +import os +import sys +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') +from django.core.management import execute_from_command_line # noqa: E402 +execute_from_command_line(sys.argv) diff --git a/examples/fastapi/proj/settings.py b/examples/fastapi/proj/settings.py new file mode 100644 index 000000000..812816d24 --- /dev/null +++ b/examples/fastapi/proj/settings.py @@ -0,0 +1,135 @@ +""" +Django settings for proj project. + +Generated by 'django-admin startproject' using Django 1.11.6. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Faust settings (used in faustapp/app.py) +FAUST_BROKER_URL = 'kafka://localhost:9092' +FAUST_STORE_URL = 'rocksdb://' + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'nb007*n+1$k!q5h6qclg$_xgyy%4s2vc=$vg-#%m0nuz-oal&5' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'faustapp', + 'accounts', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'proj.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'proj.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + }, +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': ( # ridiculuously long names are silly + 'django.contrib.auth.password_validation.' + 'UserAttributeSimilarityValidator'), + }, + { + 'NAME': ( + 'django.contrib.auth.password_validation.' + 'MinimumLengthValidator'), + }, + { + 'NAME': ( + 'django.contrib.auth.password_validation.' + 'CommonPasswordValidator'), + }, + { + 'NAME': ( + 'django.contrib.auth.password_validation.' + 'NumericPasswordValidator'), + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/examples/fastapi/proj/urls.py b/examples/fastapi/proj/urls.py new file mode 100644 index 000000000..248ad04f1 --- /dev/null +++ b/examples/fastapi/proj/urls.py @@ -0,0 +1,21 @@ +"""proj URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', admin.site.urls), +] diff --git a/examples/fastapi/proj/wsgi.py b/examples/fastapi/proj/wsgi.py new file mode 100644 index 000000000..9e1a6c4e3 --- /dev/null +++ b/examples/fastapi/proj/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for proj project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') + +application = get_wsgi_application() diff --git a/examples/fastapi/requirements/default.txt b/examples/fastapi/requirements/default.txt new file mode 100644 index 000000000..245485e1a --- /dev/null +++ b/examples/fastapi/requirements/default.txt @@ -0,0 +1,4 @@ +faust[rocksdict] +fastapi +eventlet +faust-aioeventlet diff --git a/examples/fastapi/requirements/test.txt b/examples/fastapi/requirements/test.txt new file mode 100644 index 000000000..7c0db1984 --- /dev/null +++ b/examples/fastapi/requirements/test.txt @@ -0,0 +1,2 @@ +pytest~=3.0 +pytest-asyncio>=0.8 diff --git a/examples/fastapi/setup.py b/examples/fastapi/setup.py new file mode 100644 index 000000000..eda2bfd42 --- /dev/null +++ b/examples/fastapi/setup.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +import re +from pathlib import Path +from setuptools import find_packages, setup + +NAME = 'proj' + +README = Path('README.rst') + +# -*- Classifiers -*- + +classes = ''' + Development Status :: 4 - Beta + License :: OSI Approved :: BSD License + Programming Language :: Python + Programming Language :: Python :: 3 :: Only + Operating System :: POSIX + Operating System :: POSIX :: Linux + Operating System :: MacOS :: MacOS X + Operating System :: POSIX :: BSD + Operating System :: Microsoft :: Windows + Topic :: Framework :: Django +''' +classifiers = [s.strip() for s in classes.split('\n') if s] + +# -*- Distribution Meta -*- + +re_meta = re.compile(r'__(\w+?)__\s*=\s*(.*)') +re_doc = re.compile(r'^"""(.+?)"""') + + +def add_default(m): + attr_name, attr_value = m.groups() + return ((attr_name, attr_value.strip("\"'")),) + + +def add_doc(m): + return (('doc', m.groups()[0]),) + + +pats = {re_meta: add_default, re_doc: add_doc} +here = Path(__file__).parent.absolute() +with open(here / NAME / '__init__.py') as meta_fh: + meta = {} + for line in meta_fh: + if line.strip() == '# -eof meta-': + break + for pattern, handler in pats.items(): + m = pattern.match(line.strip()) + if m: + meta.update(handler(m)) + +# -*- Installation Requires -*- + + +def strip_comments(line): + return line.split('#', 1)[0].strip() + + +def _pip_requirement(req): + if req.startswith('-r '): + _, path = req.split() + return reqs(*path.split('/')) + return [req] + + +def _reqs(*f): + path = (Path.cwd() / 'requirements').joinpath(*f) + with path.open() as fh: + reqs = [strip_comments(line) for line in fh.readlines()] + return [_pip_requirement(r) for r in reqs if r] + + +def reqs(*f): + return [req for subreq in _reqs(*f) for req in subreq] + +# -*- Long Description -*- + + +if README.exists(): + long_description = README.read_text(encoding='utf-8') +else: + long_description = 'See http://pypi.org/project/{}'.format(NAME) + +# -*- %%% -*- + +setup( + name=NAME, + version=meta['version'], + description=meta['doc'], + author=meta['author'], + author_email=meta['contact'], + url=meta['homepage'], + platforms=['any'], + license='BSD', + packages=find_packages(exclude=['ez_setup', 'tests', 'tests.*']), + include_package_data=True, + python_requires='>=3.8.0', + keywords=[], + zip_safe=False, + install_requires=reqs('default.txt'), + tests_require=reqs('test.txt'), + classifiers=classifiers, + long_description=long_description, + entry_points={ + 'console_scripts': [ + 'proj = proj.__main__:main', + 'proj-faust = faustapp.app:main', + ], + }, +) From b59dfaf5c4f50cf1bb9e1cb78a8132a088044c2f Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Wed, 29 Nov 2023 21:42:02 -0500 Subject: [PATCH 2/6] commit inital example --- examples/fastapi.py | 62 ++++++++ examples/fastapi/README.rst | 12 -- examples/fastapi/accounts/__init__.py | 0 examples/fastapi/accounts/agents.py | 31 ---- examples/fastapi/accounts/apps.py | 5 - .../accounts/migrations/0001_initial.py | 35 ----- .../fastapi/accounts/migrations/__init__.py | 0 examples/fastapi/accounts/models.py | 13 -- examples/fastapi/faustapp/__init__.py | 0 examples/fastapi/faustapp/app.py | 25 ---- examples/fastapi/faustapp/apps.py | 5 - .../fastapi/faustapp/migrations/__init__.py | 0 examples/fastapi/manage.py | 21 --- examples/fastapi/proj/__init__.py | 40 ------ examples/fastapi/proj/__main__.py | 5 - examples/fastapi/proj/settings.py | 135 ------------------ examples/fastapi/proj/urls.py | 21 --- examples/fastapi/proj/wsgi.py | 16 --- examples/fastapi/requirements/default.txt | 4 - examples/fastapi/requirements/test.txt | 2 - examples/fastapi/setup.py | 111 -------------- 21 files changed, 62 insertions(+), 481 deletions(-) create mode 100755 examples/fastapi.py delete mode 100644 examples/fastapi/README.rst delete mode 100644 examples/fastapi/accounts/__init__.py delete mode 100644 examples/fastapi/accounts/agents.py delete mode 100644 examples/fastapi/accounts/apps.py delete mode 100644 examples/fastapi/accounts/migrations/0001_initial.py delete mode 100644 examples/fastapi/accounts/migrations/__init__.py delete mode 100644 examples/fastapi/accounts/models.py delete mode 100644 examples/fastapi/faustapp/__init__.py delete mode 100644 examples/fastapi/faustapp/app.py delete mode 100644 examples/fastapi/faustapp/apps.py delete mode 100644 examples/fastapi/faustapp/migrations/__init__.py delete mode 100755 examples/fastapi/manage.py delete mode 100644 examples/fastapi/proj/__init__.py delete mode 100755 examples/fastapi/proj/__main__.py delete mode 100644 examples/fastapi/proj/settings.py delete mode 100644 examples/fastapi/proj/urls.py delete mode 100644 examples/fastapi/proj/wsgi.py delete mode 100644 examples/fastapi/requirements/default.txt delete mode 100644 examples/fastapi/requirements/test.txt delete mode 100644 examples/fastapi/setup.py diff --git a/examples/fastapi.py b/examples/fastapi.py new file mode 100755 index 000000000..3a1c5150f --- /dev/null +++ b/examples/fastapi.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +# This is just hello_world.py integrated with a FastAPI application + +import mode.loop.uvloop # noqa + +import faust + +from contextlib import asynccontextmanager + +from fastapi import FastAPI + + +def fake_answer_to_everything_ml_model(x: float): + return x * 42 + + +ml_models = {} + + +# You MUST have "app" defined +app = faust_app = faust.App( + 'hello-world-fastapi', + broker='kafka://localhost:9092', +) + +greetings_topic = faust_app.topic('greetings', value_type=str) + + +@faust_app.agent(greetings_topic) +async def print_greetings(greetings): + async for greeting in greetings: + print(greeting) + + +@faust_app.timer(5) +async def produce(): + for i in range(100): + await greetings_topic.send(value=f'hello {i}') + + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Load the ML model + ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model + await faust_app.start() + yield + # Clean up the ML models and release the resources + ml_models.clear() + await faust_app.stop() + + +fastapi_app = FastAPI(lifespan=lifespan) + + +@fastapi_app.get("/predict") +async def predict(x: float): + result = ml_models["answer_to_everything"](x) + return {"result": result} + +if __name__ == '__main__': + faust_app.main() diff --git a/examples/fastapi/README.rst b/examples/fastapi/README.rst deleted file mode 100644 index 937c6d302..000000000 --- a/examples/fastapi/README.rst +++ /dev/null @@ -1,12 +0,0 @@ -Directory Layout - -- ``proj/`` - - - -- ``faustapp/`` - - -- ``accounts/`` - - This is an example FastAPI App with stream processors. diff --git a/examples/fastapi/accounts/__init__.py b/examples/fastapi/accounts/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/fastapi/accounts/agents.py b/examples/fastapi/accounts/agents.py deleted file mode 100644 index 8c8279c2d..000000000 --- a/examples/fastapi/accounts/agents.py +++ /dev/null @@ -1,31 +0,0 @@ -from decimal import Decimal -import faust -from faust.types import StreamT -from faustapp.app import app -from .models import Account - - -class AccountRecord(faust.Record): - name: str - score: float - active: bool - - -@app.agent() -async def add_account(accounts: StreamT[AccountRecord]): - async for account in accounts: - result = Account.objects.create( - name=account.name, - score=Decimal(str(account.score)), - active=account.active, - ) - yield result.pk - - -@app.agent() -async def disable_account(account_ids: StreamT[int]): - async for account_id in account_ids: - account = Account.objects.get(pk=account_id) - account.active = False - account.save() - yield account.active diff --git a/examples/fastapi/accounts/apps.py b/examples/fastapi/accounts/apps.py deleted file mode 100644 index 9b3fc5a44..000000000 --- a/examples/fastapi/accounts/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class AccountsConfig(AppConfig): - name = 'accounts' diff --git a/examples/fastapi/accounts/migrations/0001_initial.py b/examples/fastapi/accounts/migrations/0001_initial.py deleted file mode 100644 index b5797870a..000000000 --- a/examples/fastapi/accounts/migrations/0001_initial.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 2.1.4 on 2018-12-24 12:13 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Account', - fields=[ - ('id', models.AutoField(auto_created=True, - primary_key=True, - serialize=False, - verbose_name='ID')), - ('name', models.CharField(max_length=100, - verbose_name='name')), - ('score', models.DecimalField(decimal_places=1000, - default=0.0, - max_digits=1000, - verbose_name='score')), - ('active', models.BooleanField(default=True, - verbose_name='active')), - ], - options={ - 'verbose_name': 'account', - 'verbose_name_plural': 'accounts', - }, - ), - ] diff --git a/examples/fastapi/accounts/migrations/__init__.py b/examples/fastapi/accounts/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/fastapi/accounts/models.py b/examples/fastapi/accounts/models.py deleted file mode 100644 index fa2de63b9..000000000 --- a/examples/fastapi/accounts/models.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.db import models -from django.utils.translation import ugettext_lazy as _ - - -class Account(models.Model): - name = models.CharField(_('name'), max_length=100) - score = models.DecimalField(_('score'), default=0.0, - max_digits=1000, decimal_places=1000) - active = models.BooleanField(_('active'), default=True) - - class Meta: - verbose_name = _('account') - verbose_name_plural = _('accounts') diff --git a/examples/fastapi/faustapp/__init__.py b/examples/fastapi/faustapp/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/fastapi/faustapp/app.py b/examples/fastapi/faustapp/app.py deleted file mode 100644 index bebc5e67c..000000000 --- a/examples/fastapi/faustapp/app.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -import faust - -# make sure the event loop is used as early as possible. -os.environ.setdefault('FAUST_LOOP', 'eventlet') - -# set the default Django settings module for the 'faust' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') - -app = faust.App('django-proj', autodiscover=True, origin='faustapp') - - -@app.on_configured.connect -def configure_from_settings(app, conf, **kwargs): - from django.conf import settings - conf.broker = settings.FAUST_BROKER_URL - conf.store = settings.FAUST_STORE_URL - - -def main(): - app.main() - - -if __name__ == '__main__': - main() diff --git a/examples/fastapi/faustapp/apps.py b/examples/fastapi/faustapp/apps.py deleted file mode 100644 index 9df21b5d0..000000000 --- a/examples/fastapi/faustapp/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class FaustappConfig(AppConfig): - name = 'faustapp' diff --git a/examples/fastapi/faustapp/migrations/__init__.py b/examples/fastapi/faustapp/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/fastapi/manage.py b/examples/fastapi/manage.py deleted file mode 100755 index decf36b36..000000000 --- a/examples/fastapi/manage.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == '__main__': - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') - try: - from django.core.management import execute_from_command_line - except ImportError: - # The above import may fail for some other reason. Ensure that the - # issue is really that Django is missing to avoid masking other - # exceptions on Python 2. - try: - import django # noqa - except ImportError: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - 'available on your PYTHONPATH environment variable? Did you ' - 'forget to activate a virtual environment?') - raise - execute_from_command_line(sys.argv) diff --git a/examples/fastapi/proj/__init__.py b/examples/fastapi/proj/__init__.py deleted file mode 100644 index 6f05102a4..000000000 --- a/examples/fastapi/proj/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -"""Example Django project using Faust.""" -# :copyright: (c) 2017-2020, Robinhood Markets, Inc. -# All rights reserved. -# :license: BSD (3 Clause), see LICENSE for more details. - -# -- Faust is a Python stream processing library -# mainly used with Kafka, but is generic enough to be used for general -# agent and channel based programming. - -import re -from typing import NamedTuple - -__version__ = '0.9.3' -__author__ = 'Robinhood Markets, Inc.' -__contact__ = 'opensource@robinhood.com' -__homepage__ = 'https://faust-streaming.github.io/faust/' -__docformat__ = 'restructuredtext' - -# -eof meta- - - -class version_info_t(NamedTuple): - major: int - minor: int - micro: int - releaselevel: str - serial: str - - -# bumpversion can only search for {current_version} -# so we have to parse the version here. -_temp = re.match( - r'(\d+)\.(\d+).(\d+)(.+)?', __version__).groups() -VERSION = version_info = version_info_t( - int(_temp[0]), int(_temp[1]), int(_temp[2]), _temp[3] or '', '') -del(_temp) -del(re) - -__all__ = [] diff --git a/examples/fastapi/proj/__main__.py b/examples/fastapi/proj/__main__.py deleted file mode 100755 index 6e72352ec..000000000 --- a/examples/fastapi/proj/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -import os -import sys -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') -from django.core.management import execute_from_command_line # noqa: E402 -execute_from_command_line(sys.argv) diff --git a/examples/fastapi/proj/settings.py b/examples/fastapi/proj/settings.py deleted file mode 100644 index 812816d24..000000000 --- a/examples/fastapi/proj/settings.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Django settings for proj project. - -Generated by 'django-admin startproject' using Django 1.11.6. - -For more information on this file, see -https://docs.djangoproject.com/en/1.11/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.11/ref/settings/ -""" - -import os - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Faust settings (used in faustapp/app.py) -FAUST_BROKER_URL = 'kafka://localhost:9092' -FAUST_STORE_URL = 'rocksdb://' - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'nb007*n+1$k!q5h6qclg$_xgyy%4s2vc=$vg-#%m0nuz-oal&5' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'faustapp', - 'accounts', -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'proj.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'proj.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/1.11/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - }, -} - - -# Password validation -# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': ( # ridiculuously long names are silly - 'django.contrib.auth.password_validation.' - 'UserAttributeSimilarityValidator'), - }, - { - 'NAME': ( - 'django.contrib.auth.password_validation.' - 'MinimumLengthValidator'), - }, - { - 'NAME': ( - 'django.contrib.auth.password_validation.' - 'CommonPasswordValidator'), - }, - { - 'NAME': ( - 'django.contrib.auth.password_validation.' - 'NumericPasswordValidator'), - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/1.11/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.11/howto/static-files/ - -STATIC_URL = '/static/' diff --git a/examples/fastapi/proj/urls.py b/examples/fastapi/proj/urls.py deleted file mode 100644 index 248ad04f1..000000000 --- a/examples/fastapi/proj/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -"""proj URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.11/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf.urls import url -from django.contrib import admin - -urlpatterns = [ - url(r'^admin/', admin.site.urls), -] diff --git a/examples/fastapi/proj/wsgi.py b/examples/fastapi/proj/wsgi.py deleted file mode 100644 index 9e1a6c4e3..000000000 --- a/examples/fastapi/proj/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for proj project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') - -application = get_wsgi_application() diff --git a/examples/fastapi/requirements/default.txt b/examples/fastapi/requirements/default.txt deleted file mode 100644 index 245485e1a..000000000 --- a/examples/fastapi/requirements/default.txt +++ /dev/null @@ -1,4 +0,0 @@ -faust[rocksdict] -fastapi -eventlet -faust-aioeventlet diff --git a/examples/fastapi/requirements/test.txt b/examples/fastapi/requirements/test.txt deleted file mode 100644 index 7c0db1984..000000000 --- a/examples/fastapi/requirements/test.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest~=3.0 -pytest-asyncio>=0.8 diff --git a/examples/fastapi/setup.py b/examples/fastapi/setup.py deleted file mode 100644 index eda2bfd42..000000000 --- a/examples/fastapi/setup.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python -import re -from pathlib import Path -from setuptools import find_packages, setup - -NAME = 'proj' - -README = Path('README.rst') - -# -*- Classifiers -*- - -classes = ''' - Development Status :: 4 - Beta - License :: OSI Approved :: BSD License - Programming Language :: Python - Programming Language :: Python :: 3 :: Only - Operating System :: POSIX - Operating System :: POSIX :: Linux - Operating System :: MacOS :: MacOS X - Operating System :: POSIX :: BSD - Operating System :: Microsoft :: Windows - Topic :: Framework :: Django -''' -classifiers = [s.strip() for s in classes.split('\n') if s] - -# -*- Distribution Meta -*- - -re_meta = re.compile(r'__(\w+?)__\s*=\s*(.*)') -re_doc = re.compile(r'^"""(.+?)"""') - - -def add_default(m): - attr_name, attr_value = m.groups() - return ((attr_name, attr_value.strip("\"'")),) - - -def add_doc(m): - return (('doc', m.groups()[0]),) - - -pats = {re_meta: add_default, re_doc: add_doc} -here = Path(__file__).parent.absolute() -with open(here / NAME / '__init__.py') as meta_fh: - meta = {} - for line in meta_fh: - if line.strip() == '# -eof meta-': - break - for pattern, handler in pats.items(): - m = pattern.match(line.strip()) - if m: - meta.update(handler(m)) - -# -*- Installation Requires -*- - - -def strip_comments(line): - return line.split('#', 1)[0].strip() - - -def _pip_requirement(req): - if req.startswith('-r '): - _, path = req.split() - return reqs(*path.split('/')) - return [req] - - -def _reqs(*f): - path = (Path.cwd() / 'requirements').joinpath(*f) - with path.open() as fh: - reqs = [strip_comments(line) for line in fh.readlines()] - return [_pip_requirement(r) for r in reqs if r] - - -def reqs(*f): - return [req for subreq in _reqs(*f) for req in subreq] - -# -*- Long Description -*- - - -if README.exists(): - long_description = README.read_text(encoding='utf-8') -else: - long_description = 'See http://pypi.org/project/{}'.format(NAME) - -# -*- %%% -*- - -setup( - name=NAME, - version=meta['version'], - description=meta['doc'], - author=meta['author'], - author_email=meta['contact'], - url=meta['homepage'], - platforms=['any'], - license='BSD', - packages=find_packages(exclude=['ez_setup', 'tests', 'tests.*']), - include_package_data=True, - python_requires='>=3.8.0', - keywords=[], - zip_safe=False, - install_requires=reqs('default.txt'), - tests_require=reqs('test.txt'), - classifiers=classifiers, - long_description=long_description, - entry_points={ - 'console_scripts': [ - 'proj = proj.__main__:main', - 'proj-faust = faustapp.app:main', - ], - }, -) From d532db884d977cae3d5422054d89199e0f48e4c8 Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Wed, 29 Nov 2023 22:08:51 -0500 Subject: [PATCH 3/6] update example --- examples/{fastapi.py => fastapi_example.py} | 46 ++++++++++++++------- 1 file changed, 32 insertions(+), 14 deletions(-) rename examples/{fastapi.py => fastapi_example.py} (55%) diff --git a/examples/fastapi.py b/examples/fastapi_example.py similarity index 55% rename from examples/fastapi.py rename to examples/fastapi_example.py index 3a1c5150f..72f9cc661 100755 --- a/examples/fastapi.py +++ b/examples/fastapi_example.py @@ -1,14 +1,14 @@ #!/usr/bin/env python +import asyncio +from contextlib import asynccontextmanager +from typing import Union -# This is just hello_world.py integrated with a FastAPI application - -import mode.loop.uvloop # noqa +from fastapi import FastAPI import faust -from contextlib import asynccontextmanager -from fastapi import FastAPI +# This is just hello_world.py integrated with a FastAPI application def fake_answer_to_everything_ml_model(x: float): @@ -18,11 +18,14 @@ def fake_answer_to_everything_ml_model(x: float): ml_models = {} -# You MUST have "app" defined -app = faust_app = faust.App( +# You MUST have "app" defined in order for Faust to discover the app +# if you're using "faust" on CLI, but this doesn't work yet +faust_app = faust.App( 'hello-world-fastapi', broker='kafka://localhost:9092', + web_enabled=False, ) +# app = faust_app greetings_topic = faust_app.topic('greetings', value_type=str) @@ -50,13 +53,28 @@ async def lifespan(app: FastAPI): await faust_app.stop() -fastapi_app = FastAPI(lifespan=lifespan) +app = fastapi_app = FastAPI( + # lifespan=lifespan, # TODO +) +# For now, run via "uvicorn fastapi_example:app" +# then checkout http://127.0.0.1:8000/docs + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} -@fastapi_app.get("/predict") -async def predict(x: float): - result = ml_models["answer_to_everything"](x) - return {"result": result} -if __name__ == '__main__': - faust_app.main() +@app.on_event("startup") +async def startup_event() -> None: + asyncio.create_task(faust_app.start()) + + +@app.on_event("shutdown") +async def shutdown_event() -> None: + await faust_app.stop() From 6b794481487124d6ec77e15600f858d8b7c806e8 Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Wed, 29 Nov 2023 22:09:34 -0500 Subject: [PATCH 4/6] use lifespan --- examples/fastapi_example.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/examples/fastapi_example.py b/examples/fastapi_example.py index 72f9cc661..cc897d08a 100755 --- a/examples/fastapi_example.py +++ b/examples/fastapi_example.py @@ -54,7 +54,7 @@ async def lifespan(app: FastAPI): app = fastapi_app = FastAPI( - # lifespan=lifespan, # TODO + lifespan=lifespan, ) # For now, run via "uvicorn fastapi_example:app" # then checkout http://127.0.0.1:8000/docs @@ -69,12 +69,3 @@ def read_root(): def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} - -@app.on_event("startup") -async def startup_event() -> None: - asyncio.create_task(faust_app.start()) - - -@app.on_event("shutdown") -async def shutdown_event() -> None: - await faust_app.stop() From 98f04efb32841d3739bfc154a88c4705f135f95c Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Wed, 29 Nov 2023 22:13:47 -0500 Subject: [PATCH 5/6] reorganize to have an endpoint trigger a producer --- examples/fastapi_example.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/examples/fastapi_example.py b/examples/fastapi_example.py index cc897d08a..43b471730 100755 --- a/examples/fastapi_example.py +++ b/examples/fastapi_example.py @@ -30,18 +30,6 @@ def fake_answer_to_everything_ml_model(x: float): greetings_topic = faust_app.topic('greetings', value_type=str) -@faust_app.agent(greetings_topic) -async def print_greetings(greetings): - async for greeting in greetings: - print(greeting) - - -@faust_app.timer(5) -async def produce(): - for i in range(100): - await greetings_topic.send(value=f'hello {i}') - - @asynccontextmanager async def lifespan(app: FastAPI): # Load the ML model @@ -57,15 +45,28 @@ async def lifespan(app: FastAPI): lifespan=lifespan, ) # For now, run via "uvicorn fastapi_example:app" -# then checkout http://127.0.0.1:8000/docs +# then visit http://127.0.0.1:8000/docs -@app.get("/") +@fastapi_app.get("/") def read_root(): return {"Hello": "World"} -@app.get("/items/{item_id}") +@fastapi_app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} + +@faust_app.agent(greetings_topic) +async def print_greetings(greetings): + async for greeting in greetings: + print(greeting) + + +@fastapi_app.get("/produce") +@faust_app.timer(5) +async def produce(): + for i in range(100): + await greetings_topic.send(value=f'hello {i}') + return {"success": True} From af7c7cd75734f10e45ba269bb4923c7f36b6f14f Mon Sep 17 00:00:00 2001 From: William Barnhart Date: Wed, 29 Nov 2023 22:18:58 -0500 Subject: [PATCH 6/6] reorganize timer to be above fast api decorator --- examples/fastapi_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/fastapi_example.py b/examples/fastapi_example.py index 43b471730..3e666fcda 100755 --- a/examples/fastapi_example.py +++ b/examples/fastapi_example.py @@ -64,8 +64,8 @@ async def print_greetings(greetings): print(greeting) +@faust_app.timer(5) # make sure you *always* add the timer above if you're using one @fastapi_app.get("/produce") -@faust_app.timer(5) async def produce(): for i in range(100): await greetings_topic.send(value=f'hello {i}')