Skip to content

Commit

Permalink
Merge branch 'main' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
john0isaac authored Nov 15, 2024
2 parents e79cdb4 + a80358b commit 1783d7c
Show file tree
Hide file tree
Showing 32 changed files with 197 additions and 135 deletions.
5 changes: 0 additions & 5 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ jobs:
id-token: write
steps:
- uses: actions/download-artifact@v4
# Try uploading to Test PyPI first, in case something fails.
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
packages-dir: artifact/
- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: artifact/
16 changes: 8 additions & 8 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ jobs:
fail-fast: false
matrix:
include:
- {name: Linux, python: '3.12', os: ubuntu-latest, tox: py312}
- {name: Windows, python: '3.12', os: windows-latest, tox: py312}
- {name: Mac, python: '3.12', os: macos-latest, tox: py312}
- {name: Linux, python: '3.13', os: ubuntu-latest, tox: py313}
- {name: Windows, python: '3.13', os: windows-latest, tox: py313}
- {name: Mac, python: '3.13', os: macos-latest, tox: py313}
- {name: '3.13', python: '3.13', os: ubuntu-latest, tox: py313}
- {name: '3.12', python: '3.12', os: ubuntu-latest, tox: py312}
- {name: '3.11', python: '3.11', os: ubuntu-latest, tox: py311}
- {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39}
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
- {name: Typing, python: '3.12', os: ubuntu-latest, tox: mypy}
- {name: Package, python: '3.12', os: ubuntu-latest, tox: package}
- {name: Lint, python: '3.12', os: ubuntu-latest, tox: pep8}
- {name: Format, python: '3.12', os: ubuntu-latest, tox: format}
- {name: Typing, python: '3.13', os: ubuntu-latest, tox: mypy}
- {name: Package, python: '3.13', os: ubuntu-latest, tox: package}
- {name: Lint, python: '3.13', os: ubuntu-latest, tox: pep8}
- {name: Format, python: '3.13', os: ubuntu-latest, tox: format}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down
17 changes: 17 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
0.19.9 2024-11-14
-----------------

* Fix missing ``PROVIDE_AUTOMATIC_OPTIONS`` config for compatibility
with Flask 3.1.

0.19.8 2024-10-25
-----------------

* Bugfix Fix missing check that caused the previous fix to raise an error. #366

0.19.7 2024-10-25
-----------------

* Security Fix how ``max_form_memory_size`` is applied when parsing large
non-file fields. https://github.com/advisories/GHSA-q34m-jh98-gwm2

0.19.6 2024-05-19
-----------------

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Quart can be installed via `pip
$ pip install quart
and requires Python 3.8.0 or higher (see `python version support
and requires Python 3.9.0 or higher (see `python version support
<https://quart.palletsprojects.com/en/latest/discussion/python_versions.html>`_
for reasoning).

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
language = "en"

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
Expand Down
8 changes: 2 additions & 6 deletions docs/discussion/python_versions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,5 @@
Python version support
======================

The main branch and releases >= 0.19.0 onwards only support Python
3.8.0 or greater.

The 0.6-maintenance branch supported Python3.6. The final 0.6.X
release, 0.6.15, was released in October 2019 after the release of
Python3.8.
The main branch and releases >= 0.20.0 onwards only support Python
3.9.0 or greater.
2 changes: 1 addition & 1 deletion docs/how_to_guides/developing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ environment variable. For example,
$ QUART_APP=run:app quart run
The ``quart run`` command comes with ``--host``, and ``--port`` to
specify where the app is served, and ``--cerfile`` and ``--keyfile``
specify where the app is served, and ``--certfile`` and ``--keyfile``
to specify the SSL certificates to use.

app.run()
Expand Down
2 changes: 2 additions & 0 deletions docs/how_to_guides/quart_extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ here,
validation and auto-generated API documentation.
- `Quart-session <https://github.com/sanderfoobar/quart-session>`_ server
side session support.
- `Quart-LibreTranslate <https://github.com/Quart-Addons/quart-libretranslate>`_ Simple integration to
use LibreTranslate with your Quart app.
- `Quart-Uploads <https://github.com/Quart-Addons/quart-uploads>`_ File upload handling for Quart.

Supporting sync code in a Quart Extension
Expand Down
4 changes: 2 additions & 2 deletions docs/tutorials/blog_tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ we can do by adding the command code to *src/blog/__init__.py*:
from sqlite3 import dbapi2 as sqlite3
app.config.update({
"DATABASE": app.root_path / "blog.db",
"DATABASE": Path(app.root_path) / "blog.db",
})
def _connect_db():
Expand All @@ -120,7 +120,7 @@ we can do by adding the command code to *src/blog/__init__.py*:
def init_db():
db = _connect_db()
with open(app.root_path / "schema.sql", mode="r") as file_:
with open(Path(app.root_path) / "schema.sql", mode="r") as file_:
db.cursor().executescript(file_.read())
db.commit()
Expand Down
11 changes: 4 additions & 7 deletions docs/tutorials/deployment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,10 @@ See the `Hypercorn docs <https://hypercorn.readthedocs.io/>`_.
Alternative ASGI Servers
------------------------

