releases.shpreview
Inngest/Python SDK/inngest@0.5.0

inngest@0.5.0

inngest v0.5.0

$npx -y @buildinternet/releases show rel_anTg78JX7eZQaQE6DQ3Q5

Release blog post here

Full migration guide here

New features

  • First-class Pydantic support in step and function output
  • AI orchestration with step.infer (docs) -- currently experimental
  • Connect is stable (docs)
  • Python 3.13 support
  • Event-sending retries
  • Function singletons (docs)
  • Function timeouts (docs)
  • Improved parallel step performance

Breaking changes

Move step into ctx

The step object will be moved to ctx.step.

Before:

@inngest_client.create_function(
    fn_id="provision-user",
    trigger=inngest.TriggerEvent(event="user.signup"),
)
async def fn(ctx: inngest.Context, step: inngest.Step) -> None:
    await step.run("create-user", create_db_user)

After:

@inngest_client.create_function(
    fn_id="provision-user",
    trigger=inngest.TriggerEvent(event="user.signup"),
)
async def fn(ctx: inngest.Context) -> None:
    await ctx.step.run("create-user", create_db_user)

Parallel steps

step.parallel will be removed in favor of a new ctx.group.parallel method. This method will behave the same way, so it's a drop-in replacement for step.parallel.

@client.create_function(
  fn_id="my-fn",
  trigger=inngest.TriggerEvent(event="my-event"),
)
async def fn(
  ctx: inngest.Context,
  step: inngest.Step,
) -> None:
  user_id = ctx.event.data["user_id"]

  await ctx.group.parallel(
    (
      lambda: step.run("update-user", update_user, user_id),
      lambda: step.run("send-email", send_email, user_id),
    )
  )

Remove event.user

We're sunsetting event.user. It's already incompatible with some features (e.g. function run replay).

Disallow mixed async-ness within Inngest functions

Setting an async on_failure on a non-async Inngest function will throw an error:

async def on_failure(ctx: inngest.Context) -> None:
    pass

@client.create_function(
    fn_id="foo",
    trigger=inngest.TriggerEvent(event="foo"),
    on_failure=on_failure,
)
def fn(ctx: inngest.ContextSync) -> None:
    pass

Setting a non-async on_failure on an async Inngest function will throw an error:

def on_failure(ctx: inngest.ContextSync) -> None:
    pass

@client.create_function(
    fn_id="foo",
    trigger=inngest.TriggerEvent(event="foo"),
    on_failure=on_failure,
)
async def fn(ctx: inngest.Context) -> None:
    pass

Static error when passing a non-async callback to an async step.run

When passing a non-async callback to an async step.run, it will work at runtime but there will be a static type error.

@client.create_function(
    fn_id="foo",
    trigger=inngest.TriggerEvent(event="foo"),
)
async def fn(ctx: inngest.Context) -> None:
    # Type error because `lambda: "hello"` is non-async.
    msg = await step.run("step", lambda: "hello")

    # Runtime value is "hello", as expected.
    print(msg)

inngest.Function is generic

The inngest.Function class is now a generic that represents the return type. So if an Inngest function returns str then it would be inngest.Function[str].

Middleware order

Use LIFO for the "after" hooks. In other words, when multiple middleware is specified then the "after" hooks are run in reverse order.

For example, let's say the following middleware is defined and used:

class A(inngest.MiddlewareSync):
    def before_execution(self) -> None:
        # ...

    def after_execution(self) -> None:
        # ...

class B(inngest.MiddlewareSync):
    def before_execution(self) -> None:
        # ...

    def after_execution(self) -> None:
        # ...

inngest.Inngest(
    app_id="my-app",
    middleware=[A, B],
)

The middleware will be executed in the following order for each hook:

  • before_execution -- A then B.
  • after_execution -- B then A.

The "before" hooks are:

before_execution
before_response
before_send_events
transform_input

The "after" hooks are:

after_execution
after_send_events
transform_output

Remove middleware hooks

  • before_memoization
  • after_memoization

Remove experimental stuff

  • inngest.experimental.encryption_middleware (it's now the inngest-encryption package).
  • experimental_execution option on functions. We won't support native asyncio methods (e.g. asyncio.gather) going forward.

Dependencies

Drop support for Python 3.9.

Bump dependency minimum versions:

httpx>=0.26.0
pydantic>=2.11.0
typing-extensions>=4.13.0

Bump peer dependency minimum versions:

Django>=5.0
Flask>=3.0.0
fastapi>=0.110.0
tornado>=6.4

Fetched April 3, 2026