# Python SDK migration guide: v0.4 to v0.5
Source: https://www.inngest.com/docs/reference/python/migrations/v0.4-to-v0.5

This guide will help you migrate your Inngest Python SDK from v0.4 to v0.5.

## New features

- First-class Pydantic support in step and function output ([docs](/docs/reference/python/guides/pydantic))
- Python 3.13 support
- Function singletons ([docs](/docs/guides/singleton))
- Function timeouts ([docs](/docs/features/inngest-functions/cancellation/cancel-on-timeouts))
- Experimental step.infer ([docs](/docs/features/inngest-functions/steps-workflows/step-ai-orchestration))
- Improved parallel step performance

## Breaking changes

### Move `step` into `ctx`
The `step` object will be moved to `ctx.step`.

Before:
```py
!snippet:path=snippets/py/v0_4/migration_from/step.py
```

After:
```py
!snippet:path=snippets/py/v0_5/migration_to/step.py
```

### 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`.

```py
!snippet:path=snippets/py/v0_5/migration_to/parallel.py
```

### 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:

```py
!snippet:path=snippets/py/v0_5/migration_to/mixed_async_sync_fn.py
```

Setting a non-async `on_failure` on an async Inngest function will throw an error:
```py
!snippet:path=snippets/py/v0_5/migration_to/mixed_async_async_fn.py
```

### 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.

```py
!snippet:path=snippets/py/v0_5/migration_to/non_async_step_run_callback.py
```

### `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:
```py
!snippet:path=snippets/py/v0_5/migration_to/middleware_order.py
```

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](https://pypi.org/project/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
```