==================================================== ====== ====== =========== ==================
Server name HTTP/2 HTTP/3 Server Push Websocket Response
==================================================== ====== ====== =========== ==================
`Hypercorn <https://github.com/pgjones/hypercorn>`_ ✓ ✓ ✓ ✓
`Daphne <https://github.com/django/daphne>`_ ✓ ✗ ✗ ✗
`Uvicorn <https://github.com/encode/uvicorn>`_ ✗ ✗ ✗ ✗
==================================================== ====== ====== =========== ==================
Alongside `Hypercorn <https://github.com/pgjones/hypercorn>`_, `Daphne
<https://github.com/django/daphne>`_, and `Uvicorn
<https://github.com/encode/uvicorn>`_ are available ASGI servers that
work with Quart.

Serverless deployment
---------------------
Expand Down
6 changes: 3 additions & 3 deletions docs/tutorials/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Installation
============

Quart is only compatible with Python 3.8 or higher and can be installed
Quart is only compatible with Python 3.9 or higher and can be installed
using pip or your favorite python package manager:

.. code-block:: console
Expand All @@ -20,11 +20,11 @@ be installed with Quart:
- blinker, to manager signals,
- click, to manage command line arguments
- hypercorn, an ASGI server for development,
- importlib_metadata only for Python 3.8,
- importlib_metadata only for Python 3.9,
- itsdangerous, for signing secure cookies,
- jinja2, for template rendering,
- markupsafe, for markup rendering,
- typing_extensions only for Python 3.8,
- typing_extensions only for Python 3.9,
- werkzeug, as the basis of many Quart classes.

You can choose to install with the dotenv extra:
Expand Down
9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "Quart"
version = "0.19.6"
version = "0.20.0.dev"
description = "A Python ASGI web microframework with the same API as Flask"
authors = ["pgjones <[email protected]>"]
classifiers = [
Expand All @@ -11,11 +11,11 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
]
Expand All @@ -26,7 +26,7 @@ repository = "https://github.com/pallets/quart/"
documentation = "https://quart.palletsprojects.com"

[tool.poetry.dependencies]
python = ">=3.8"
python = ">=3.9"
aiofiles = "*"
blinker = ">=1.6"
click = ">=8.0.0"
Expand Down Expand Up @@ -55,7 +55,7 @@ dotenv = ["python-dotenv"]

[tool.black]
line-length = 100
target-version = ["py38"]
target-version = ["py39"]

[tool.isort]
combine_as_imports = true
Expand Down Expand Up @@ -87,6 +87,7 @@ warn_unused_ignores = true

