From dd41471fae6f016fcf7fe8630ec90f5dd336a9da Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 10:32:59 -0500 Subject: [PATCH 1/9] Update readme --- CONTRIBUTING.md | 6 +++--- pkg/inngest/Makefile | 2 +- pkg/inngest_encryption/Makefile | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c9f6778..be5b236c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,12 +65,12 @@ DEV_SERVER_VERBOSE=1 # Publish -Change the package version in `pyproject.toml` and `const.py`. +Change the package version in the package's `pyproject.toml` (e.g. `pkg/inngest/pyproject.toml`). -Create and push a git tag that matches the package version. For example, the following command tags and releases version `1.2.3`: +Set the `VERSION` env var and run the `release` make target for the package. For example, the following command releases version `1.2.3` of the `inngest` package: ```sh -(export TAG=1.2.3 && git tag $TAG && git push origin $TAG) +(cd pkg/inngest && export VERSION=1.2.3 && make release) ``` This will start CI for the tag, including publishing to PyPI. diff --git a/pkg/inngest/Makefile b/pkg/inngest/Makefile index 745c8431..8852923a 100644 --- a/pkg/inngest/Makefile +++ b/pkg/inngest/Makefile @@ -19,7 +19,7 @@ lint: check-venv @ruff check . release: - @grep "version = \"$${VERSION}\"" pyproject.toml && git tag $${VERSION} && git push origin $${VERSION} || echo "pyproject.toml version does not match" + @grep "version = \"$${VERSION}\"" pyproject.toml && git tag inngest@$${VERSION} && git push origin inngest@$${VERSION} || echo "pyproject.toml version does not match" .PHONY: type-check type-check: check-venv diff --git a/pkg/inngest_encryption/Makefile b/pkg/inngest_encryption/Makefile index 75fdbd90..749d0027 100644 --- a/pkg/inngest_encryption/Makefile +++ b/pkg/inngest_encryption/Makefile @@ -20,6 +20,9 @@ itest: check-venv lint: check-venv @ruff check . +release: + @grep "version = \"$${VERSION}\"" pyproject.toml && git tag inngest_encryption@$${VERSION} && git push origin inngest_encryption@$${VERSION} || echo "pyproject.toml version does not match" + .PHONY: type-check type-check: check-venv @mypy --config-file=../../mypy.ini . From b9b3e3219166be047553a15d5fc7226f27afa96b Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 10:43:23 -0500 Subject: [PATCH 2/9] Change version; add readme --- pkg/inngest_encryption/README.md | 3 +++ pkg/inngest_encryption/pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 pkg/inngest_encryption/README.md diff --git a/pkg/inngest_encryption/README.md b/pkg/inngest_encryption/README.md new file mode 100644 index 00000000..c78463a9 --- /dev/null +++ b/pkg/inngest_encryption/README.md @@ -0,0 +1,3 @@ +# Inngest Python SDK: Encryption + +This package provides encryption for the Inngest Python SDK. diff --git a/pkg/inngest_encryption/pyproject.toml b/pkg/inngest_encryption/pyproject.toml index b9a7e7e2..7711b72f 100644 --- a/pkg/inngest_encryption/pyproject.toml +++ b/pkg/inngest_encryption/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "inngest_encryption" -version = "0.0.1" +version = "0.0.1a0" authors = [{ name = "Inngest Inc.", email = "hello@inngest.com" }] description = "Encryption for the Inngest SDK" readme = "README.md" From f1304cb184c3bbfbd49c5125d0c47e6379a96f80 Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 10:50:17 -0500 Subject: [PATCH 3/9] Fix publish dir --- .github/workflows/inngest.yml | 2 ++ .github/workflows/inngest_encryption.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/inngest.yml b/.github/workflows/inngest.yml index 447c9e24..bfc9beb7 100644 --- a/.github/workflows/inngest.yml +++ b/.github/workflows/inngest.yml @@ -84,6 +84,8 @@ jobs: working-directory: "./pkg/inngest" - name: "Upload package to PyPI" uses: "pypa/gh-action-pypi-publish@release/v1" + with: + packages-dir: "./pkg/inngest/dist" type-check: runs-on: "ubuntu-latest" diff --git a/.github/workflows/inngest_encryption.yml b/.github/workflows/inngest_encryption.yml index 15206c45..aa7e5662 100644 --- a/.github/workflows/inngest_encryption.yml +++ b/.github/workflows/inngest_encryption.yml @@ -82,6 +82,8 @@ jobs: working-directory: "./pkg/inngest_encryption" - name: "Upload package to PyPI" uses: "pypa/gh-action-pypi-publish@release/v1" + with: + packages-dir: "./pkg/inngest_encryption/dist" type-check: runs-on: "ubuntu-latest" From 7c818fc840acc3617ee3f7d8feaf421364dd9308 Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 11:10:48 -0500 Subject: [PATCH 4/9] Test relative readme --- .../inngest_encryption/py.typed | 0 pkg/inngest_encryption/pyproject.toml | 15 ++------------- pkg/inngest_encryption/{README.md => xREADME.md} | 0 3 files changed, 2 insertions(+), 13 deletions(-) create mode 100644 pkg/inngest_encryption/inngest_encryption/py.typed rename pkg/inngest_encryption/{README.md => xREADME.md} (100%) diff --git a/pkg/inngest_encryption/inngest_encryption/py.typed b/pkg/inngest_encryption/inngest_encryption/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pkg/inngest_encryption/pyproject.toml b/pkg/inngest_encryption/pyproject.toml index 7711b72f..afeb5d40 100644 --- a/pkg/inngest_encryption/pyproject.toml +++ b/pkg/inngest_encryption/pyproject.toml @@ -1,9 +1,9 @@ [project] name = "inngest_encryption" -version = "0.0.1a0" +version = "0.0.1a1" authors = [{ name = "Inngest Inc.", email = "hello@inngest.com" }] description = "Encryption for the Inngest SDK" -readme = "README.md" +readme = "../../README.md" classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", @@ -20,17 +20,6 @@ dependencies = ["pynacl>=1.5.0"] "Homepage" = "https://github.com/inngest/inngest-py" "Bug Tracker" = "https://github.com/inngest/inngest-py/issues" -# [tool.mypy] -# enable_error_code = ["possibly-undefined", "redundant-expr", "truthy-bool"] -# incremental = false -# mypy_path = ["../inngest", "../test_core"] -# strict = true -# warn_unreachable = true - -# [[tool.mypy.overrides]] -# module = "jcs" -# ignore_missing_imports = true - [tool.pylint.'MESSAGES CONTROL'] disable = [ 'broad-exception-caught', diff --git a/pkg/inngest_encryption/README.md b/pkg/inngest_encryption/xREADME.md similarity index 100% rename from pkg/inngest_encryption/README.md rename to pkg/inngest_encryption/xREADME.md From 8fea11b14840c7149406660abc58af8d9d54de8e Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 11:16:06 -0500 Subject: [PATCH 5/9] Try symlink readme --- pkg/inngest_encryption/README.md | 1 + pkg/inngest_encryption/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 120000 pkg/inngest_encryption/README.md diff --git a/pkg/inngest_encryption/README.md b/pkg/inngest_encryption/README.md new file mode 120000 index 00000000..61cfe9a5 --- /dev/null +++ b/pkg/inngest_encryption/README.md @@ -0,0 +1 @@ +./README.md \ No newline at end of file diff --git a/pkg/inngest_encryption/pyproject.toml b/pkg/inngest_encryption/pyproject.toml index afeb5d40..ade61731 100644 --- a/pkg/inngest_encryption/pyproject.toml +++ b/pkg/inngest_encryption/pyproject.toml @@ -3,7 +3,7 @@ name = "inngest_encryption" version = "0.0.1a1" authors = [{ name = "Inngest Inc.", email = "hello@inngest.com" }] description = "Encryption for the Inngest SDK" -readme = "../../README.md" +readme = "README.md" classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", From d57e9217caeb457ab21d1d436b70620e7f67bdb8 Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 11:32:24 -0500 Subject: [PATCH 6/9] Use readme symlink in repo root --- README.md | 207 +----------------------------- pkg/inngest/README.md | 206 +++++++++++++++++++++++++++++ pkg/inngest_encryption/README.md | 4 +- pkg/inngest_encryption/xREADME.md | 3 - 4 files changed, 210 insertions(+), 210 deletions(-) mode change 100644 => 120000 README.md create mode 100644 pkg/inngest/README.md mode change 120000 => 100644 pkg/inngest_encryption/README.md delete mode 100644 pkg/inngest_encryption/xREADME.md diff --git a/README.md b/README.md deleted file mode 100644 index 7fb69ba4..00000000 --- a/README.md +++ /dev/null @@ -1,206 +0,0 @@ -
-
- -
-
-

