Version 21.6

Table of Contents

Introduction#

This is the second release of the version 21 release cycle. There will be one more release in September before version 21 is "finalized" in the December long-term support version. One thing users may have noticed starting in 21.3, the router was moved to its own package: sanic-routing. This change is likely to stay for now. Starting with this release, the minimum required version is 0.7.0.

What to know#

More details in the Changelog. Notable new or breaking features, and what to upgrade...

Deprecation of StreamingHTTPResponse#

The use of StreamingHTTPResponse has been deprecated and will be removed in the 21.12 release. This impacts both sanic.response.stream and sanic.response.file_stream, which both under the hood instantiate StreamingHTTPResponse.

Although the exact migration path has yet to be determined, sanic.response.stream and sanic.response.file_stream will continue to exist in v21.12 in some form as convenience operators. Look for more details throughout this Summer as we hope to have this finalized by the September release.

Deprecation of CompositionView#

Usage of CompositionView has been deprecated and will be removed in 21.12.

Deprecation of path parameter types: string and number#

Going forward, you should use str and float for path param types instead of string and number.

@app.get("/<foo:str>/<bar:float>")
async def handler(request, foo: str, bar: float):
    ...

Existing string and number types are aliased and will continue to work, but will be removed in v21.12.

Version 0.7 router upgrades#

This includes a number of bug fixes and more gracefully handles a wider array of edge cases than v0.6. If you experience any patterns that are not supported, please report them. You can see some of the issues resolved on the sanic-routing release notes.

Inline streaming with eof()#

Version 21.3 included big changes in how streaming is handled. The pattern introduced will become the default (see below). As a convenience, a new response.eof() method has been included. It should be called once the final data has been pushed to the client:

@app.route("/")
async def test(request):
    response = await request.respond(content_type="text/csv")
    await response.send("foo,")
    await response.send("bar")
    await response.eof()
    return response

New path parameter type: slug#

You can now specify a dynamic path segment as a slug with appropriate matching:

@app.get("/articles/<article_slug:slug>")
async def article(request, article_slug: str):
    ...

Slugs must consist of lowercase letters or digits. They may contain a hyphen (-), but it cannot be the first character.

this-is-a-slug
with-123-is-also-a-slug
111-at-start-is-a-slug
NOT-a-slug
-NOT-a-slug

Stricter application and blueprint names, and deprecation#

Your application and Blueprint instances must conform to a stricter set of requirements:

  1. Only consisting of alphanumeric characters
  2. May contain a hyphen (-) or an underscore (_)
  3. Must begin with a letter (uppercase or lowercase)

The naming convention is similar to Python variable naming conventions, with the addition of allowing hyphens (-).

The looser standard has been deprecatated. Beginning in 21.12, non-conformance will be a startup time error.

A new access on Route object: route.uri#

The Route object in v21.3 no longer had a uri attribute. Instead, the closes you could get was route.path. However, because of how sanic-routing works, the path property does not have a leading /. This has been corrected so that now there is a route.uri with a leading slash:

route.uri == f"/{route.path}"

A new accessor on Request object impacting IPs#

To access the IP address of the incoming request, Sanic has had a convenience accessor on the request object: request.ip. That is not new, and comes from an underlying object that provides details about the open HTTP connection: request.conn_info.

The current version adds a new client_ip accessor to that conn_info object. For IPv4, you will not notice a difference. However, for IPv6 applications, the new accessor will provide an "unwrapped" version of the address. Consider the following example:

@app.get("/")
async def handler(request):
    return json(
        {
            "request.ip": request.ip,
            "request.conn_info.client": request.conn_info.client,
            "request.conn_info.client_ip": request.conn_info.client_ip,
        }
    )

app.run(sock=my_ipv6_sock)
$ curl http://\[::1\]:8000
{
  "request.ip": "::1",
  "request.conn_info.client": "[::1]",
  "request.conn_info.client_ip": "::1"
}

Alternate Config and Sanic.ctx objects#

You can now pass your own config and context objects to your Sanic applications. A custom configuration should be a subclass of sanic.config.Config. The context object can be anything you want, with no restrictions whatsoever.

class CustomConfig(Config):
    ...

config = CustomConfig()
app = Sanic("custom", config=config)
assert isinstance(app.config, CustomConfig)

And...

class CustomContext:
    ...

ctx = CustomContext()
app = Sanic("custom", ctx=ctx)
assert isinstance(app.ctx, CustomContext)

Sanic CLI improvements#

  1. New flag for existing feature: --auto-reload
  2. Some new shorthand flags for existing arguments
  3. New feature: --factory
  4. New feature: --simple
  5. New feature: --reload-dir