[tool.pytest.ini_options]
addopts = "--no-cov-on-fail --showlocals --strict-markers"
asyncio_default_fixture_loop_scope = "session"
asyncio_mode = "auto"
testpaths = ["tests"]

Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[flake8]
ignore = E203, E252, E704, FI58, W503, W504
max_line_length = 100
min_version = 3.8
min_version = 3.9
per-file-ignores =
src/quart/__init__.py:F401
require_code = True
23 changes: 16 additions & 7 deletions src/quart/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import warnings
from collections import defaultdict
from datetime import timedelta
from inspect import isasyncgen, isgenerator
from inspect import isasyncgen, iscoroutinefunction as _inspect_iscoroutinefunction, isgenerator
from types import TracebackType
from typing import (
Any,
Expand All @@ -28,7 +28,6 @@

from aiofiles import open as async_open
from aiofiles.base import AiofilesContextManager
from aiofiles.threadpool.binary import AsyncBufferedReader
from flask.sansio.app import App
from flask.sansio.scaffold import setupmethod
from hypercorn.asyncio import serve
Expand Down Expand Up @@ -125,7 +124,16 @@
try:
from typing import ParamSpec
except ImportError:
from typing_extensions import ParamSpec # type: ignore
from typing_extensions import ParamSpec

# Python 3.14 deprecated asyncio.iscoroutinefunction, but suggested
# inspect.iscoroutinefunction does not work correctly in some Python
# versions before 3.12.
# See https://github.com/python/cpython/issues/122858#issuecomment-2466239748
if sys.version_info >= (3, 12):
iscoroutinefunction = _inspect_iscoroutinefunction
else:
iscoroutinefunction = asyncio.iscoroutinefunction

AppOrBlueprintKey = Optional[str] # The App key is None, whereas blueprints are named
T_after_serving = TypeVar("T_after_serving", bound=AfterServingCallable)
Expand Down Expand Up @@ -241,6 +249,7 @@ class Quart(App):
"PREFER_SECURE_URLS": False,
"PRESERVE_CONTEXT_ON_EXCEPTION": None,
"PROPAGATE_EXCEPTIONS": None,
"PROVIDE_AUTOMATIC_OPTIONS": True,
"RESPONSE_TIMEOUT": 60, # Second
"SECRET_KEY": None,
"SEND_FILE_MAX_AGE_DEFAULT": timedelta(hours=12),
Expand Down Expand Up @@ -375,7 +384,7 @@ async def open_resource(
self,
path: FilePath,
mode: str = "rb",
) -> AiofilesContextManager[None, None, AsyncBufferedReader]:
) -> AiofilesContextManager:
"""Open a file for reading.
Use as
Expand All @@ -392,7 +401,7 @@ async def open_resource(

async def open_instance_resource(
self, path: FilePath, mode: str = "rb"
) -> AiofilesContextManager[None, None, AsyncBufferedReader]:
) -> AiofilesContextManager:
"""Open a file for reading.
Use as
Expand Down Expand Up @@ -1130,7 +1139,7 @@ def ensure_async(
run. Before Quart 0.11 this did not run the synchronous code
in an executor.
"""
if asyncio.iscoroutinefunction(func):
if iscoroutinefunction(func):
return func
else:
return self.sync_to_async(cast(Callable[P, T], func))
Expand Down Expand Up @@ -1393,7 +1402,7 @@ async def make_response(self, result: ResponseReturnValue | HTTPException) -> Re
response.status_code = int(status)

if headers is not None:
response.headers.update(headers) # type: ignore[arg-type]
response.headers.update(headers)

return response

Expand Down
3 changes: 1 addition & 2 deletions src/quart/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from aiofiles import open as async_open
from aiofiles.base import AiofilesContextManager
from aiofiles.threadpool.binary import AsyncBufferedReader
from flask.sansio.app import App
from flask.sansio.blueprints import ( # noqa
Blueprint as SansioBlueprint,
Expand Down Expand Up @@ -101,7 +100,7 @@ async def open_resource(
self,
path: FilePath,
mode: str = "rb",
) -> AiofilesContextManager[None, None, AsyncBufferedReader]:
) -> AiofilesContextManager:
"""Open a file for reading.
Use as
Expand Down
10 changes: 10 additions & 0 deletions src/quart/formparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from urllib.parse import parse_qsl

from werkzeug.datastructures import Headers, MultiDict
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.formparser import default_stream_factory
from werkzeug.http import parse_options_header
from werkzeug.sansio.multipart import Data, Epilogue, Field, File, MultipartDecoder, NeedData
Expand Down Expand Up @@ -173,19 +174,28 @@ async def parse(
files = []

current_part: Field | File
field_size: int | None = None
async for data in body:
parser.receive_data(data)
event = parser.next_event()
while not isinstance(event, (Epilogue, NeedData)):
if isinstance(event, Field):
current_part = event
field_size = 0
container = []
_write = container.append
elif isinstance(event, File):
current_part = event
field_size = None
container = self.start_file_streaming(event, content_length)
_write = container.write
elif isinstance(event, Data):
if self.max_form_memory_size is not None and field_size is not None:
field_size += len(event.data)

if field_size > self.max_form_memory_size:
raise RequestEntityTooLarge()

_write(event.data)
if not event.more_data:
if isinstance(current_part, Field):
Expand Down
6 changes: 3 additions & 3 deletions src/quart/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ def get_flashed_messages(
all messages will be popped, but only those matching the filter
returned. See :func:`~quart.helpers.flash` for message creation.
"""
flashes = request_ctx.flashes
flashes: list[str] = request_ctx.flashes
if flashes is None:
flashes = session.pop("_flashes") if "_flashes" in session else []
request_ctx.flashes = flashes
flashes = session.pop("_flashes", [])
request_ctx.flashes = flashes # type: ignore[assignment]
if category_filter:
flashes = [flash for flash in flashes if flash[0] in category_filter]
if not with_categories:
Expand Down
2 changes: 1 addition & 1 deletion src/quart/testing/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_client(self) -> TestClientProtocol:
return self.app.test_client()

async def startup(self) -> None:
scope: LifespanScope = {"type": "lifespan", "asgi": {"spec_version": "2.0"}}
scope: LifespanScope = {"type": "lifespan", "asgi": {"spec_version": "2.0"}, "state": {}}
self._task = asyncio.ensure_future(self.app(scope, self._asgi_receive, self._asgi_send))
await self._app_queue.put({"type": "lifespan.startup"})
await asyncio.wait_for(self._startup.wait(), timeout=self.startup_timeout)
Expand Down
Loading

0 comments on commit 1783d7c

Please sign in to comment.