---
title: Version 23.6
---

# Version 23.6

.. toc::


## Introduction

This is the second release of the version 23 [release cycle](../../organization/policies.md#release-schedule). If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose).

## What to know

More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...

### Remove Python 3.7 support

Python 3.7 is due to reach its scheduled upstream end-of-life on 2023-06-27. Sanic is now dropping support for Python 3.7, and requires Python 3.8 or newer.

See [#2777](https://github.com/sanic-org/sanic/pull/2777).

### Resolve pypy compatibility issues

A small patch was added to the `os` module to once again allow for Sanic to run with PyPy. This workaround replaces the missing `readlink` function (missing in PyPy `os` module) with the function `os.path.realpath`, which serves to the same purpose.

See [#2782](https://github.com/sanic-org/sanic/pull/2782).

### Add custom typing to config and ctx objects

The `sanic.Sanic` and `sanic.Request` object have become generic types that will make it more convenient to have fully typed `config` and `ctx` objects.

In the most simple form, the `Sanic` object is typed as:

```python
from sanic import Sanic
app = Sanic("test")
reveal_type(app)  # N: Revealed type is "sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]"
```


.. tip:: Note

    It should be noted, there is *no* requirement to use the generic types. The default types are `sanic.config.Config` and `types.SimpleNamespace`. This new feature is just an option for those that want to use it and existing types of `app: Sanic` and `request: Request` should work just fine.


Now it is possible to have a fully-type `app.config`, `app.ctx`, and `request.ctx` objects though generics. This allows for better integration with auto completion tools in IDEs improving the developer experience.

```python
from sanic import Request, Sanic
from sanic.config import Config

class CustomConfig(Config):
    pass

class Foo:
    pass

class RequestContext:
    foo: Foo

class CustomRequest(Request[Sanic[CustomConfig, Foo], RequestContext]):
    @staticmethod
    def make_context() -> RequestContext:
        ctx = RequestContext()
        ctx.foo = Foo()
        return ctx

app = Sanic(
    "test", config=CustomConfig(), ctx=Foo(), request_class=CustomRequest
)

@app.get("/")
async def handler(request: CustomRequest):
   ...
```

As a side effect, now `request.ctx` is lazy initialized, which should reduce some overhead when the `request.ctx` is unused.

One further change you may have noticed in the above snippet is the `make_context` method. This new method can be used by custom `Request` types to inject an object different from a `SimpleNamespace` similar to how Sanic has allowed custom application context objects for a while.

For a more thorough discussion, see [custom typed application](../../guide/basics/app.md#custom-typed-application) and [custom typed request](../../guide/basics/app.md#custom-typed-request).

See [#2785](https://github.com/sanic-org/sanic/pull/2785).

### Universal exception signal

A new exception signal added for **ALL** exceptions raised while the server is running: `"server.exception.reporting"`. This is a universal signal that will be emitted for any exception raised, and dispatched as its own task. This means that it will *not* block the request handler, and will *not* be affected by any middleware.

This is useful for catching exceptions that may occur outside of the request handler (for example in signals, or in a background task), and it intended for use to create a consistent error handling experience for the user.

```python
from sanic.signals import Event

@app.signal(Event.SERVER_LIFECYCLE_EXCEPTION)
async def catch_any_exception(app: Sanic, exception: Exception):
    app.ctx.my_error_reporter_utility.error(exception)
```

This pattern can be simplified with a new decorator `@app.report_exception`:

```python
@app.report_exception
async def catch_any_exception(app: Sanic, exception: Exception):
    print("Caught exception:", exception)
```

It should be pointed out that this happens in a background task and is **NOT** for manipulation of an error response. It is only for reporting, logging, or other purposes that should be triggered when an application error occurs.

See [#2724](https://github.com/sanic-org/sanic/pull/2724) and [#2792](https://github.com/sanic-org/sanic/pull/2792).

### Add name prefixing to BP groups

Sanic had been raising a warning on duplicate route names for a while, and started to enforce route name uniqueness in [v23.3](https://sanic.dev/en/guide/release-notes/v23.3.html#deprecations-and-removals). This created a complication for blueprint composition.

New name prefixing parameter for blueprints groups has been added to alleviate this issue. It allows nesting of blueprints and groups to make them composable.

The addition is the new `name_prefix` parameter as shown in this snippet.

```python
bp1 = Blueprint("bp1", url_prefix="/bp1")
bp2 = Blueprint("bp2", url_prefix="/bp2")

bp1.add_route(lambda _: ..., "/", name="route1")
bp2.add_route(lambda _: ..., "/", name="route2")

group_a = Blueprint.group(
    bp1, bp2, url_prefix="/group-a", name_prefix="group-a"
)
group_b = Blueprint.group(
    bp1, bp2, url_prefix="/group-b", name_prefix="group-b"
)

app = Sanic("TestApp")
app.blueprint(group_a)
app.blueprint(group_b)
```

The routes built will be named as follows:
- `TestApp.group-a_bp1.route1`
- `TestApp.group-a_bp2.route2`
- `TestApp.group-b_bp1.route1`
- `TestApp.group-b_bp2.route2`

See [#2727](https://github.com/sanic-org/sanic/pull/2727).

### Add `request.client_ip`

Sanic has introduced `request.client_ip`, a new accessor that provides client's IP address from both local and proxy data. It allows running the application directly on Internet or behind a proxy. This is equivalent to `request.remote_addr or request.ip`, providing the client IP regardless of how the application is deployed.

See [#2790](https://github.com/sanic-org/sanic/pull/2790).

### Increase of `KEEP_ALIVE_TIMEOUT` default to 120 seconds

The default `KEEP_ALIVE_TIMEOUT` value changed from 5 seconds to 120 seconds. It is of course still configurable, but this change should improve performance on long latency connections, where reconnecting is expensive, and better fits typical user flow browsing pages with longer-than-5-second intervals.

Sanic has historically used 5 second timeouts to quickly close idle connections. The chosen value of **120 seconds** is indeed larger than Nginx default of 75, and is the same value that Caddy server has by default.

Related to [#2531](https://github.com/sanic-org/sanic/issues/2531) and 
[#2681](https://github.com/sanic-org/sanic/issues/2681). 

See [#2670](https://github.com/sanic-org/sanic/pull/2670).

### Set multiprocessing start method early

Due to how Python handles `multiprocessing`, it may be confusing to some users how to properly create synchronization primitives. This is due to how Sanic creates the `multiprocessing` context. This change sets the start method early so that any primitives created will properly attach to the correct context.

For most users, this should not be noticeable or impactful. But, it should make creation of something like this easier and work as expected.

```python
from multiprocessing import Queue

@app.main_process_start
async def main_process_start(app):
    app.shared_ctx.queue = Queue()
```

See [#2776](https://github.com/sanic-org/sanic/pull/2776).

## Thank you

Thank you to everyone that participated in this release: :clap:

[@ahopkins](https://github.com/ahopkins)
[@ChihweiLHBird](https://github.com/ChihweiLHBird)
[@chuckds](https://github.com/chuckds)
[@deounix](https://github.com/deounix)
[@guacs](https://github.com/guacs)
[@liamcoatman](https://github.com/liamcoatman)
[@moshe742](https://github.com/moshe742)
[@prryplatypus](https://github.com/prryplatypus)
[@SaidBySolo](https://github.com/SaidBySolo)
[@Thirumalai](https://github.com/Thirumalai)
[@Tronic](https://github.com/Tronic)

---

If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).
