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 May 19, 2024
2 parents 0230b82 + 2fc6d4f commit e79cdb4
Show file tree
Hide file tree
Showing 14 changed files with 67 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lock.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v5
with:
issue-inactive-days: 14
pr-inactive-days: 14
8 changes: 4 additions & 4 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
hash: ${{ steps.hash.outputs.hash }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- run: pip install poetry
Expand All @@ -19,7 +19,7 @@ jobs:
- name: generate hash
id: hash
run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
path: ./dist

Expand All @@ -42,7 +42,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
- name: create release
run: >
gh release create --draft --repo ${{ github.repository }}
Expand All @@ -59,7 +59,7 @@ jobs:
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
# Try uploading to Test PyPI first, in case something fails.
- uses: pypa/gh-action-pypi-publish@release/v1
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- {name: Format, python: '3.12', os: ubuntu-latest, tox: format}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: update pip
Expand All @@ -45,7 +45,7 @@ jobs:
pip install -U setuptools
python -m pip install -U pip
- name: cache mypy
uses: actions/cache@v3.3.2
uses: actions/cache@v4.0.0
with:
path: ./.mypy_cache
key: mypy|${{ matrix.python }}|${{ hashFiles('pyproject.toml') }}
Expand Down
7 changes: 7 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
0.19.6 2024-05-19
-----------------

* Bugfix use ContentRange in the right way. See issue #331.
* Bugfix hold a strong reference to background tasks.
* Bugfix avoid ResourceWarning in DataBody.__aiter__.

0.19.5 2024-04-01
-----------------

Expand Down
Binary file modified artwork/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/_static/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/_static/logo_short.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "Quart"
version = "0.19.5"
version = "0.19.6"
description = "A Python ASGI web microframework with the same API as Flask"
authors = ["pgjones <[email protected]>"]
classifiers = [
Expand Down
39 changes: 24 additions & 15 deletions src/quart/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
NoReturn,
Optional,
overload,
Set,
TypeVar,
Union,
)
from urllib.parse import quote
from weakref import WeakSet

from aiofiles import open as async_open
from aiofiles.base import AiofilesContextManager
Expand Down Expand Up @@ -315,7 +315,7 @@ def __init__(
self.after_websocket_funcs: dict[AppOrBlueprintKey, list[AfterWebsocketCallable]] = (
defaultdict(list)
)
self.background_tasks: WeakSet[asyncio.Task] = WeakSet()
self.background_tasks: Set[asyncio.Task] = set()
self.before_serving_funcs: list[Callable[[], Awaitable[None]]] = []
self.before_websocket_funcs: dict[AppOrBlueprintKey, list[BeforeWebsocketCallable]] = (
defaultdict(list)
Expand Down Expand Up @@ -1037,7 +1037,7 @@ async def handle_exception(self, error: Exception) -> ResponseTypes:
"""
exc_info = sys.exc_info()
await got_request_exception.send_async(
self, _sync_wrapper=self.ensure_async, exception=error
self, _sync_wrapper=self.ensure_async, exception=error # type: ignore
)
propagate = self.config["PROPAGATE_EXCEPTIONS"]

Expand Down Expand Up @@ -1069,7 +1069,7 @@ async def handle_websocket_exception(self, error: Exception) -> ResponseTypes |
"""
exc_info = sys.exc_info()
await got_websocket_exception.send_async(
self, _sync_wrapper=self.ensure_async, exception=error
self, _sync_wrapper=self.ensure_async, exception=error # type: ignore
)
propagate = self.config["PROPAGATE_EXCEPTIONS"]

Expand Down Expand Up @@ -1163,7 +1163,9 @@ async def do_teardown_request(
for function in reversed(self.teardown_request_funcs[name]):
await self.ensure_async(function)(exc)

await request_tearing_down.send_async(self, _sync_wrapper=self.ensure_async, exc=exc)
await request_tearing_down.send_async(
self, _sync_wrapper=self.ensure_async, exc=exc # type: ignore
)

async def do_teardown_websocket(
self, exc: BaseException | None, websocket_context: WebsocketContext | None = None
Expand All @@ -1181,13 +1183,17 @@ async def do_teardown_websocket(
for function in reversed(self.teardown_websocket_funcs[name]):
await self.ensure_async(function)(exc)

await websocket_tearing_down.send_async(self, _sync_wrapper=self.ensure_async, exc=exc)
await websocket_tearing_down.send_async(
self, _sync_wrapper=self.ensure_async, exc=exc # type: ignore
)

async def do_teardown_appcontext(self, exc: BaseException | None) -> None:
"""Teardown the app (context), calling the teardown functions."""
for function in self.teardown_appcontext_funcs:
await self.ensure_async(function)(exc)
await appcontext_tearing_down.send_async(self, _sync_wrapper=self.ensure_async, exc=exc)
await appcontext_tearing_down.send_async(
self, _sync_wrapper=self.ensure_async, exc=exc # type: ignore
)

def app_context(self) -> AppContext:
"""Create and return an app context.
Expand Down Expand Up @@ -1318,10 +1324,11 @@ async def _wrapper() -> None:

task = asyncio.get_event_loop().create_task(_wrapper())
self.background_tasks.add(task)
task.add_done_callback(self.background_tasks.discard)

async def handle_background_exception(self, error: Exception) -> None:
await got_background_exception.send_async(
self, _sync_wrapper=self.ensure_async, exception=error
self, _sync_wrapper=self.ensure_async, exception=error # type: ignore
)

self.log_exception(sys.exc_info())
Expand Down Expand Up @@ -1424,7 +1431,7 @@ async def full_dispatch_request(
omits this argument.
"""
try:
await request_started.send_async(self, _sync_wrapper=self.ensure_async)
await request_started.send_async(self, _sync_wrapper=self.ensure_async) # type: ignore

result: ResponseReturnValue | HTTPException | None
result = await self.preprocess_request(request_context)
Expand All @@ -1444,7 +1451,9 @@ async def full_dispatch_websocket(
the Flask convention.
"""
try:
await websocket_started.send_async(self, _sync_wrapper=self.ensure_async)
await websocket_started.send_async(
self, _sync_wrapper=self.ensure_async # type: ignore
)

result: ResponseReturnValue | HTTPException | None
result = await self.preprocess_websocket(websocket_context)
Expand Down Expand Up @@ -1558,7 +1567,7 @@ async def finalize_request(
try:
response = await self.process_response(response, request_context)
await request_finished.send_async(
self, _sync_wrapper=self.ensure_async, response=response
self, _sync_wrapper=self.ensure_async, response=response # type: ignore
)
except Exception:
if not from_error_handler:
Expand Down Expand Up @@ -1586,7 +1595,7 @@ async def finalize_websocket(
try:
response = await self.postprocess_websocket(response, websocket_context)
await websocket_finished.send_async(
self, _sync_wrapper=self.ensure_async, response=response
self, _sync_wrapper=self.ensure_async, response=response # type: ignore
)
except Exception:
if not from_error_handler:
Expand Down Expand Up @@ -1693,7 +1702,7 @@ async def startup(self) -> None:
await gen.__anext__()
except Exception as error:
await got_serving_exception.send_async(
self, _sync_wrapper=self.ensure_async, exception=error
self, _sync_wrapper=self.ensure_async, exception=error # type: ignore
)
self.log_exception(sys.exc_info())
raise
Expand All @@ -1706,7 +1715,7 @@ async def shutdown(self) -> None:
timeout=self.config["BACKGROUND_TASK_SHUTDOWN_TIMEOUT"],
)
except asyncio.TimeoutError:
await cancel_tasks(self.background_tasks) # type: ignore
await cancel_tasks(self.background_tasks)

try:
async with self.app_context():
Expand All @@ -1721,7 +1730,7 @@ async def shutdown(self) -> None:
raise RuntimeError("While serving generator didn't terminate")
except Exception as error:
await got_serving_exception.send_async(
self, _sync_wrapper=self.ensure_async, exception=error
self, _sync_wrapper=self.ensure_async, exception=error # type: ignore
)
self.log_exception(sys.exc_info())
raise
Expand Down
8 changes: 6 additions & 2 deletions src/quart/ctx.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,9 @@ def copy(self) -> AppContext:

async def push(self) -> None:
self._cv_tokens.append(_cv_app.set(self))
await appcontext_pushed.send_async(self.app, _sync_wrapper=self.app.ensure_async)
await appcontext_pushed.send_async(
self.app, _sync_wrapper=self.app.ensure_async # type: ignore
)

async def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
try:
Expand All @@ -259,7 +261,9 @@ async def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ign
if ctx is not self:
raise AssertionError(f"Popped wrong app context. ({ctx!r} instead of {self!r})")

await appcontext_popped.send_async(self.app, _sync_wrapper=self.app.ensure_async)
await appcontext_popped.send_async(
self.app, _sync_wrapper=self.app.ensure_async # type: ignore
)

async def __aenter__(self) -> AppContext:
await self.push()
Expand Down
2 changes: 1 addition & 1 deletion src/quart/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ async def save_session(
val = self.get_signing_serializer(app).dumps(dict(session))
response.set_cookie(
name,
val, # type: ignore
val,
expires=expires,
httponly=httponly,
domain=domain,
Expand Down
8 changes: 4 additions & 4 deletions src/quart/templating.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ async def render_template_string(source: str, **context: Any) -> str:

async def _render(template: Template, context: dict, app: Quart) -> str:
await before_render_template.send_async(
app, _sync_wrapper=app.ensure_async, template=template, context=context
app, _sync_wrapper=app.ensure_async, template=template, context=context # type: ignore
)
rendered_template = await template.render_async(context)
await template_rendered.send_async(
app, _sync_wrapper=app.ensure_async, template=template, context=context
app, _sync_wrapper=app.ensure_async, template=template, context=context # type: ignore
)
return rendered_template

Expand Down Expand Up @@ -115,14 +115,14 @@ async def stream_template_string(source: str, **context: Any) -> AsyncIterator[s

async def _stream(app: Quart, template: Template, context: dict[str, Any]) -> AsyncIterator[str]:
await before_render_template.send_async(
app, _sync_wrapper=app.ensure_async, template=template, context=context
app, _sync_wrapper=app.ensure_async, template=template, context=context # type: ignore
)

async def generate() -> AsyncIterator[str]:
async for chunk in template.generate_async(context):
yield chunk
await template_rendered.send_async(
app, _sync_wrapper=app.ensure_async, template=template, context=context
app, _sync_wrapper=app.ensure_async, template=template, context=context # type: ignore
)

# If a request context is active, keep it while generating.
Expand Down
22 changes: 16 additions & 6 deletions src/quart/wrappers/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,8 @@ async def __aenter__(self) -> DataBody:
async def __aexit__(self, exc_type: type, exc_value: BaseException, tb: TracebackType) -> None:
pass

def __aiter__(self) -> AsyncIterator:
async def _aiter() -> AsyncGenerator[bytes, None]:
yield self.data[self.begin : self.end]

return _aiter()
def __aiter__(self) -> AsyncIterator[bytes]:
return _DataBodyGen(self)

async def make_conditional(self, begin: int, end: int | None) -> int:
self.begin = begin
Expand All @@ -91,6 +88,19 @@ async def make_conditional(self, begin: int, end: int | None) -> int:
return len(self.data)


class _DataBodyGen(AsyncIterator[bytes]):
def __init__(self, data_body: DataBody):
self._data_body = data_body
self._iterated = False

async def __anext__(self) -> bytes:
if self._iterated:
raise StopAsyncIteration

self._iterated = True
return self._data_body.data[self._data_body.begin : self._data_body.end]


class IterableBody(ResponseBody):
def __init__(self, iterable: AsyncGenerator[bytes, None] | Iterable) -> None:
self.iter: AsyncGenerator[bytes, None]
Expand Down Expand Up @@ -414,7 +424,7 @@ async def _process_range_request(
self.content_range = ContentRange(
request_range.units,
self.response.begin, # type: ignore
self.response.end - 1, # type: ignore
self.response.end, # type: ignore
complete_length,
)
self.status_code = 206
Expand Down
2 changes: 1 addition & 1 deletion tests/wrappers/test_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async def test_response_make_conditional(http_scope: HTTPScope) -> None:
assert response.accept_ranges == "bytes"
assert response.content_range.units == "bytes"
assert response.content_range.start == 0
assert response.content_range.stop == 3
assert response.content_range.stop == 4
assert response.content_range.length == 6


Expand Down

0 comments on commit e79cdb4

Please sign in to comment.