Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-starlette for
openSUSE:Factory checked in at 2025-11-01 23:34:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-starlette (Old)
and /work/SRC/openSUSE:Factory/.python-starlette.new.1980 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-starlette"
Sat Nov 1 23:34:24 2025 rev:35 rq:1314383 version:0.49.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-starlette/python-starlette.changes
2025-10-18 14:37:06.980665912 +0200
+++
/work/SRC/openSUSE:Factory/.python-starlette.new.1980/python-starlette.changes
2025-11-01 23:34:26.362028297 +0100
@@ -1,0 +2,15 @@
+Wed Oct 29 09:14:32 UTC 2025 - Nico Krapp <[email protected]>
+
+- Update to 0.49.1 (fixes CVE-2025-62727, bsc#1252805)
+ * This release fixes a security vulnerability in the parsing logic of the
+ Range header in FileResponse. You can view the full security advisory:
+ GHSA-7f5h-v6xp-fcq8
+ * Optimize the HTTP ranges parsing logic
+- Update to 0.49.0
+ * Add encoding parameter to Config class
+ * Support multiple cookie headers in Request.cookies
+ * Use Literal type for WebSocketEndpoint encoding values
+ * Do not pollute exception context in Middleware when using
+ BaseHTTPMiddleware
+
+-------------------------------------------------------------------
Old:
----
starlette-0.48.0.tar.gz
New:
----
starlette-0.49.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-starlette.spec ++++++
--- /var/tmp/diff_new_pack.GgD7c0/_old 2025-11-01 23:34:27.114059741 +0100
+++ /var/tmp/diff_new_pack.GgD7c0/_new 2025-11-01 23:34:27.118059909 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-starlette
#
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -27,7 +27,7 @@
%{?sle15_python_module_pythons}
Name: python-starlette%{psuffix}
-Version: 0.48.0
+Version: 0.49.1
Release: 0
Summary: Lightweight ASGI framework/toolkit
License: BSD-3-Clause
++++++ starlette-0.48.0.tar.gz -> starlette-0.49.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/.github/workflows/main.yml
new/starlette-0.49.1/.github/workflows/main.yml
--- old/starlette-0.48.0/.github/workflows/main.yml 2025-09-13
10:39:11.000000000 +0200
+++ new/starlette-0.49.1/.github/workflows/main.yml 2025-10-28
18:31:53.000000000 +0100
@@ -20,7 +20,7 @@
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #
v5.0.0
- name: Install uv
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 #
v6.6.1
+ uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e #
v6.8.0
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/.github/workflows/publish.yml
new/starlette-0.49.1/.github/workflows/publish.yml
--- old/starlette-0.48.0/.github/workflows/publish.yml 2025-09-13
10:39:11.000000000 +0200
+++ new/starlette-0.49.1/.github/workflows/publish.yml 2025-10-28
18:31:53.000000000 +0100
@@ -4,6 +4,7 @@
push:
tags:
- '*'
+ workflow_dispatch:
jobs:
build:
@@ -13,7 +14,7 @@
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #
v5.0.0
- name: Install uv
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 #
v6.6.1
+ uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e #
v6.8.0
with:
python-version: "3.11"
enable-cache: true
@@ -39,6 +40,7 @@
pypi-publish:
runs-on: ubuntu-latest
needs: build
+ if: success() && startsWith(github.ref, 'refs/tags/')
permissions:
id-token: write
@@ -77,12 +79,39 @@
git config user.name github-actions[bot]
git config user.email
41898282+github-actions[bot]@users.noreply.github.com
- - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c #
v6.0.0
+ - name: Install uv
+ uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e #
v6.8.0
with:
python-version: "3.12"
+ enable-cache: true
- name: Install dependencies
run: scripts/install
- name: Publish documentation π to GitHub Pages
- run: mkdocs gh-deploy --force
+ run: uv run mkdocs gh-deploy --force
+
+ docs-cloudflare:
+ runs-on: ubuntu-latest
+ needs: build
+
+ environment:
+ name: cloudflare
+ url: https://starlette.dev
+
+ steps:
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #
v5.0.0
+ - name: Download artifacts
+ uses:
actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
+ with:
+ name: documentation
+ path: site/
+
+ - uses:
cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
+ with:
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ command: >
+ pages deploy ./site
+ --project-name starlette
+ --commit-hash ${{ github.sha }}
+ --branch main
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/CITATION.cff
new/starlette-0.49.1/CITATION.cff
--- old/starlette-0.48.0/CITATION.cff 2025-09-13 10:39:11.000000000 +0200
+++ new/starlette-0.49.1/CITATION.cff 2025-10-28 18:31:53.000000000 +0100
@@ -15,7 +15,7 @@
family-names: Christie
email: [email protected]
repository-code: "https://github.com/Kludex/starlette"
-url: "https://www.starlette.io/"
+url: "https://starlette.dev/"
abstract: Starlette is an ASGI web framework for Python.
keywords:
- asgi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/README.md
new/starlette-0.49.1/README.md
--- old/starlette-0.48.0/README.md 2025-09-13 10:39:11.000000000 +0200
+++ new/starlette-0.49.1/README.md 2025-10-28 18:31:53.000000000 +0100
@@ -15,10 +15,11 @@
[](https://github.com/Kludex/starlette/actions)
[](https://pypi.python.org/pypi/starlette)
[](https://pypi.org/project/starlette)
+[](https://discord.gg/RxKUF5JuHs)
---
-**Documentation**: <a href="https://www.starlette.io/"
target="_blank">https://www.starlette.io</a>
+**Documentation**: <a href="https://starlette.dev/"
target="_blank">https://starlette.dev</a>
**Source Code**: <a href="https://github.com/Kludex/starlette"
target="_blank">https://github.com/Kludex/starlette</a>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/docs/config.md
new/starlette-0.49.1/docs/config.md
--- old/starlette-0.48.0/docs/config.md 2025-09-13 10:39:11.000000000 +0200
+++ new/starlette-0.49.1/docs/config.md 2025-10-28 18:31:53.000000000 +0100
@@ -120,6 +120,18 @@
ENVIRONMENT = config('ENVIRONMENT') # lookups APP_ENVIRONMENT, raises KeyError
as variable is not defined
```
+## Custom encoding for environment files
+
+By default, Starlette reads environment files using UTF-8 encoding.
+You can specify a different encoding by setting `encoding` argument.
+
+```python title="myproject/settings.py"
+from starlette.config import Config
+
+# Using custom encoding for .env file
+config = Config(".env", encoding="latin-1")
+```
+
## A full example
Structuring large applications can be complex. You need proper separation of
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/docs/contributing.md
new/starlette-0.49.1/docs/contributing.md
--- old/starlette-0.48.0/docs/contributing.md 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/docs/contributing.md 2025-10-28 18:31:53.000000000
+0100
@@ -166,5 +166,3 @@
Once created this release will be automatically uploaded to PyPI.
-If something goes wrong with the PyPI job the release can be published using
the
-`scripts/publish` script.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/docs/exceptions.md
new/starlette-0.49.1/docs/exceptions.md
--- old/starlette-0.48.0/docs/exceptions.md 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/docs/exceptions.md 2025-10-28 18:31:53.000000000
+0100
@@ -99,7 +99,7 @@
}
```
-It's important to notice that in case a
[`BackgroundTask`](https://www.starlette.io/background/) raises an exception,
+It's important to notice that in case a [`BackgroundTask`](background.md)
raises an exception,
it will be handled by the `handle_error` function, but at that point, the
response was already sent. In other words,
the response created by `handle_error` will be discarded. In case the error
happens before the response was sent, then
it will use the response object - in the above example, the returned
`JSONResponse`.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/docs/index.md
new/starlette-0.49.1/docs/index.md
--- old/starlette-0.48.0/docs/index.md 2025-09-13 10:39:11.000000000 +0200
+++ new/starlette-0.49.1/docs/index.md 2025-10-28 18:31:53.000000000 +0100
@@ -15,11 +15,14 @@
<a href="https://pypi.org/project/starlette" target="_blank">
<img
src="https://img.shields.io/pypi/pyversions/starlette.svg?color=%2334D058"
alt="Supported Python versions">
</a>
+<a href="https://discord.gg/RxKUF5JuHs">
+ <img
src="https://img.shields.io/discord/1051468649518616576?logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2"
alt="Discord">
+</a>
</p>
---
-**Documentation**: <a href="https://www.starlette.io/"
target="_blank">https://www.starlette.io</a>
+**Documentation**: <a href="https://starlette.dev/"
target="_blank">https://starlette.dev</a>
**Source Code**: <a href="https://github.com/Kludex/starlette"
target="_blank">https://github.com/Kludex/starlette</a>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/docs/overrides/main.html
new/starlette-0.49.1/docs/overrides/main.html
--- old/starlette-0.48.0/docs/overrides/main.html 1970-01-01
01:00:00.000000000 +0100
+++ new/starlette-0.49.1/docs/overrides/main.html 2025-10-28
18:31:53.000000000 +0100
@@ -0,0 +1,12 @@
+{% extends "base.html" %}
+
+{% block extrahead %}
+ {{ super() }}
+ <script>
+ // Redirect starlette.io to starlette.dev
+ if (window.location.hostname === 'www.starlette.io' ||
window.location.hostname === 'starlette.io') {
+ const newUrl =
window.location.href.replace(/^https?:\/\/(www\.)?starlette\.io/,
'https://starlette.dev');
+ window.location.replace(newUrl);
+ }
+ </script>
+{% endblock %}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/docs/release-notes.md
new/starlette-0.49.1/docs/release-notes.md
--- old/starlette-0.48.0/docs/release-notes.md 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/docs/release-notes.md 2025-10-28 18:31:53.000000000
+0100
@@ -2,6 +2,28 @@
toc_depth: 2
---
+## 0.49.1 (October 28, 2025)
+
+This release fixes a security vulnerability in the parsing logic of the
`Range` header in `FileResponse`.
+
+You can view the full security advisory:
[GHSA-7f5h-v6xp-fcq8](https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8)
+
+#### Fixed
+
+* Optimize the HTTP ranges parsing logic
[4ea6e22b489ec388d6004cfbca52dd5b147127c5](https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5)
+
+## 0.49.0 (October 28, 2025)
+
+#### Added
+
+* Add `encoding` parameter to `Config` class
[#2996](https://github.com/Kludex/starlette/pull/2996).
+* Support multiple cookie headers in `Request.cookies`
[#3029](https://github.com/Kludex/starlette/pull/3029).
+* Use `Literal` type for `WebSocketEndpoint` encoding values
[#3027](https://github.com/Kludex/starlette/pull/3027).
+
+#### Changed
+
+* Do not pollute exception context in `Middleware` when using
`BaseHTTPMiddleware` [#2976](https://github.com/Kludex/starlette/pull/2976).
+
## 0.48.0 (September 13, 2025)
#### Added
@@ -731,7 +753,7 @@
### Removed
* `UJSONResponse` was removed (this change was intended to be included in
0.14.0). Please see the
-
[documentation](https://www.starlette.io/responses/#custom-json-serialization)
for how to
+ [documentation](https://starlette.dev/responses/#custom-json-serialization)
for how to
implement responses using custom JSON serialization -
[#1074](https://github.com/Kludex/starlette/pull/1047).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/docs/sponsorship.md
new/starlette-0.49.1/docs/sponsorship.md
--- old/starlette-0.48.0/docs/sponsorship.md 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/docs/sponsorship.md 2025-10-28 18:31:53.000000000
+0100
@@ -59,8 +59,8 @@
## Sponsorship Tiers π
-<div style="display: grid; grid-template-columns: repeat(auto-fit,
minmax(250px, 1fr)); gap: 1.5rem; margin: 2rem 0;">
- <div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius:
6px; background: #fff; display: flex; flex-direction: column;">
+<div style="display: grid; grid-template-columns: repeat(auto-fit,
minmax(250px, 1fr)); gap: 1.5rem; margin: 2rem 0; color: #fff">
+ <div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius:
6px; background: linear-gradient(135deg, #6e5494, #24292e); display: flex;
flex-direction: column;">
<h3 style="color: #cd7f32;">π₯ Bronze Sponsor</h3>
<div style="font-size: 1.5em; margin: 1rem 0;">$100<span
style="font-size: 0.6em;">/month</span></div>
<ul style="list-style: none; padding: 0; margin-bottom: 1rem;
min-height: 90px;">
@@ -74,7 +74,7 @@
</a>
</div>
</div>
- <div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius:
6px; background: #fff; display: flex; flex-direction: column;">
+ <div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius:
6px; background: linear-gradient(135deg, #6e5494, #24292e); display: flex;
flex-direction: column;">
<h3 style="color: #c0c0c0;">π₯ Silver Sponsor</h3>
<div style="font-size: 1.5em; margin: 1rem 0;">$250<span
style="font-size: 0.6em;">/month</span></div>
<ul style="list-style: none; padding: 0; margin-bottom: 1rem;
min-height: 90px;">
@@ -88,7 +88,7 @@
</a>
</div>
</div>
- <div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius:
6px; background: #fff; position: relative; overflow: hidden; display: flex;
flex-direction: column;">
+ <div style="padding: 1.5rem; border: 1px solid #e1e4e8; border-radius:
6px; background: linear-gradient(135deg, #6e5494, #24292e); position: relative;
overflow: hidden; display: flex; flex-direction: column;">
<div style="position: absolute; top: 10px; right: -25px; background:
#238636; color: white; padding: 5px 30px; transform: rotate(45deg);">
Popular
</div>
@@ -148,7 +148,7 @@
## Alternative Sponsorship Platforms
-<div style="background: #f6f8fa; padding: 1.5rem; border-radius: 8px; margin:
2rem 0;">
+<div style="background: linear-gradient(135deg,
var(--md-default-fg-color--lighter), var(--md-default-fg-color--lightest));
padding: 1.5rem; border-radius: 8px; margin: 2rem 0;">
<h3>π’ We Want Your Input!</h3>
<p>We are currently evaluating whether to expand our sponsorship options
beyond GitHub Sponsors. If your company would be interested in sponsoring
Starlette and Uvicorn but prefers to use a different platform (e.g., Open
Collective, direct invoicing), please let us know!</p>
<p>Your feedback is invaluable in helping us make sponsorship as
accessible as possible. Share your thoughts by:</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/docs/third-party-packages.md
new/starlette-0.49.1/docs/third-party-packages.md
--- old/starlette-0.48.0/docs/third-party-packages.md 2025-09-13
10:39:11.000000000 +0200
+++ new/starlette-0.49.1/docs/third-party-packages.md 2025-10-28
18:31:53.000000000 +0100
@@ -252,7 +252,7 @@
<a href="https://github.com/DavidVentura/starlette-static-resources"
target="_blank">GitHub</a>
-Allows mounting [package
resources](https://docs.python.org/3/library/importlib.resources.html#module-importlib.resources)
for static data, similar to
[StaticFiles](https://www.starlette.io/staticfiles/).
+Allows mounting [package
resources](https://docs.python.org/3/library/importlib.resources.html#module-importlib.resources)
for static data, similar to [StaticFiles](staticfiles.md).
### Sentry
@@ -266,4 +266,4 @@
<a href="https://github.com/posit-dev/py-shiny" target="_blank">GitHub</a> |
<a href="https://shiny.posit.co/py/" target="_blank">Documentation</a>
-Leveraging Starlette and asyncio, Shiny allows developers to create effortless
Python web applications using the power of reactive programming. Shiny
eliminates the hassle of manual state management, automatically determining the
best execution path for your app at runtime while simultaneously minimizing
re-rendering. This means that Shiny can support everything from the simplest
dashboard to full-featured web apps.
\ No newline at end of file
+Leveraging Starlette and asyncio, Shiny allows developers to create effortless
Python web applications using the power of reactive programming. Shiny
eliminates the hassle of manual state management, automatically determining the
best execution path for your app at runtime while simultaneously minimizing
re-rendering. This means that Shiny can support everything from the simplest
dashboard to full-featured web apps.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/mkdocs.yml
new/starlette-0.49.1/mkdocs.yml
--- old/starlette-0.48.0/mkdocs.yml 2025-09-13 10:39:11.000000000 +0200
+++ new/starlette-0.49.1/mkdocs.yml 2025-10-28 18:31:53.000000000 +0100
@@ -1,6 +1,6 @@
site_name: Starlette
site_description: The little ASGI library that shines.
-site_url: https://www.starlette.io
+site_url: https://starlette.dev
repo_name: Kludex/starlette
repo_url: https://github.com/Kludex/starlette
@@ -62,6 +62,17 @@
analytics:
provider: google
property: G-Z37GTYBR6M
+ social:
+ - icon: fontawesome/brands/github-alt
+ link: https://github.com/Kludex/starlette
+ - icon: fontawesome/brands/discord
+ link: https://discord.com/invite/RxKUF5JuHs
+ - icon: fontawesome/brands/twitter
+ link: https://x.com/marcelotryle
+ - icon: fontawesome/brands/linkedin
+ link: https://www.linkedin.com/in/marcelotryle
+ - icon: fontawesome/solid/globe
+ link: https://fastapiexpert.com
markdown_extensions:
- attr_list
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/pyproject.toml
new/starlette-0.49.1/pyproject.toml
--- old/starlette-0.48.0/pyproject.toml 2025-09-13 10:39:11.000000000 +0200
+++ new/starlette-0.49.1/pyproject.toml 2025-10-28 18:31:53.000000000 +0100
@@ -8,17 +8,19 @@
description = "The little ASGI library that shines."
readme = "README.md"
license = "BSD-3-Clause"
+license-files = ["LICENSE.md"]
requires-python = ">=3.9"
authors = [
{ name = "Tom Christie", email = "[email protected]" },
- { name = "Marcelo Trylesinski", email = "[email protected]" }
+]
+maintainers = [
+ { name = "Marcelo Trylesinski", email = "[email protected]" },
]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Framework :: AnyIO",
"Intended Audience :: Developers",
- "License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
@@ -70,8 +72,8 @@
[project.urls]
Homepage = "https://github.com/Kludex/starlette"
-Documentation = "https://www.starlette.io/"
-Changelog = "https://www.starlette.io/release-notes/"
+Documentation = "https://starlette.dev/"
+Changelog = "https://starlette.dev/release-notes/"
Funding = "https://github.com/sponsors/Kludex"
Source = "https://github.com/Kludex/starlette"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/scripts/README.md
new/starlette-0.49.1/scripts/README.md
--- old/starlette-0.48.0/scripts/README.md 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/scripts/README.md 2025-10-28 18:31:53.000000000
+0100
@@ -6,6 +6,5 @@
* `scripts/check` - Run the code linting, checking that it passes.
* `scripts/coverage` - Check that code coverage is complete.
* `scripts/build` - Build source and wheel packages.
-* `scripts/publish` - Publish the latest version to PyPI.
Styled after GitHub's ["Scripts to Rule Them
All"](https://github.com/github/scripts-to-rule-them-all).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/scripts/publish
new/starlette-0.49.1/scripts/publish
--- old/starlette-0.48.0/scripts/publish 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/scripts/publish 1970-01-01 01:00:00.000000000
+0100
@@ -1,20 +0,0 @@
-#!/bin/sh -e
-
-VERSION_FILE="starlette/__init__.py"
-
-if [ ! -z "$GITHUB_ACTIONS" ]; then
- git config --local user.email
"41898282+github-actions[bot]@users.noreply.github.com"
- git config --local user.name "GitHub Action"
-
- VERSION=`grep __version__ ${VERSION_FILE} | grep -o '[0-9][^"]*'`
-
- if [ "refs/tags/${VERSION}" != "${GITHUB_REF}" ] ; then
- echo "GitHub Ref '${GITHUB_REF}' did not match package version
'${VERSION}'"
- exit 1
- fi
-fi
-
-set -x
-
-uv run twine upload dist/*
-uv run mkdocs gh-deploy --force
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/starlette/__init__.py
new/starlette-0.49.1/starlette/__init__.py
--- old/starlette-0.48.0/starlette/__init__.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/starlette/__init__.py 2025-10-28 18:31:53.000000000
+0100
@@ -1 +1 @@
-__version__ = "0.48.0"
+__version__ = "0.49.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/starlette/applications.py
new/starlette-0.49.1/starlette/applications.py
--- old/starlette-0.48.0/starlette/applications.py 2025-09-13
10:39:11.000000000 +0200
+++ new/starlette-0.49.1/starlette/applications.py 2025-10-28
18:31:53.000000000 +0100
@@ -166,7 +166,7 @@
def exception_handler(self, exc_class_or_status_code: int |
type[Exception]) -> Callable: # type: ignore[type-arg]
warnings.warn(
"The `exception_handler` decorator is deprecated, and will be
removed in version 1.0.0. "
- "Refer to https://www.starlette.io/exceptions/ for the recommended
approach.",
+ "Refer to https://starlette.dev/exceptions/ for the recommended
approach.",
DeprecationWarning,
)
@@ -192,7 +192,7 @@
"""
warnings.warn(
"The `route` decorator is deprecated, and will be removed in
version 1.0.0. "
- "Refer to https://www.starlette.io/routing/ for the recommended
approach.",
+ "Refer to https://starlette.dev/routing/ for the recommended
approach.",
DeprecationWarning,
)
@@ -218,7 +218,7 @@
"""
warnings.warn(
"The `websocket_route` decorator is deprecated, and will be
removed in version 1.0.0. "
- "Refer to https://www.starlette.io/routing/#websocket-routing for
the recommended approach.",
+ "Refer to https://starlette.dev/routing/#websocket-routing for the
recommended approach.",
DeprecationWarning,
)
@@ -238,7 +238,7 @@
"""
warnings.warn(
"The `middleware` decorator is deprecated, and will be removed in
version 1.0.0. "
- "Refer to https://www.starlette.io/middleware/#using-middleware
for recommended approach.",
+ "Refer to https://starlette.dev/middleware/#using-middleware for
recommended approach.",
DeprecationWarning,
)
assert middleware_type == "http", 'Currently only middleware("http")
is supported.'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/starlette/config.py
new/starlette-0.49.1/starlette/config.py
--- old/starlette-0.48.0/starlette/config.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/starlette/config.py 2025-10-28 18:31:53.000000000
+0100
@@ -52,6 +52,7 @@
env_file: str | Path | None = None,
environ: Mapping[str, str] = environ,
env_prefix: str = "",
+ encoding: str = "utf-8",
) -> None:
self.environ = environ
self.env_prefix = env_prefix
@@ -60,7 +61,7 @@
if not os.path.isfile(env_file):
warnings.warn(f"Config file '{env_file}' not found.")
else:
- self.file_values = self._read_file(env_file)
+ self.file_values = self._read_file(env_file, encoding)
@overload
def __call__(self, key: str, *, default: None) -> str | None: ...
@@ -107,9 +108,9 @@
return self._perform_cast(key, default, cast)
raise KeyError(f"Config '{key}' is missing, and has no default.")
- def _read_file(self, file_name: str | Path) -> dict[str, str]:
+ def _read_file(self, file_name: str | Path, encoding: str) -> dict[str,
str]:
file_values: dict[str, str] = {}
- with open(file_name) as input_file:
+ with open(file_name, encoding=encoding) as input_file:
for line in input_file.readlines():
line = line.strip()
if "=" in line and not line.startswith("#"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/starlette/endpoints.py
new/starlette-0.49.1/starlette/endpoints.py
--- old/starlette-0.48.0/starlette/endpoints.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/starlette/endpoints.py 2025-10-28 18:31:53.000000000
+0100
@@ -2,7 +2,7 @@
import json
from collections.abc import Generator
-from typing import Any, Callable
+from typing import Any, Callable, Literal
from starlette import status
from starlette._utils import is_async_callable
@@ -52,7 +52,7 @@
class WebSocketEndpoint:
- encoding: str | None = None # May be "text", "bytes", or "json".
+ encoding: Literal["text", "bytes", "json"] | None = None
def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:
assert scope["type"] == "websocket"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/starlette/middleware/base.py
new/starlette-0.49.1/starlette/middleware/base.py
--- old/starlette-0.48.0/starlette/middleware/base.py 2025-09-13
10:39:11.000000000 +0200
+++ new/starlette-0.49.1/starlette/middleware/base.py 2025-10-28
18:31:53.000000000 +0100
@@ -156,7 +156,16 @@
if app_exc is not None:
nonlocal exception_already_raised
exception_already_raised = True
- raise app_exc
+ # Prevent `anyio.EndOfStream` from polluting app exception
context.
+ # If both cause and context are None then the context is
suppressed
+ # and `anyio.EndOfStream` is not present in the exception
traceback.
+ # If exception cause is not None then it is propagated with
+ # reraising here.
+ # If exception has no cause but has context set then the
context is
+ # propagated as a cause with the reraise. This is
necessary in order
+ # to prevent `anyio.EndOfStream` from polluting the
exception
+ # context.
+ raise app_exc from app_exc.__cause__ or app_exc.__context__
raise RuntimeError("No response returned.")
assert message["type"] == "http.response.start"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/starlette/requests.py
new/starlette-0.49.1/starlette/requests.py
--- old/starlette-0.48.0/starlette/requests.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/starlette/requests.py 2025-10-28 18:31:53.000000000
+0100
@@ -140,10 +140,11 @@
def cookies(self) -> dict[str, str]:
if not hasattr(self, "_cookies"):
cookies: dict[str, str] = {}
- cookie_header = self.headers.get("cookie")
+ cookie_headers = self.headers.getlist("cookie")
+
+ for header in cookie_headers:
+ cookies.update(cookie_parser(header))
- if cookie_header:
- cookies = cookie_parser(cookie_header)
self._cookies = cookies
return self._cookies
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/starlette/responses.py
new/starlette-0.49.1/starlette/responses.py
--- old/starlette-0.48.0/starlette/responses.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/starlette/responses.py 2025-10-28 18:31:53.000000000
+0100
@@ -4,7 +4,6 @@
import http.cookies
import json
import os
-import re
import stat
import sys
import warnings
@@ -291,9 +290,6 @@
self.max_size = max_size
-_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)")
-
-
class FileResponse(Response):
chunk_size = 64 * 1024
@@ -455,8 +451,8 @@
def _should_use_range(self, http_if_range: str) -> bool:
return http_if_range == self.headers["last-modified"] or http_if_range
== self.headers["etag"]
- @staticmethod
- def _parse_range_header(http_range: str, file_size: int) ->
list[tuple[int, int]]:
+ @classmethod
+ def _parse_range_header(cls, http_range: str, file_size: int) ->
list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
@@ -468,14 +464,7 @@
if units != "bytes":
raise MalformedRangeHeader("Only support bytes range")
- ranges = [
- (
- int(_[0]) if _[0] else file_size - int(_[1]),
- int(_[1]) + 1 if _[0] and _[1] and int(_[1]) < file_size else
file_size,
- )
- for _ in _RANGE_PATTERN.findall(range_)
- if _ != ("", "")
- ]
+ ranges = cls._parse_ranges(range_, file_size)
if len(ranges) == 0:
raise MalformedRangeHeader("Range header: range must be requested")
@@ -507,6 +496,35 @@
return result
+ @classmethod
+ def _parse_ranges(cls, range_: str, file_size: int) -> list[tuple[int,
int]]:
+ ranges: list[tuple[int, int]] = []
+
+ for part in range_.split(","):
+ part = part.strip()
+
+ # If the range is empty or a single dash, we ignore it.
+ if not part or part == "-":
+ continue
+
+ # If the range is not in the format "start-end", we ignore it.
+ if "-" not in part:
+ continue
+
+ start_str, end_str = part.split("-", 1)
+ start_str = start_str.strip()
+ end_str = end_str.strip()
+
+ try:
+ start = int(start_str) if start_str else file_size -
int(end_str)
+ end = int(end_str) + 1 if start_str and end_str and
int(end_str) < file_size else file_size
+ ranges.append((start, end))
+ except ValueError:
+ # If the range is not numeric, we ignore it.
+ continue
+
+ return ranges
+
def generate_multipart(
self,
ranges: Sequence[tuple[int, int]],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/starlette/routing.py
new/starlette-0.49.1/starlette/routing.py
--- old/starlette-0.48.0/starlette/routing.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/starlette/routing.py 2025-10-28 18:31:53.000000000
+0100
@@ -601,7 +601,7 @@
warnings.warn(
"The on_startup and on_shutdown parameters are deprecated, and
they "
"will be removed on version 1.0. Use the lifespan parameter
instead. "
- "See more about it on https://www.starlette.io/lifespan/.",
+ "See more about it on https://starlette.dev/lifespan/.",
DeprecationWarning,
)
if lifespan:
@@ -818,7 +818,7 @@
"""
warnings.warn(
"The `route` decorator is deprecated, and will be removed in
version 1.0.0."
- "Refer to https://www.starlette.io/routing/#http-routing for the
recommended approach.",
+ "Refer to https://starlette.dev/routing/#http-routing for the
recommended approach.",
DeprecationWarning,
)
@@ -844,7 +844,7 @@
"""
warnings.warn(
"The `websocket_route` decorator is deprecated, and will be
removed in version 1.0.0. Refer to "
- "https://www.starlette.io/routing/#websocket-routing for the
recommended approach.",
+ "https://starlette.dev/routing/#websocket-routing for the
recommended approach.",
DeprecationWarning,
)
@@ -865,7 +865,7 @@
def on_event(self, event_type: str) -> Callable: # type: ignore[type-arg]
warnings.warn(
"The `on_event` decorator is deprecated, and will be removed in
version 1.0.0. "
- "Refer to https://www.starlette.io/lifespan/ for recommended
approach.",
+ "Refer to https://starlette.dev/lifespan/ for recommended
approach.",
DeprecationWarning,
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/tests/middleware/test_base.py
new/starlette-0.49.1/tests/middleware/test_base.py
--- old/starlette-0.48.0/tests/middleware/test_base.py 2025-09-13
10:39:11.000000000 +0200
+++ new/starlette-0.49.1/tests/middleware/test_base.py 2025-10-28
18:31:53.000000000 +0100
@@ -1243,3 +1243,62 @@
assert len(events) == 2
assert events[0]["type"] == "http.response.start"
assert events[1]["type"] == "http.response.pathsend"
+
+
+def test_error_context_propagation(test_client_factory: TestClientFactory) ->
None:
+ class PassthroughMiddleware(BaseHTTPMiddleware):
+ async def dispatch(
+ self,
+ request: Request,
+ call_next: RequestResponseEndpoint,
+ ) -> Response:
+ return await call_next(request)
+
+ def exception_without_context(request: Request) -> None:
+ raise Exception("Exception")
+
+ def exception_with_context(request: Request) -> None:
+ try:
+ raise Exception("Inner exception")
+ except Exception:
+ raise Exception("Outer exception")
+
+ def exception_with_cause(request: Request) -> None:
+ try:
+ raise Exception("Inner exception")
+ except Exception as e:
+ raise Exception("Outer exception") from e
+
+ app = Starlette(
+ routes=[
+ Route("/exception-without-context",
endpoint=exception_without_context),
+ Route("/exception-with-context", endpoint=exception_with_context),
+ Route("/exception-with-cause", endpoint=exception_with_cause),
+ ],
+ middleware=[Middleware(PassthroughMiddleware)],
+ )
+ client = test_client_factory(app)
+
+ # For exceptions without context the context is filled with the
`anyio.EndOfStream`
+ # but it is suppressed therefore not propagated to traceback.
+ with pytest.raises(Exception) as ctx:
+ client.get("/exception-without-context")
+ assert str(ctx.value) == "Exception"
+ assert ctx.value.__cause__ is None
+ assert ctx.value.__context__ is not None
+ assert ctx.value.__suppress_context__ is True
+
+ # For exceptions with context the context is propagated as a cause to avoid
+ # `anyio.EndOfStream` error from overwriting it.
+ with pytest.raises(Exception) as ctx:
+ client.get("/exception-with-context")
+ assert str(ctx.value) == "Outer exception"
+ assert ctx.value.__cause__ is not None
+ assert str(ctx.value.__cause__) == "Inner exception"
+
+ # For exceptions with cause check that it gets correctly propagated.
+ with pytest.raises(Exception) as ctx:
+ client.get("/exception-with-cause")
+ assert str(ctx.value) == "Outer exception"
+ assert ctx.value.__cause__ is not None
+ assert str(ctx.value.__cause__) == "Inner exception"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/tests/test_config.py
new/starlette-0.49.1/tests/test_config.py
--- old/starlette-0.48.0/tests/test_config.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/tests/test_config.py 2025-10-28 18:31:53.000000000
+0100
@@ -140,3 +140,10 @@
with pytest.raises(KeyError):
config.get("ENVIRONMENT")
+
+
+def test_config_with_encoding(tmpdir: Path) -> None:
+ path = tmpdir / ".env"
+ path.write_text("MESSAGE=Hello δΈη\n", encoding="utf-8")
+ config = Config(path, encoding="utf-8")
+ assert config.get("MESSAGE") == "Hello δΈη"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/tests/test_requests.py
new/starlette-0.49.1/tests/test_requests.py
--- old/starlette-0.48.0/tests/test_requests.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/tests/test_requests.py 2025-10-28 18:31:53.000000000
+0100
@@ -450,6 +450,19 @@
assert result["cookies"] == expected
+def test_multiple_cookie_headers(test_client_factory: TestClientFactory) ->
None:
+ async def app(scope: Scope, receive: Receive, send: Send) -> None:
+ scope["headers"] = [(b"cookie", b"a=abc"), (b"cookie", b"b=def"),
(b"cookie", b"c=ghi")]
+ request = Request(scope, receive)
+ response = JSONResponse({"cookies": request.cookies})
+ await response(scope, receive, send)
+
+ client = test_client_factory(app)
+ response = client.get("/")
+ result = response.json()
+ assert result["cookies"] == {"a": "abc", "b": "def", "c": "ghi"}
+
+
def test_chunked_encoding(test_client_factory: TestClientFactory) -> None:
async def app(scope: Scope, receive: Receive, send: Send) -> None:
request = Request(scope, receive)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/tests/test_responses.py
new/starlette-0.49.1/tests/test_responses.py
--- old/starlette-0.48.0/tests/test_responses.py 2025-09-13
10:39:11.000000000 +0200
+++ new/starlette-0.49.1/tests/test_responses.py 2025-10-28
18:31:53.000000000 +0100
@@ -798,6 +798,34 @@
]
+def test_file_response_range_without_dash(file_response_client: TestClient) ->
None:
+ response = file_response_client.get("/", headers={"Range": "bytes=100,
0-50"})
+ assert response.status_code == 206
+ assert response.headers["content-range"] == f"bytes
0-50/{len(README.encode('utf8'))}"
+
+
+def test_file_response_range_empty_start_and_end(file_response_client:
TestClient) -> None:
+ response = file_response_client.get("/", headers={"Range": "bytes= - ,
0-50"})
+ assert response.status_code == 206
+ assert response.headers["content-range"] == f"bytes
0-50/{len(README.encode('utf8'))}"
+
+
+def test_file_response_range_ignore_non_numeric(file_response_client:
TestClient) -> None:
+ response = file_response_client.get("/", headers={"Range": "bytes=abc-def,
0-50"})
+ assert response.status_code == 206
+ assert response.headers["content-range"] == f"bytes
0-50/{len(README.encode('utf8'))}"
+
+
+def test_file_response_suffix_range(file_response_client: TestClient) -> None:
+ # Test suffix range (last N bytes) - line 523 with empty start_str
+ response = file_response_client.get("/", headers={"Range": "bytes=-100"})
+ assert response.status_code == 206
+ file_size = len(README.encode("utf8"))
+ assert response.headers["content-range"] == f"bytes {file_size -
100}-{file_size - 1}/{file_size}"
+ assert response.headers["content-length"] == "100"
+ assert response.content == README.encode("utf8")[-100:]
+
+
@pytest.mark.anyio
async def test_file_response_multi_small_chunk_size(readme_file: Path) -> None:
class SmallChunkSizeFileResponse(FileResponse):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.48.0/tests/test_status.py
new/starlette-0.49.1/tests/test_status.py
--- old/starlette-0.48.0/tests/test_status.py 2025-09-13 10:39:11.000000000
+0200
+++ new/starlette-0.49.1/tests/test_status.py 2025-10-28 18:31:53.000000000
+0100
@@ -21,3 +21,11 @@
getattr(importlib.import_module("starlette.status"), constant)
assert len(record) == 1
assert msg in str(record.list[0])
+
+
+def test_unknown_status() -> None:
+ with pytest.raises(
+ AttributeError,
+ match="module 'starlette.status' has no attribute
'HTTP_999_UNKNOWN_STATUS_CODE'",
+ ):
+ getattr(importlib.import_module("starlette.status"),
"HTTP_999_UNKNOWN_STATUS_CODE")