- Serverless event-driven queues, background jobs, and scheduled jobs for Python.
- Add durable functions and workflows to any framework and platform. -

- Read the documentation and get started in minutes. -
-

- -[![pypi](https://img.shields.io/pypi/v/inngest.svg)](https://pypi.python.org/pypi/inngest) -![versions](https://img.shields.io/pypi/pyversions/inngest.svg) -[![discord](https://img.shields.io/discord/842170679536517141?label=discord)](https://www.inngest.com/discord) -[![twitter](https://img.shields.io/twitter/follow/inngest?style=social)](https://twitter.com/inngest) - -

-
- -
- -# Inngest Python SDK - -Inngest's SDK adds durable functions to Python in a few lines of code. Using this SDK, you can write -background jobs as step functions without new queueing infrastructure such as celery. - -We currently support the following frameworks (but adding a new framework is easy!): - -- DigitalOcean Functions -- Django (`>=4.2`) -- FastAPI (`>=0.100.0`) -- Flask (`>=2.3.0`) -- Tornado (`>=6.3`) - -Python 3.9 is the minimum version we support. - -## Getting started - -[Quick start guide](https://www.inngest.com/docs/getting-started/quick-start/python) - -## Examples - -> 💡 You can mix `async` and non-`async` functions in the same app! - -- [Basic](#basic-no-steps) -- [Step run](#step-run) -- [Async function](#async-function) - -### Basic (no steps) - -This is a minimal example of an Inngest function: - -```py -import flask -import inngest.flask -import requests - -inngest_client = inngest.Inngest( - app_id="flask_example", - is_production=False, -) - -@inngest_client.create_function( - fn_id="find_person", - trigger=inngest.TriggerEvent(event="app/person.find"), -) -def fetch_person( - ctx: inngest.Context, - step: inngest.StepSync, -) -> dict: - person_id = ctx.event.data["person_id"] - res = requests.get(f"https://swapi.dev/api/people/{person_id}") - return res.json() - -app = flask.Flask(__name__) - -# Register functions with the Inngest server -inngest.flask.serve( - app, - inngest_client, - [fetch_person], -) - -app.run(port=8000) -``` - -[Each function is automatically backed by its own queue](https://www.inngest.com/docs/learn/how-functions-are-executed). Functions can contain steps, which act as code -level transactions. Each step retries on failure, and runs once on success. Function state is automatically managed. - -Let's run the function. Send the following event in the [local development server (Dev Server UI)](https://www.inngest.com/docs/local-development) and the `fetch_person` function will run: - -```json -{ - "name": "app/person.find", - "data": { - "person_id": 1 - } -} -``` - -### Step run - -The following example registers a function that will: - -1. Get the person ID from the event -1. Fetch the person with that ID -1. Fetch the person's ships -1. Return a summary dict - -```py -@inngest_client.create_function( - fn_id="find_ships", - trigger=inngest.TriggerEvent(event="app/ships.find"), -) -def fetch_ships( - ctx: inngest.Context, - step: inngest.StepSync, -) -> dict: - """ - Find all the ships a person has. - """ - - person_id = ctx.event.data["person_id"] - - def _fetch_person() -> dict: - res = requests.get(f"https://swapi.dev/api/people/{person_id}") - return res.json() - - # Wrap the function with step.run to enable retries - person = step.run("fetch_person", _fetch_person) - - def _fetch_ship(url: str) -> dict: - res = requests.get(url) - return res.json() - - ship_names = [] - for ship_url in person["starships"]: - # step.run works in loops! - ship = step.run("fetch_ship", _fetch_ship, ship_url) - - ship_names.append(ship["name"]) - - return { - "person_name": person["name"], - "ship_names": ship_names, - } -``` - -Send the following event in the Dev Server UI and the `fetch_person` function will run: - -```json -{ - "name": "app/ships.find", - "data": { - "person_id": 1 - } -} -``` - -### Async function - -```py -@inngest_client.create_function( - fn_id="find_person", - trigger=inngest.TriggerEvent(event="app/person.find"), -) -async def fetch_person( - ctx: inngest.Context, - step: inngest.Step, -) -> dict: - person_id = ctx.event.data["person_id"] - async with httpx.AsyncClient() as client: - res = await client.get(f"https://swapi.dev/api/people/{person_id}") - return res.json() -``` - -### Sending an event outside a function - -Sometimes you want to send an event from a normal, non-Inngest function. You can do that using the client: - -```py -inngest_client.send_sync(inngest.Event(name="app/test", data={"person_id": 1})) -``` - -If you prefer `async` then use the `send` method instead: - -```py -await inngest_client.send(inngest.Event(name="app/test", data={"person_id": 1})) -``` - -## Using in production - -The Dev Server is not used in production. [Inngest Cloud](https://app.inngest.com) is used instead. - -The `INNGEST_EVENT_KEY` and `INNGEST_SIGNING_KEY` environment variables must be set. These secrets establish trust between Inngest Cloud and your app. We also use request signature verification to mitigate man-in-the-middle attacks. You can read more about [environment variables](https://www.inngest.com/docs/reference/python/overview/env-vars) in our docs. - -Your Inngest client must be in production mode. This is typically done with an environment variable: - -```py -inngest_client = inngest.Inngest( - app_id="my_app", - is_production=os.getenv("INNGEST_DEV") is None, -) -``` diff --git a/README.md b/README.md new file mode 120000 index 00000000..c781c642 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +./pkg/inngest/README.md \ No newline at end of file diff --git a/pkg/inngest/README.md b/pkg/inngest/README.md new file mode 100644 index 00000000..7fb69ba4 --- /dev/null +++ b/pkg/inngest/README.md @@ -0,0 +1,206 @@ +
+
+ +
+
+

+ Serverless event-driven queues, background jobs, and scheduled jobs for Python.
+ Add durable functions and workflows to any framework and platform. +

+ Read the documentation and get started in minutes. +
+

+ +[![pypi](https://img.shields.io/pypi/v/inngest.svg)](https://pypi.python.org/pypi/inngest) +![versions](https://img.shields.io/pypi/pyversions/inngest.svg) +[![discord](https://img.shields.io/discord/842170679536517141?label=discord)](https://www.inngest.com/discord) +[![twitter](https://img.shields.io/twitter/follow/inngest?style=social)](https://twitter.com/inngest) + +

+
+ +
+ +# Inngest Python SDK + +Inngest's SDK adds durable functions to Python in a few lines of code. Using this SDK, you can write +background jobs as step functions without new queueing infrastructure such as celery. + +We currently support the following frameworks (but adding a new framework is easy!): + +- DigitalOcean Functions +- Django (`>=4.2`) +- FastAPI (`>=0.100.0`) +- Flask (`>=2.3.0`) +- Tornado (`>=6.3`) + +Python 3.9 is the minimum version we support. + +## Getting started + +[Quick start guide](https://www.inngest.com/docs/getting-started/quick-start/python) + +## Examples + +> 💡 You can mix `async` and non-`async` functions in the same app! + +- [Basic](#basic-no-steps) +- [Step run](#step-run) +- [Async function](#async-function) + +### Basic (no steps) + +This is a minimal example of an Inngest function: + +```py +import flask +import inngest.flask +import requests + +inngest_client = inngest.Inngest( + app_id="flask_example", + is_production=False, +) + +@inngest_client.create_function( + fn_id="find_person", + trigger=inngest.TriggerEvent(event="app/person.find"), +) +def fetch_person( + ctx: inngest.Context, + step: inngest.StepSync, +) -> dict: + person_id = ctx.event.data["person_id"] + res = requests.get(f"https://swapi.dev/api/people/{person_id}") + return res.json() + +app = flask.Flask(__name__) + +# Register functions with the Inngest server +inngest.flask.serve( + app, + inngest_client, + [fetch_person], +) + +app.run(port=8000) +``` + +[Each function is automatically backed by its own queue](https://www.inngest.com/docs/learn/how-functions-are-executed). Functions can contain steps, which act as code +level transactions. Each step retries on failure, and runs once on success. Function state is automatically managed. + +Let's run the function. Send the following event in the [local development server (Dev Server UI)](https://www.inngest.com/docs/local-development) and the `fetch_person` function will run: + +```json +{ + "name": "app/person.find", + "data": { + "person_id": 1 + } +} +``` + +### Step run + +The following example registers a function that will: + +1. Get the person ID from the event +1. Fetch the person with that ID +1. Fetch the person's ships +1. Return a summary dict + +```py +@inngest_client.create_function( + fn_id="find_ships", + trigger=inngest.TriggerEvent(event="app/ships.find"), +) +def fetch_ships( + ctx: inngest.Context, + step: inngest.StepSync, +) -> dict: + """ + Find all the ships a person has. + """ + + person_id = ctx.event.data["person_id"] + + def _fetch_person() -> dict: + res = requests.get(f"https://swapi.dev/api/people/{person_id}") + return res.json() + + # Wrap the function with step.run to enable retries + person = step.run("fetch_person", _fetch_person) + + def _fetch_ship(url: str) -> dict: + res = requests.get(url) + return res.json() + + ship_names = [] + for ship_url in person["starships"]: + # step.run works in loops! + ship = step.run("fetch_ship", _fetch_ship, ship_url) + + ship_names.append(ship["name"]) + + return { + "person_name": person["name"], + "ship_names": ship_names, + } +``` + +Send the following event in the Dev Server UI and the `fetch_person` function will run: + +```json +{ + "name": "app/ships.find", + "data": { + "person_id": 1 + } +} +``` + +### Async function + +```py +@inngest_client.create_function( + fn_id="find_person", + trigger=inngest.TriggerEvent(event="app/person.find"), +) +async def fetch_person( + ctx: inngest.Context, + step: inngest.Step, +) -> dict: + person_id = ctx.event.data["person_id"] + async with httpx.AsyncClient() as client: + res = await client.get(f"https://swapi.dev/api/people/{person_id}") + return res.json() +``` + +### Sending an event outside a function + +Sometimes you want to send an event from a normal, non-Inngest function. You can do that using the client: + +```py +inngest_client.send_sync(inngest.Event(name="app/test", data={"person_id": 1})) +``` + +If you prefer `async` then use the `send` method instead: + +```py +await inngest_client.send(inngest.Event(name="app/test", data={"person_id": 1})) +``` + +## Using in production + +The Dev Server is not used in production. [Inngest Cloud](https://app.inngest.com) is used instead. + +The `INNGEST_EVENT_KEY` and `INNGEST_SIGNING_KEY` environment variables must be set. These secrets establish trust between Inngest Cloud and your app. We also use request signature verification to mitigate man-in-the-middle attacks. You can read more about [environment variables](https://www.inngest.com/docs/reference/python/overview/env-vars) in our docs. + +Your Inngest client must be in production mode. This is typically done with an environment variable: + +```py +inngest_client = inngest.Inngest( + app_id="my_app", + is_production=os.getenv("INNGEST_DEV") is None, +) +``` diff --git a/pkg/inngest_encryption/README.md b/pkg/inngest_encryption/README.md deleted file mode 120000 index 61cfe9a5..00000000 --- a/pkg/inngest_encryption/README.md +++ /dev/null @@ -1 +0,0 @@ -./README.md \ No newline at end of file diff --git a/pkg/inngest_encryption/README.md b/pkg/inngest_encryption/README.md new file mode 100644 index 00000000..c78463a9 --- /dev/null +++ b/pkg/inngest_encryption/README.md @@ -0,0 +1,3 @@ +# Inngest Python SDK: Encryption + +This package provides encryption for the Inngest Python SDK. diff --git a/pkg/inngest_encryption/xREADME.md b/pkg/inngest_encryption/xREADME.md deleted file mode 100644 index c78463a9..00000000 --- a/pkg/inngest_encryption/xREADME.md +++ /dev/null @@ -1,3 +0,0 @@ -# Inngest Python SDK: Encryption - -This package provides encryption for the Inngest Python SDK. From 9b0a703144bc5d4b0bd5eeb9cfd884ba3f428b4a Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 11:33:08 -0500 Subject: [PATCH 7/9] Bump version --- pkg/inngest_encryption/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/inngest_encryption/pyproject.toml b/pkg/inngest_encryption/pyproject.toml index ade61731..58667861 100644 --- a/pkg/inngest_encryption/pyproject.toml +++ b/pkg/inngest_encryption/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "inngest_encryption" -version = "0.0.1a1" +version = "0.0.1a2" authors = [{ name = "Inngest Inc.", email = "hello@inngest.com" }] description = "Encryption for the Inngest SDK" readme = "README.md" From 9f1bf50369b1335ca1410f23377b24dc1d96a4a4 Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 11:42:04 -0500 Subject: [PATCH 8/9] inngest dep --- pkg/inngest_encryption/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/inngest_encryption/pyproject.toml b/pkg/inngest_encryption/pyproject.toml index 58667861..85959e28 100644 --- a/pkg/inngest_encryption/pyproject.toml +++ b/pkg/inngest_encryption/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "inngest_encryption" -version = "0.0.1a2" +version = "0.0.1a3" authors = [{ name = "Inngest Inc.", email = "hello@inngest.com" }] description = "Encryption for the Inngest SDK" readme = "README.md" @@ -14,7 +14,7 @@ classifiers = [ ] requires-python = ">=3.9" -dependencies = ["pynacl>=1.5.0"] +dependencies = ["inngest>=0.4.18", "pynacl>=1.5.0"] [project.urls] "Homepage" = "https://github.com/inngest/inngest-py" From a2c6b8d2b9d9fbc0bbf599ab7cad7328a170d3e8 Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Mon, 10 Feb 2025 11:48:08 -0500 Subject: [PATCH 9/9] Version 0.0.1 --- pkg/inngest_encryption/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/inngest_encryption/pyproject.toml b/pkg/inngest_encryption/pyproject.toml index 85959e28..ae19e9de 100644 --- a/pkg/inngest_encryption/pyproject.toml +++ b/pkg/inngest_encryption/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "inngest_encryption" -version = "0.0.1a3" +version = "0.0.1" authors = [{ name = "Inngest Inc.", email = "hello@inngest.com" }] description = "Encryption for the Inngest SDK" readme = "README.md"