Skip to content

Commit 1f47cda

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents b76e094 + c4e07f9 commit 1f47cda

File tree

10 files changed

+129
-105
lines changed

10 files changed

+129
-105
lines changed

django/core/handlers/asgi.py

Lines changed: 30 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
import tempfile
55
import traceback
6-
from contextlib import aclosing
6+
from contextlib import aclosing, closing
77

88
from asgiref.sync import ThreadSensitiveContext, sync_to_async
99

@@ -174,65 +174,41 @@ async def handle(self, scope, receive, send):
174174
body_file = await self.read_body(receive)
175175
except RequestAborted:
176176
return
177-
# Request is complete and can be served.
178-
set_script_prefix(get_script_prefix(scope))
179-
await signals.request_started.asend(sender=self.__class__, scope=scope)
180-
# Get the request and check for basic issues.
181-
request, error_response = self.create_request(scope, body_file)
182-
if request is None:
183-
body_file.close()
184-
await self.send_response(error_response, send)
185-
await sync_to_async(error_response.close)()
186-
return
187177

188-
async def process_request(request, send):
189-
response = await self.run_get_response(request)
190-
try:
191-
await self.send_response(response, send)
192-
except asyncio.CancelledError:
193-
# Client disconnected during send_response (ignore exception).
178+
with closing(body_file):
179+
# Request is complete and can be served.
180+
set_script_prefix(get_script_prefix(scope))
181+
await signals.request_started.asend(sender=self.__class__, scope=scope)
182+
# Get the request and check for basic issues.
183+
request, error_response = self.create_request(scope, body_file)
184+
if request is None:
185+
body_file.close()
186+
await self.send_response(error_response, send)
187+
await sync_to_async(error_response.close)()
188+
return
189+
190+
class RequestProcessed(Exception):
194191
pass
195192

196-
return response
197-
198-
# Try to catch a disconnect while getting response.
199-
tasks = [
200-
# Check the status of these tasks and (optionally) terminate them
201-
# in this order. The listen_for_disconnect() task goes first
202-
# because it should not raise unexpected errors that would prevent
203-
# us from cancelling process_request().
204-
asyncio.create_task(self.listen_for_disconnect(receive)),
205-
asyncio.create_task(process_request(request, send)),
206-
]
207-
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
208-
# Now wait on both tasks (they may have both finished by now).
209-
for task in tasks:
210-
if task.done():
211-
try:
212-
task.result()
213-
except RequestAborted:
214-
# Ignore client disconnects.
215-
pass
216-
except AssertionError:
217-
body_file.close()
218-
raise
219-
else:
220-
# Allow views to handle cancellation.
221-
task.cancel()
193+
response = None
194+
try:
222195
try:
223-
await task
224-
except asyncio.CancelledError:
225-
# Task re-raised the CancelledError as expected.
196+
async with asyncio.TaskGroup() as tg:
197+
tg.create_task(self.listen_for_disconnect(receive))
198+
response = await self.run_get_response(request)
199+
await self.send_response(response, send)
200+
raise RequestProcessed
201+
except* (RequestProcessed, RequestAborted):
226202
pass
203+
except BaseExceptionGroup as exception_group:
204+
if len(exception_group.exceptions) == 1:
205+
raise exception_group.exceptions[0]
206+
raise
227207

228-
try:
229-
response = tasks[1].result()
230-
except asyncio.CancelledError:
231-
await signals.request_finished.asend(sender=self.__class__)
232-
else:
233-
await sync_to_async(response.close)()
234-
235-
body_file.close()
208+
if response is None:
209+
await signals.request_finished.asend(sender=self.__class__)
210+
else:
211+
await sync_to_async(response.close)()
236212

237213
async def listen_for_disconnect(self, receive):
238214
"""Listen for disconnect from the client."""

