Compare commits

...

6 Commits

Author SHA1 Message Date
Marcelo Trylesinski
479a2c0c89
Version 0.47.0 (#2937) 2026-05-14 06:20:53 -07:00
Marcelo Trylesinski
89347fd166
Add 7-day cooldown for dependency resolution via uv exclude-newer (#2936) 2026-05-12 15:48:51 +00:00
Marcelo Trylesinski
767315b38a
Drop unused contents/actions permissions from zizmor workflow (#2935) 2026-05-12 15:08:08 +02:00
dependabot[bot]
f25ee43e68
chore(deps): bump urllib3 from 2.6.3 to 2.7.0 (#2933) 2026-05-12 07:58:53 +02:00
Stefan Wójcik
8782666189
Fix typo in docs/deployment/index.md. (#2932) 2026-05-09 19:12:56 +02:00
Eugene Toder
ad5ff87c86
Treat fd=0 as a valid file descriptor with reload/workers (#2927)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
2026-04-30 20:26:13 +02:00
9 changed files with 61 additions and 21 deletions

View File

@ -14,8 +14,6 @@ jobs:
permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
contents: read # Only needed for private repos. Needed to clone the repo.
actions: read # Only needed for private repos. Needed for upload-sarif to read workflow run info.
steps:
- name: Checkout repository

View File

@ -82,7 +82,7 @@ The default process manager monitors the status of child processes and automatic
You can also manage child processes by sending specific signals to the main process. (Not supported on Windows.)
- `SIGHUP`: Work processeses are graceful restarted one after another. If you update the code, the new worker process will use the new code.
- `SIGHUP`: Work processes are graceful restarted one after another. If you update the code, the new worker process will use the new code.
- `SIGTTIN`: Increase the number of worker processes by one.
- `SIGTTOU`: Decrease the number of worker processes by one.

View File

@ -2,6 +2,20 @@
toc_depth: 2
---
## 0.47.0 (May 14, 2026)
### Added
* Add `ssl_context_factory` for custom `SSLContext` configuration (#2920)
### Changed
* Eagerly import the ASGI app in the parent process (#2919)
### Fixed
* Treat `fd=0` as a valid file descriptor with reload/workers (#2927)
## 0.46.0 (April 23, 2026)
### Added

View File

@ -82,7 +82,8 @@ docs = [
[tool.uv]
default-groups = ["dev", "docs"]
required-version = ">=0.8.6"
required-version = ">=0.9.17"
exclude-newer = "7 days"
[project.scripts]
uvicorn = "uvicorn.main:main"

View File

@ -553,6 +553,37 @@ def test_bind_fd_works_with_reload_or_workers(reload: bool, workers: int): # pr
fdsock.close()
@pytest.fixture
def stdin_socket() -> Iterator[socket.socket]: # pragma: py-win32
with closing(socket.socket(socket.AF_INET)) as sock:
sock.bind(("127.0.0.1", 0))
saved_stdin = os.dup(0)
os.dup2(sock.fileno(), 0)
try:
yield sock
finally:
os.dup2(saved_stdin, 0)
os.close(saved_stdin)
@pytest.mark.parametrize(
"reload, workers",
[
(True, 1),
(False, 2),
],
ids=["--reload=True --workers=1", "--reload=False --workers=2"],
)
@pytest.mark.skipif(sys.platform == "win32", reason="require unix-like system")
def test_bind_stdin_works_with_reload_or_workers(
reload: bool, workers: int, stdin_socket: socket.socket
): # pragma: py-win32
config = Config(app=asgi_app, fd=0, reload=reload, workers=workers)
config.load()
with closing(config.bind_socket()) as sock:
assert sock.getsockname() == stdin_socket.getsockname()
@pytest.mark.parametrize(
"reload, workers, expected",
[

View File

@ -142,17 +142,13 @@ async def test_limit_max_requests_jitter(
config = Config(
app=app, limit_max_requests=1, limit_max_requests_jitter=2, port=unused_tcp_port, http=http_protocol_cls
)
server = Server(config=config)
limit = server.limit_max_requests
assert limit is not None
assert 1 <= limit <= 3
task = asyncio.create_task(server.serve())
while not server.started:
await asyncio.sleep(0.01)
async with httpx.AsyncClient() as client:
for _ in range(limit + 1):
await client.get(f"http://127.0.0.1:{unused_tcp_port}")
await task
async with run_server(config) as server:
limit = server.limit_max_requests
assert limit is not None
assert 1 <= limit <= 3
async with httpx.AsyncClient() as client:
tasks = [client.get(f"http://127.0.0.1:{unused_tcp_port}") for _ in range(limit + 1)]
await asyncio.gather(*tasks)
assert f"Maximum request limit of {limit} exceeded. Terminating process." in caplog.text

6
uv.lock generated
View File

@ -1757,11 +1757,11 @@ wheels = [
[[package]]
name = "urllib3"
version = "2.6.3"
version = "2.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
]
[[package]]

View File

@ -1,5 +1,5 @@
from uvicorn.config import Config
from uvicorn.main import Server, main, run
__version__ = "0.46.0"
__version__ = "0.47.0"
__all__ = ["main", "run", "Config", "Server"]

View File

@ -537,7 +537,7 @@ class Config:
def bind_socket(self) -> socket.socket:
logger_args: list[str | int]
if self.uds: # pragma: py-win32
if self.uds is not None: # pragma: py-win32
path = self.uds
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
@ -552,7 +552,7 @@ class Config:
sock_name_format = "%s"
color_message = "Uvicorn running on " + click.style(sock_name_format, bold=True) + " (Press CTRL+C to quit)"
logger_args = [self.uds]
elif self.fd: # pragma: py-win32
elif self.fd is not None: # pragma: py-win32
sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM)
message = "Uvicorn running on socket %s (Press CTRL+C to quit)"
fd_name_format = "%s"