14 KiB
Version 23.3
.. toc::
Introduction
This is the first release of the version 23 release cycle. As such contains some deprecations and hopefully some small breaking changes. If you run into any issues, please raise a concern on GitHub.
What to know
More details in the Changelog. Notable new or breaking features, and what to upgrade...
Nicer traceback formatting
The SCO adopted two projects into the Sanic namespace on GitHub: tracerite and html5tagger. These projects team up to provide and incredible new error page with more details to help the debugging process.
This is provided out of the box, and will adjust to display only relevant information whether in DEBUG more or PROD mode.
.. column::
**Using PROD mode**
![image](/assets/images/error-html-no-debug.png)
.. column::
**Using DEBUG mode**
![image](/assets/images/error-html-debug.png)
Light and dark mode HTML pages are available and will be used implicitly.
Basic file browser on directories
When serving a directory from a static handler, Sanic can be configured to show a basic file browser instead using directory_view=True
.
.. column::
```python
app.static("/uploads/", "/path/to/dir/", directory_view=True)
```
.. column::
![image](/assets/images/directory-view.png)
Light and dark mode HTML pages are available and will be used implicitly.
HTML templating with Python
Because Sanic is using html5tagger under the hood to render the new error pages, you now have the package available to you to easily generate HTML pages in Python code:
.. column::
```python
from html5tagger import Document
from sanic import Request, Sanic, html
app = Sanic("TestApp")
@app.get("/")
async def handler(request: Request):
doc = Document("My Website")
doc.h1("Hello, world.")
with doc.table(id="data"):
doc.tr.th("First").th("Second").th("Third")
doc.tr.td(1).td(2).td(3)
doc.p(class_="text")("A paragraph with ")
doc.a(href="/files")("a link")(" and ").em("formatting")
return html(doc)
```
.. column::
```html
<!DOCTYPE html>
<meta charset="utf-8">
<title>My Website</title>
<h1>Hello, world.</h1>
<table id=data>
<tr>
<th>First
<th>Second
<th>Third
<tr>
<td>1
<td>2
<td>3
</table>
<p class=text>
A paragraph with <a href="/files">a link</a> and <em>formatting</em>
```
Auto-index serving is available on static handlers
Sanic can now be configured to serve an index file when serving a static directory.
app.static("/assets/", "/path/to/some/dir", index="index.html")
When using the above, requests to http://example.com/assets/
will automatically serve the index.html
file located in that directory.
Simpler CLI targets
It is common practice for Sanic applications to use the variable app
as the application instance. Because of this, the CLI application target (the second value of the sanic
CLI command) now tries to infer the application instance based upon what the target is. If the target is a module that contains an app
variable, it will use that.
There are now four possible ways to launch a Sanic application from the CLI.
1. Application instance
As normal, providing a path to a module and an application instance will work as expected.
sanic path.to.module:app # global app instance
2. Application factory
Previously, to serve the factory pattern, you would need to use the --factory
flag. This can be omitted now.
sanic path.to.module:create_app # factory pattern
3. Path to launch Sanic Simple Server
Similarly, to launch the Sanic simple server (serve static directory), you previously needed to use the --simple
flag. This can be omitted now, and instead simply provide the path to the directory.
sanic ./path/to/directory/ # simple serve
4. Python module containing an app
variable
As stated above, if the target is a module that contains an app
variable, it will use that (assuming that app
variable is a Sanic
instance).
sanic path.to.module # module with app instance
More convenient methods for setting and deleting cookies
The old cookie pattern was awkward and clunky. It didn't look like regular Python because of the "magic" going on under the hood.
.. column::
😱 This is not intuitive and is confusing for newcomers.
.. column::
```python
response = text("There's a cookie up in this response")
response.cookies["test"] = "It worked!"
response.cookies["test"]["domain"] = ".yummy-yummy-cookie.com"
response.cookies["test"]["httponly"] = True
```
There are now new methods (and completely overhauled Cookie
and CookieJar
objects) to make this process more convenient.
.. column::
😌 Ahh... Much nicer.
.. column::
```python
response = text("There's a cookie up in this response")
response.add_cookie(
"test",
"It worked!",
domain=".yummy-yummy-cookie.com",
httponly=True
)
```
Better cookie compatibility
Sanic has added support for cookie prefixes, making it seemless and easy to read and write cookies with the values.
While setting the cookie...
response.cookies.add_cookie("foo", "bar", host_prefix=True)
This will create the prefixed cookie: __Host-foo
. However, when accessing the cookie on an incoming request, you can do so without knowing about the existence of the header.
request.cookies.get("foo")
It should also be noted, cookies can be accessed as properties just like headers.
request.cookies.foo
And, cookies are similar to the request.args
and request.form
objects in that multiple values can be retrieved using getlist
.
request.cookies.getlist("foo")
Also added is support for creating partitioned cookies.
response.cookies.add_cookie(..., partitioned=True)
🚨 BREAKING CHANGE - More consistent and powerful SanicException
Sanic has for a while included the SanicException
as a base class exception. This could be extended to add status_code
, etc. See more details.
NOW, using all of the various exceptions has become easier. The commonly used exceptions can be imported directly from the root level module.
from sanic import NotFound, Unauthorized, BadRequest, ServerError
In addition, all of these arguments are available as keyword arguments on every exception type:
argument | type | description |
---|---|---|
quiet |
bool |
Suppress the traceback from the logs |
context |
dict |
Additional information shown in error pages always |
extra |
dict |
Additional information shown in error pages in DEBUG mode |
headers |
dict |
Additional headers sent in the response |
None of these are themselves new features. However, they are more consistent in how you can use them, thus creating a powerful way to control error responses directly.
raise ServerError(headers={"foo": "bar"})
The part of this that is a breaking change is that some formerly positional arguments are now keyword only.
You are encouraged to look at the specific implementations for each error in the API documents.
🚨 BREAKING CHANGE - Refresh Request.accept
functionality to be more performant and spec-compliant
Parsing od the Accept
headers into the Request.accept
accessor has been improved. If you were using this property and relying upon its equality operation, this has changed. You should probably transition to using the request.accept.match()
method.
Access any header as a property
To simplify access to headers, you can access a raw (unparsed) version of the header as a property. The name of the header is the name of the property in all lowercase letters, and switching any hyphens (-
) to underscores (_
).
For example:
.. column::
```
GET /foo/bar HTTP/1.1
Host: localhost
User-Agent: curl/7.88.1
X-Request-ID: 123ABC
```
.. column::
```py
request.headers.host
request.headers.user_agent
request.headers.x_request_id
```
Consume DELETE
body by default
By default, the body of a DELETE
request will now be consumed and read onto the Request
object. This will make body
available like on POST
, PUT
, and PATCH
requests without any further action.
Custom CertLoader
for direct control of creating SSLContext
Sometimes you may want to create your own SSLContext
object. To do this, you can create your own subclass of CertLoader
that will generate your desired context object.
from sanic.worker.loader import CertLoader
class MyCertLoader(CertLoader):
def load(self, app: Sanic) -> SSLContext:
...
app = Sanic(..., certloader_class=MyCertLoader)
Deprecations and Removals
- DEPRECATED - Dict-style cookie setting
- DEPRECATED - Using existence of JSON data on the request for one factor in using JSON error formatter
- REMOVED - Remove deprecated
__blueprintname__
property - REMOVED - duplicate route names
- REMOVED - duplicate exception handler definitions
- REMOVED - inspector CLI with flags
- REMOVED - legacy server (including
sanic.server.serve_single
andsanic.server.serve_multiple
) - REMOVED - serving directory with bytes string
- REMOVED -
Request.request_middleware_started
- REMOVED -
Websocket.connection
Duplicated route names are no longer allowed
In version 22.9, Sanic announced that v23.3 would deprecate allowing routes to be registered with duplicate names. If you see the following error, it is because of that change:
sanic.exceptions.ServerError: Duplicate route names detected: SomeApp.some_handler. You should rename one or more of them explicitly by using the
name
param, or changing the implicit name derived from the class and function name. For more details, please see https://sanic.dev/en/guide/release-notes/v23.3.html#duplicated-route-names-are-no-longer-allowed
If you are seeing this, you should opt-in to using explicit names for your routes.
.. column::
**BAD**
```python
app = Sanic("SomeApp")
@app.get("/")
@app.get("/foo")
async def handler(request: Request):
```
.. column::
**GOOD**
```python
app = Sanic("SomeApp")
@app.get("/", name="root")
@app.get("/foo", name="foo")
async def handler(request: Request):
```
Response cookies
Response cookies act as a dict
for compatibility purposes only. In version 24.3, all dict
methods will be removed and response cookies will be objects only.
Therefore, if you are using this pattern to set cookie properties, you will need to upgrade it before version 24.3.
resp = HTTPResponse()
resp.cookies["foo"] = "bar"
resp.cookies["foo"]["httponly"] = True
Instead, you should be using the add_cookie
method:
resp = HTTPResponse()
resp.add_cookie("foo", "bar", httponly=True)
Request cookies
Sanic has added support for reading duplicated cookie keys to be more in compliance with RFC specifications. To retain backwards compatibility, accessing a cookie value using __getitem__
will continue to work to fetch the first value sent. Therefore, in version 23.3 and prior versions this will be True
.
assert request.cookies["foo"] == "bar"
assert request.cookies.get("foo") == "bar"
Version 23.3 added getlist
assert request.cookies.getlist("foo") == ["bar"]
As stated above, the get
and getlist
methods are available similar to how they exist on other request properties (request.args
, request.form
, etc). Starting in v24.3, the __getitem__
method for cookies will work exactly like those properties. This means that __getitem__
will return a list of values.
Therefore, if you are relying upon this functionality to return only one value, you should upgrade to the following pattern before v24.3.
assert request.cookies["foo"] == ["bar"]
assert request.cookies.get("foo") == "bar"
assert request.cookies.getlist("foo") == ["bar"]
Thank you
Thank you to everyone that participated in this release: 👏
@ahopkins @ChihweiLHBird @deounix @Kludex @mbendiksen @prryplatypus @r0x0d @SaidBySolo @sjsadowski @stricaud @Tracyca209 @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.