Factory applications#

For applications that follow the factory pattern (a function that returns a sanic.Sanic instance), you can now launch your application from the Sanic CLI using the --factory flag.

from sanic import Blueprint, Sanic, text

bp = Blueprint(__file__)

@bp.get("/")
async def handler(request):
    return text("😎")

def create_app() -> Sanic:
    app = Sanic(__file__)
    app.blueprint(bp)
    return app

You can now run it:

$ sanic path.to:create_app --factory 

Sanic Simple Server#

Sanic CLI now includes a simple pattern to serve a directory as a web server. It will look for an index.html at the directory root.

$ sanic ./path/to/dir --simple

Warning

This feature is still in early beta mode. It is likely to change in scope.

Additional reload directories#

When using either debug or auto-reload, you can include additional directories for Sanic to watch for new files.

sanic ... --reload-dir=/path/to/foo --reload-dir=/path/to/bar

Tip

You do NOT need to include this on your application directory. Sanic will automatically reload when any Python file in your application changes. You should use the reload-dir argument when you want to listen and update your application when static files are updated.

Version prefix#

When adding version, your route is prefixed with /v<YOUR_VERSION_NUM>. This will always be at the beginning of the path. This is not new.

# /v1/my/path
app.route("/my/path", version=1)

Now, you can alter the prefix (and therefore add path segments before the version).

# /api/v1/my/path
app.route("/my/path", version=1, version_prefix="/api/v")

The version_prefix argument is can be defined in:

  • app.route and bp.route decorators (and all the convenience decorators also)
  • Blueprint instantiation
  • Blueprint.group constructor
  • BlueprintGroup instantiation
  • app.blueprint registration

Signal event auto-registration#

Setting config.EVENT_AUTOREGISTER to True will allow you to await any signal event even if it has not previously been defined with a signal handler.

@app.signal("do.something.start")
async def signal_handler():
    await do_something()
    await app.dispatch("do.something.complete")

# somethere else in your app:
await app.event("do.something.complete")

Infinitely reusable and nestable Blueprint and BlueprintGroup#

A single Blueprint may not be assigned and reused to multiple groups. The groups themselves can also by infinitely nested into one or more other groups. This allows for an unlimited range of composition.

HTTP methods as Enum#

Sanic now has sanic.HTTPMethod, which is an Enum. It can be used interchangeably with strings:

from sanic import Sanic, HTTPMethod

@app.route("/", methods=["post", "PUT", HTTPMethod.PATCH])
async def handler(...):
    ...

Expansion of HTTPMethodView#

Class based views may be attached now in one of three ways:

Option 1 - Existing

class DummyView(HTTPMethodView):
    ...

app.add_route(DummyView.as_view(), "/dummy")

Option 2 - From attach method

class DummyView(HTTPMethodView):
    ...

DummyView.attach(app, "/")

Option 3 - From class definition at __init_subclass__

class DummyView(HTTPMethodView, attach=app, uri="/"):
    ...

Options 2 and 3 are useful if your CBV is located in another file:

from sanic import Sanic, HTTPMethodView

class DummyView(HTTPMethodView, attach=Sanic.get_app(), uri="/"):
    ...

News#

Discord and support forums#

If you have not already joined our community, you can become a part by joining the Discord server and the Community Forums. Also, follow @sanicframework on Twitter.

SCO 2022 elections#

The Summer 🏝/Winter ❄️ (choose your Hemisphere) is upon us. That means we will be holding elections for the SCO. This year, we will have the following positions to fill:

  • Steering Council Member (2 year term)
  • Steering Council Member (2 year term)
  • Steering Council Member (1 year term)
  • Release Manager v22
  • Release Manager v22

@vltr will be staying on to complete his second year on the Steering Council.

If you are interested in learning more, you can read about the SCO roles and responsibilities, or Adam Hopkins on Discord.

Nominations will begin September 1. More details will be available on the Forums as we get closer.

New project underway#

We have added a new project to the SCO umbrella: sanic-ext. It is not yet released, and in heavy active development. The goal for the project will ultimately be to replace sanic-openapi with something that provides more features for web application developers, including input validation, CORS handling, and HTTP auto-method handlers. If you are interested in helping out, let us know on Discord. Look for an initial release of this project sometime (hopefully) before the September release.

Thank you#

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

@aaugustin @ahopkins @ajaygupta2790 @ashleysommer @ENT8R @fredlllll @graingert @harshanarayana @jdraymon @Kyle-Verhoog @sanjeevanahilan @sjsadowski @Tronic @vltr @ZinkLu


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.