Skip to content

Commit 4352608

Browse files
committed
Merge branch 'master' into release
2 parents 9836f50 + 8f20651 commit 4352608

33 files changed

+285
-99
lines changed

.github/workflows/tests.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13']
19+
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
2020

2121
services:
2222
postgres:
@@ -49,7 +49,9 @@ jobs:
4949
strategy:
5050
fail-fast: false
5151
matrix:
52-
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13']
52+
# FIXME: skipping 3.9 due to issues with `psycopg2-binary`
53+
# python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
54+
python-version: ['3.10', '3.11', '3.12', '3.13']
5355

5456
steps:
5557
- uses: actions/checkout@v4
@@ -71,7 +73,7 @@ jobs:
7173
strategy:
7274
fail-fast: false
7375
matrix:
74-
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13']
76+
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
7577

7678
steps:
7779
- uses: actions/checkout@v4

CHANGES.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
Emmett changelog
22
================
33

4+
Version 2.7
5+
-----------
6+
7+
Released on April 11th 2025, codename Hopper
8+
9+
- Dropped Python 3.8 support
10+
- Use RSGI spec 1.5
11+
- Added `Response.stream` helper
12+
- Added `Pipe.on_stream` method
13+
- Added `stream` and `StreamPipe` helpers to `tools` module
14+
- Added Server-Sent-Events helper to `tools.sse`
15+
- Made `uvloop` dependency optional
16+
- Removed `uvicorn` dependency extra
17+
- Added experimental support for `psycopg` 3 in ORM
18+
419
Version 2.6
520
-----------
621

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ The *bloggy* example described in the [Tutorial](https://emmett.sh/docs/latest/t
4949

5050
## Status of the project
5151

52-
Emmett is production ready and is compatible with Python 3.8 and above versions.
52+
Emmett is production ready and is compatible with Python 3.9 and above versions.
5353

5454
Emmett follows a *semantic versioning* for its releases, with a `{major}.{minor}.{patch}` scheme for versions numbers, where:
5555

docs/deployment.md

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ Deployment
33

44
Depending on your setup and preferences, there are multiple ways to run Emmett applications. In this chapter, we'll try to document the most common ones.
55

6-
If you want to use an ASGI server not listed in this section, please refer to its documentation, remembering that your Emmett application object is the actual ASGI application (following spec version 3.0).
7-
86
Included server
97
---------------
108

11-
*Changed in version 2.5*
9+
*Changed in version 2.7*
1210

1311
Emmett comes with [Granian](https://github.com/emmett-framework/granian) as its HTTP server. In order to run your application in production you can just use the included `serve` command:
1412

@@ -22,35 +20,27 @@ You can inspect all the available options of the `serve` command using the `--he
2220
| port | 8000 | Bind port |
2321
| workers | 1 | Number of worker processes |
2422
| threads | 1 | Number of threads |
25-
| threading-mode | workers | Threading implementation (possible values: runtime,workers) |
23+
| blocking-trheads | 1 | Number of blocking threads |
24+
| runtime-mode | st | Runtime implementation (possible values: st,mt) |
2625
| interface | rsgi | Server interface (possible values: rsgi,asgi) |
2726
| http | auto | HTTP protocol version (possible values: auto,1,2) |
27+
| http-read-timeout | 10000 | HTTP read timeout (in milliseconds) |
2828
| ws/no-ws | ws | Enable/disable websockets support |
29-
| loop | auto | Loop implementation (possible values: auto,asyncio,uvloop) |
29+
| loop | auto | Loop implementation (possible values: auto,asyncio,rloop,uvloop) |
3030
| log-level | info | Logging level (possible values: debug,info,warning,error,critical) |
3131
| backlog | 2048 | Maximum connection queue |
32+
| backpressure | | Maximum number of requests to process concurrently |
3233
| ssl-certfile | | Path to SSL certificate file |
3334
| ssl-keyfile | | Path to SSL key file |
3435

35-
Uvicorn
36-
-------
37-
38-
*Changed in version 2.5*
39-
40-
In case you want to stick with a more popular option, Emmett also comes with included support for [Uvicorn](https://github.com/encode/uvicorn).
41-
42-
You can just use the `emmett[uvicorn]` extra during installation and rely on the `uvicorn` command to serve your application.
43-
44-
Gunicorn
45-
--------
46-
47-
The included server might suit most of the common demands, but whenever you need additional features, you can use [Gunicorn](https://gunicorn.org).
36+
Other ASGI servers
37+
------------------
4838

49-
Emmett includes a Gunicorn worker class allowing you to run ASGI applications with the Emmett's environment, while also giving you Gunicorn's fully-featured process management:
39+
*Changed in version 2.7*
5040

51-
gunicorn myapp:app -w 4 -k emmett.asgi.workers.EmmettWorker
41+
Since an Emmett application object is also an [ASGI](https://asgi.readthedocs.io/en/latest/) application, you can serve your project with any [ASGI compliant server](https://asgi.readthedocs.io/en/latest/implementations.html#servers).
5242

53-
This allows you to increase or decrease the number of worker processes on the fly, restart worker processes gracefully, or perform server upgrades without downtime.
43+
To serve your project with such servers, just refer to the specific server documentation an point it to your application object.
5444

5545
Docker
5646
------

docs/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Installation
44

55
So, how do you get Emmett on your computer quickly? There are many ways you could do that, but the most kick-ass method is virtualenv, so let’s have a look at that first.
66

7-
You will need Python version 3.8 or higher in order to get Emmett working.
7+
You will need Python version 3.9 or higher in order to get Emmett working.
88

99
virtualenv
1010
----------

docs/orm.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This is the list of the supported database engines, where we included the name u
1313
| Supported DBMS | adapter name | python driver |
1414
| --- | --- | --- |
1515
| SQLite | sqlite | |
16-
| PostgreSQL | postgres | psycopg2, pg8000, zxjdbc |
16+
| PostgreSQL | postgres | psycopg2, pyscopg 3 (experimental), pg8000, zxjdbc |
1717
| MySQL | mysql | pymysql, mysqldb |
1818
| MSSQL | mssql | pyodbc |
1919
| MongoDB | mongo | pymongo |

docs/pipeline.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,27 @@ async def foo_monthly(start, end):
235235
Requests and sockets
236236
--------------------
237237

238-
*Added in version 2.0*
238+
*Changed in version 2.7*
239239

240240
The `Pipe`'s methods we saw until now are commonly handled by Emmett on all the routes you define, without distinction between routes handling requests and routes handling websockets.
241241

242-
But since handling the former or the latter ones make a great difference in terms of *flow*, `Pipe`s objects also have two dedicated methods for websockets only, and in particular:
242+
But since handling the former or the latter ones make a great difference in terms of *flow*, `Pipe`s objects also have one dedicated method for requests and two dedicated methods for websockets only, in particular:
243243

244+
- on\_stream
244245
- on\_receive
245246
- on\_send
246247

247-
These two methods accepts only one argument: the message; and they will be called sequentially when receiving or sending messages. Here is an example:
248+
The `on_stream` method accepts no arguments and it will be called sequentially when a [response stream](./response#streaming-responses) starts. Here is an example:
249+
250+
```python
251+
from emmett import response
252+
253+
class MyStreamPipe(Pipe):
254+
def on_stream(self):
255+
response.headers["x-my-stream"] = "true"
256+
```
257+
258+
The `on_receive` and `on_send` methods accept only one argument instead: the message. These methods will be called sequentially when receiving or sending messages. Here is an example:
248259

249260
```python
250261
class WSPipe(Pipe):
@@ -284,6 +295,7 @@ To summarize, here is the complete table of methods available on Emmett pipes:
284295
| pipe | pipe\_request | pipe\_ws |
285296
| on\_pipe\_success | | |
286297
| on\_pipe\_failure | | |
298+
| | on\_stream | |
287299
| | | on\_receive |
288300
| | | on\_send |
289301

@@ -356,7 +368,7 @@ from emmett import Injector
356368
class DateInjector(Injector):
357369
namespace = "dates"
358370

359-
def pretty(d):
371+
def pretty(self, d):
360372
# your prettydate code
361373

362374
app.injectors = [DateInjector()]
@@ -376,7 +388,7 @@ You can also expose all the contents of your injectors without specifying the na
376388
from emmett import Injector
377389

378390
class CommonInjector(Injector):
379-
def pretty_date(d):
391+
def pretty_date(self, d):
380392
# your prettydate code
381393

382394
app.injectors = [CommonInjector()]

docs/response.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ async def response_aiter():
114114
return response.wrap_aiter(aiterator())
115115
```
116116

117+
> **Note:** both `wrap_iter` and `wrap_aiter` perform the iteration outside of the [pipeline](./pipeline). Whenever you need to stream contents and keep the pipeline open or use the global context, you should use the [stream utilities](#streaming-responses) instead.
118+
117119
### File responses
118120

119121
You can produce responses from file using two different methods in Emmett:
@@ -129,10 +131,74 @@ async def file(name):
129131

130132
@app.route("/io/<name:str>")
131133
async def io(name):
132-
with open(f"assets/{name}", "r") as f:
134+
with open(f"assets/{name}", "rb") as f:
133135
return response.wrap_io(f)
134136
```
135137

138+
Streaming responses
139+
-------------------
140+
141+
*New in version 2.7*
142+
143+
Emmett `Response` object provides a `stream` awaitable method in order to stream content from a generator while keeping the [pipeline](./pipeline) open:
144+
145+
```python
146+
async def iterator():
147+
for _ in range(3):
148+
yield b"hello"
149+
150+
@app.route()
151+
async def response_stream():
152+
response.status = 200
153+
return await response.stream(iterator())
154+
```
155+
156+
### Streaming utilities
157+
158+
On top of the `Response.stream` method, Emmett also provides a `StreamPipe` pipe and a `stream` decorator, so you can define your route function as the generator:
159+
160+
```python
161+
from emmett.tools import StreamPipe, stream
162+
163+
# the following routes behave the same
164+
@app.route(pipeline=[StreamPipe()])
165+
async def stream_1():
166+
for _ in range(3):
167+
yield b"hello"
168+
169+
@app.route()
170+
@stream()
171+
async def stream_2():
172+
for _ in range(3):
173+
yield b"hello"
174+
```
175+
176+
As you can't change the `Response` object directly in the route anymore, both `StreamPipe` and `stream` accept the same parameters to do so, specifically:
177+
178+
| parameter | type | default |
179+
| --- | --- | --- |
180+
| status | `int` | 200 |
181+
| headers | `dict[str, str]` | `{}` |
182+
| cookies | `dict[str, Any]` | `{}` |
183+
184+
> **Note:** while `status` will overwrite the existing value, both `headers` and `cookies` will be merged on top of the existing `Response` ones.
185+
186+
### Server-Sent Events
187+
188+
Emmett also provides some specific utilities to stream [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events):
189+
190+
```python
191+
from emmett.tools import sse
192+
193+
@app.route()
194+
@sse()
195+
async def sse_stream():
196+
for _ in range(3):
197+
yield sse.Event(data={"msg": "hello"})
198+
```
199+
200+
The `sse` decorator is based on the `stream` one but already configures some headers for you, like the `content-type` and caching. You can still use the same parameters of the `stream` decorator to customise the response.
201+
136202
Message flashing
137203
----------------
138204

docs/upgrading.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,44 @@ Just as a remind, you can update Emmett using *pip*:
1313
$ pip install -U emmett
1414
```
1515

16+
Version 2.7
17+
-----------
18+
19+
Emmett 2.7 introduces some changes you should be aware of, and some new features you might be interested into.
20+
21+
This release drops support for Python 3.8.
22+
23+
### Breaking changes
24+
25+
#### Serve command arguments
26+
27+
As Emmett 2.7 updates the minimum [Granian](https://github.com/emmett-framework/granian) dependency version, the previous `threading-mode` argument of the `serve` command is now moved to `runtime-mode`.
28+
In case you specified this argument in previous Emmett versions, please update your code using the following conversion table:
29+
30+
| previous value | new value |
31+
| --- | --- |
32+
| `--threading-mode runtime` | `--runtime-mode mt` |
33+
| `--threading-mode workers` | `--runtime-mode st` |
34+
35+
The serve command also includes some new params you might want to use. Please check the [relevant chapter](./deployment#included-server) of the documentation for more information.
36+
37+
#### Removed uvicorn extra dependency
38+
39+
Prior to 2.7, Emmett provided the `uvicorn` extra dependency. Given the additional work required to keep such dependency extras in sync with other projects release cycles, we no longer offer this facility.
40+
41+
You can still use Uvicorn to serve your Emmett application, you simply need to manage that dependency yourself. See also the [deployment chapter](./deployment) of the documentation.
42+
43+
#### Uvloop is now an optional dependency
44+
45+
Emmett 2.7 doesn't require [uvloop](https://github.com/MagicStack/uvloop) anymore. This dependency is now optional and gated under the `uvloop` extra. If you want to keep using uvloop, you can add it to your project dependencies or switch your Emmett dependency spec to `emmett[uvloop]`.
46+
47+
### New feautures
48+
49+
- [RSGI](https://github.com/emmett-framework/granian/blob/master/docs/spec/RSGI.md) spec 1.5
50+
- Response [streaming utilities](./response#streaming-responses)
51+
- First class support for [Server-Sent Events](./response#server-sent-events)
52+
- Pipes `on_stream` [method](./pipeline#requests-and-sockets)
53+
1654
Version 2.6
1755
-----------
1856

emmett/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.6.3"
1+
__version__ = "2.7.0"

0 commit comments

Comments
 (0)