django/db/backends/sqlite3/operations.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import datetime
22
import decimal
3+
import sqlite3
34
import uuid
45
from functools import lru_cache
56
from itertools import chain
@@ -143,16 +144,15 @@ def _quote_params_for_last_executed_query(self, params):
143144
"""
144145
Only for last_executed_query! Don't use this to execute SQL queries!
145146
"""
146-
# This function is limited both by SQLITE_LIMIT_VARIABLE_NUMBER (the
147-
# number of parameters, default = 999) and SQLITE_MAX_COLUMN (the
148-
# number of return values, default = 2000). Since Python's sqlite3
149-
# module doesn't expose the get_limit() C API, assume the default
150-
# limits are in effect and split the work in batches if needed.
151-
BATCH_SIZE = 999
152-
if len(params) > BATCH_SIZE:
147+
connection = self.connection.connection
148+
variable_limit = self.connection.features.max_query_params
149+
column_limit = connection.getlimit(sqlite3.SQLITE_LIMIT_COLUMN)
150+
batch_size = min(variable_limit, column_limit)
151+
152+
if len(params) > batch_size:
153153
results = ()
154-
for index in range(0, len(params), BATCH_SIZE):
155-
chunk = params[index : index + BATCH_SIZE]
154+
for index in range(0, len(params), batch_size):
155+
chunk = params[index : index + batch_size]
156156
results += self._quote_params_for_last_executed_query(chunk)
157157
return results
158158

django/dispatch/dispatcher.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ def _make_id(target):
2222
NO_RECEIVERS = object()
2323

2424

25+
async def _gather(*coros):
26+
if len(coros) == 0:
27+
return []
28+
29+
if len(coros) == 1:
30+
return [await coros[0]]
31+
32+
async def run(i, coro):
33+
results[i] = await coro
34+
35+
try:
36+
async with asyncio.TaskGroup() as tg:
37+
results = [None] * len(coros)
38+
for i, coro in enumerate(coros):
39+
tg.create_task(run(i, coro))
40+
return results
41+
except BaseExceptionGroup as exception_group:
42+
if len(exception_group.exceptions) == 1:
43+
raise exception_group.exceptions[0]
44+
raise
45+
46+
2547
class Signal:
2648
"""
2749
Base class for all signals
@@ -186,7 +208,7 @@ def send(self, sender, **named):
186208
187209
If any receivers are asynchronous, they are called after all the
188210
synchronous receivers via a single call to async_to_sync(). They are
189-
also executed concurrently with asyncio.gather().
211+
also executed concurrently with asyncio.TaskGroup().
190212
191213
Arguments:
192214
@@ -211,7 +233,7 @@ def send(self, sender, **named):
211233
if async_receivers:
212234

213235
async def asend():
214-
async_responses = await asyncio.gather(
236+
async_responses = await _gather(
215237
*(
216238
receiver(signal=self, sender=sender, **named)
217239
for receiver in async_receivers
@@ -235,7 +257,7 @@ async def asend(self, sender, **named):
235257
sync_to_async() adaption before executing any asynchronous receivers.
236258
237259
If any receivers are asynchronous, they are grouped and executed
238-
concurrently with asyncio.gather().
260+
concurrently with asyncio.TaskGroup().
239261
240262
Arguments:
241263
@@ -268,9 +290,9 @@ def sync_send():
268290
async def sync_send():
269291
return []
270292

271-
responses, async_responses = await asyncio.gather(
293+
responses, async_responses = await _gather(
272294
sync_send(),
273-
asyncio.gather(
295+
_gather(
274296
*(
275297
receiver(signal=self, sender=sender, **named)
276298
for receiver in async_receivers
@@ -294,7 +316,7 @@ def send_robust(self, sender, **named):
294316
295317
If any receivers are asynchronous, they are called after all the
296318
synchronous receivers via a single call to async_to_sync(). They are
297-
also executed concurrently with asyncio.gather().
319+
also executed concurrently with asyncio.TaskGroup().
298320
299321
Arguments:
300322
@@ -340,7 +362,7 @@ async def asend_and_wrap_exception(receiver):
340362
return response
341363

342364
async def asend():
343-
async_responses = await asyncio.gather(
365+
async_responses = await _gather(
344366
*(
345367
asend_and_wrap_exception(receiver)
346368
for receiver in async_receivers
@@ -359,7 +381,7 @@ async def asend_robust(self, sender, **named):
359381
sync_to_async() adaption before executing any asynchronous receivers.
360382
361383
If any receivers are asynchronous, they are grouped and executed
362-
concurrently with asyncio.gather.
384+
concurrently with asyncio.TaskGroup.
363385
364386
Arguments:
365387
@@ -414,9 +436,9 @@ async def asend_and_wrap_exception(receiver):
414436
return err
415437
return response
416438

417-
responses, async_responses = await asyncio.gather(
439+
responses, async_responses = await _gather(
418440
sync_send(),
419-
asyncio.gather(
441+
_gather(
420442
*(asend_and_wrap_exception(receiver) for receiver in async_receivers),
421443
),
422444
)

django/utils/html.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import re
66
import warnings
7+
from collections import deque
78
from collections.abc import Mapping
89
from html.parser import HTMLParser
910
from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit
@@ -429,7 +430,7 @@ def trim_punctuation(self, word):
429430
# Strip all opening wrapping punctuation.
430431
middle = word.lstrip(self.wrapping_punctuation_openings)
431432
lead = word[: len(word) - len(middle)]
432-
trail = ""
433+
trail = deque()
433434

434435
# Continue trimming until middle remains unchanged.
435436
trimmed_something = True
@@ -442,7 +443,7 @@ def trim_punctuation(self, word):
442443
rstripped = middle.rstrip(closing)
443444
if rstripped != middle:
444445
strip = counts[closing] - counts[opening]
445-
trail = middle[-strip:]
446+
trail.appendleft(middle[-strip:])
446447
middle = middle[:-strip]
447448
trimmed_something = True
448449
counts[closing] -= strip
@@ -453,7 +454,7 @@ def trim_punctuation(self, word):
453454
else:
454455
rstripped = middle.rstrip(self.trailing_punctuation_chars_no_semicolon)
455456
if rstripped != middle:
456-
trail = middle[len(rstripped) :] + trail
457+
trail.appendleft(middle[len(rstripped) :])
457458
middle = rstripped
458459
trimmed_something = True
459460

@@ -470,13 +471,14 @@ def trim_punctuation(self, word):
470471
# entity.
471472
recent_semicolon = middle[trail_start:].index(";")
472473
middle_semicolon_index = recent_semicolon + trail_start + 1
473-
trail = middle[middle_semicolon_index:] + trail
474+
trail.appendleft(middle[middle_semicolon_index:])
474475
middle = rstripped + middle[trail_start:middle_semicolon_index]
475476
else:
476-
trail = middle[trail_start:] + trail
477+
trail.appendleft(middle[trail_start:])
477478
middle = rstripped
478479
trimmed_something = True
479480

481+
trail = "".join(trail)
480482
return lead, middle, trail
481483

482484
@staticmethod

django/utils/http.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,11 @@ def int_to_base36(i):
169169
raise ValueError("Negative base36 conversion input.")
170170
if i < 36:
171171
return char_set[i]
172-
b36 = ""
172+
b36_parts = []
173173
while i != 0:
174174
i, n = divmod(i, 36)
175-
b36 = char_set[n] + b36
176-
return b36
175+
b36_parts.append(char_set[n])
176+
return "".join(reversed(b36_parts))
177177

178178

179179
def urlsafe_base64_encode(s):

django/utils/numberformat.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,15 @@ def format(
9191
# grouping is a single value
9292
intervals = [grouping, 0]
9393
active_interval = intervals.pop(0)
94-
int_part_gd = ""
94+
int_part_gd = []
9595
cnt = 0
9696
for digit in int_part[::-1]:
9797
if cnt and cnt == active_interval:
9898
if intervals:
9999
active_interval = intervals.pop(0) or active_interval
100-
int_part_gd += thousand_sep[::-1]
100+
int_part_gd.append(thousand_sep[::-1])
101101
cnt = 0
102-
int_part_gd += digit
102+
int_part_gd.append(digit)
103103
cnt += 1
104-
int_part = int_part_gd[::-1]
104+
int_part = "".join(int_part_gd)[::-1]
105105
return sign + int_part + dec_part

0 commit comments

Comments
 (0)