Bug 2002002 - Stop using vendored taskcluster-taskgraph, r=firefox-build-system-reviewers,taskgraph-reviewers,releng-reviewers,mach-reviewers,ahal,ahochheiden
Differential Revision: https://phabricator.services.mozilla.com/D273841
This commit is contained in:
committed by
amadan@mozilla.com
parent
7f33921416
commit
fff9fde7e9
@@ -229,7 +229,6 @@ tasks:
|
||||
# This causes cached_task digest generation to be random for
|
||||
# some tasks. Disable bytecode generation to work around that.
|
||||
PYTHONDONTWRITEBYTECODE: '1'
|
||||
MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE: 'system'
|
||||
- $if: 'tasks_for == "action"'
|
||||
then:
|
||||
ACTION_TASK_GROUP_ID: '${action.taskGroupId}' # taskGroupId of the target task
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[include]
|
||||
# List of dependencies for the command
|
||||
path:python/sites/docs.txt
|
||||
path:taskcluster/requirements.txt
|
||||
|
||||
# Code for generating docs.
|
||||
glob:docs/**
|
||||
|
||||
@@ -12,16 +12,13 @@ import pathlib
|
||||
import shutil
|
||||
from collections import OrderedDict
|
||||
|
||||
# As a result of the selective module loading changes, this import has to be
|
||||
# done here. It is not explicitly used, but it has an implicit side-effect
|
||||
# (bringing in TASKCLUSTER_ROOT_URL) which is necessary.
|
||||
import gecko_taskgraph.main # noqa: F401
|
||||
import mozversioncontrol
|
||||
from mach.decorators import Command, CommandArgument, SubCommand
|
||||
|
||||
from mozbuild.artifact_builds import JOB_CHOICES
|
||||
from mozbuild.base import MachCommandConditions as conditions
|
||||
from mozbuild.dirutils import ensureParentDir
|
||||
from mozbuild.util import get_root_url, get_taskcluster_client
|
||||
|
||||
_COULD_NOT_FIND_ARTIFACTS_TEMPLATE = (
|
||||
"ERROR!!!!!! Could not find artifacts for a toolchain build named "
|
||||
@@ -290,7 +287,6 @@ def artifact_toolchain(
|
||||
|
||||
import redo
|
||||
import requests
|
||||
from taskgraph.util.taskcluster import get_artifact_url
|
||||
|
||||
from mozbuild.action.tooltool import FileRecord, open_manifest, unpack_file
|
||||
from mozbuild.artifacts import ArtifactCache
|
||||
@@ -316,9 +312,8 @@ def artifact_toolchain(
|
||||
cache_dir = os.path.join(command_context._mach_context.state_dir, "toolchains")
|
||||
|
||||
tooltool_host = os.environ.get("TOOLTOOL_HOST", "tooltool.mozilla-releng.net")
|
||||
taskcluster_proxy_url = os.environ.get("TASKCLUSTER_PROXY_URL")
|
||||
if taskcluster_proxy_url:
|
||||
tooltool_url = f"{taskcluster_proxy_url}/{tooltool_host}"
|
||||
if "TASKCLUSTER_PROXY_URL" in os.environ:
|
||||
tooltool_url = f"{get_root_url()}/{tooltool_host}"
|
||||
else:
|
||||
tooltool_url = f"https://{tooltool_host}"
|
||||
|
||||
@@ -343,10 +338,12 @@ def artifact_toolchain(
|
||||
|
||||
class ArtifactRecord(DownloadRecord):
|
||||
def __init__(self, task_id, artifact_name):
|
||||
queue = get_taskcluster_client("queue")
|
||||
for _ in redo.retrier(attempts=retry + 1, sleeptime=60):
|
||||
cot = cache._download_manager.session.get(
|
||||
get_artifact_url(task_id, "public/chain-of-trust.json")
|
||||
cot_url = queue.buildUrl(
|
||||
"getLatestArtifact", task_id, "public/chain-of-trust.json"
|
||||
)
|
||||
cot = cache._download_manager.session.get(cot_url)
|
||||
if cot.status_code >= 500:
|
||||
continue
|
||||
cot.raise_for_status()
|
||||
@@ -362,11 +359,18 @@ def artifact_toolchain(
|
||||
pass
|
||||
|
||||
name = os.path.basename(artifact_name)
|
||||
artifact_url = get_artifact_url(
|
||||
task_id,
|
||||
artifact_name,
|
||||
use_proxy=not artifact_name.startswith("public/"),
|
||||
)
|
||||
if (
|
||||
not artifact_name.startswith("public/")
|
||||
and "TASKCLUSTER_PROXY_URL" in os.environ
|
||||
):
|
||||
artifact_url = queue.buildUrl(
|
||||
"getLatestArtifact", task_id, artifact_name
|
||||
)
|
||||
else:
|
||||
public_queue = get_taskcluster_client("queue", block_proxy=True)
|
||||
artifact_url = public_queue.buildUrl(
|
||||
"getLatestArtifact", task_id, artifact_name
|
||||
)
|
||||
super().__init__(artifact_url, name, None, digest, algorithm, unpack=True)
|
||||
|
||||
records = OrderedDict()
|
||||
|
||||
@@ -59,13 +59,11 @@ from mozpack import executables
|
||||
from mozpack.files import FileFinder, JarFinder, TarFinder
|
||||
from mozpack.mozjar import JarReader, JarWriter
|
||||
from mozpack.packager.unpack import UnpackFinder
|
||||
from taskcluster.exceptions import TaskclusterRestFailure
|
||||
from taskgraph.util.taskcluster import find_task_id, get_artifact_url, list_artifacts
|
||||
|
||||
from mozbuild.artifact_builds import JOB_CHOICES
|
||||
from mozbuild.artifact_cache import ArtifactCache
|
||||
from mozbuild.dirutils import ensureParentDir, mkdir
|
||||
from mozbuild.util import FileAvoidWrite
|
||||
from mozbuild.util import FileAvoidWrite, get_root_url, get_taskcluster_client
|
||||
|
||||
# Number of candidate pushheads to cache per parent changeset.
|
||||
NUM_PUSHHEADS_TO_QUERY_PER_PARENT = 50
|
||||
@@ -1173,8 +1171,13 @@ class TaskCache(CacheManager):
|
||||
{"namespace": namespace},
|
||||
"Searching Taskcluster index with namespace: {namespace}",
|
||||
)
|
||||
|
||||
from taskcluster.exceptions import TaskclusterRestFailure
|
||||
|
||||
try:
|
||||
taskId = find_task_id(namespace)
|
||||
index = get_taskcluster_client("index")
|
||||
task = index.findTask(namespace)
|
||||
taskId = task["taskId"]
|
||||
except (KeyError, TaskclusterRestFailure) as e:
|
||||
if isinstance(e, TaskclusterRestFailure) and e.status_code != 404:
|
||||
raise
|
||||
@@ -1183,7 +1186,9 @@ class TaskCache(CacheManager):
|
||||
# care about; and even those that do may not have completed yet.
|
||||
raise ValueError(f"Task for {namespace} does not exist (yet)!")
|
||||
|
||||
return taskId, list_artifacts(taskId)
|
||||
queue = get_taskcluster_client("queue")
|
||||
response = queue.listLatestArtifacts(taskId)
|
||||
return taskId, response["artifacts"]
|
||||
|
||||
|
||||
class Artifacts:
|
||||
@@ -1593,7 +1598,9 @@ https://firefox-source-docs.mozilla.org/contributing/vcs/mercurial_bundles.html
|
||||
|
||||
urls = []
|
||||
for artifact_name in self._artifact_job.find_candidate_artifacts(artifacts):
|
||||
url = get_artifact_url(taskId, artifact_name)
|
||||
url = (
|
||||
f"{get_root_url()}/api/queue/v1/task/{taskId}/artifacts/{artifact_name}"
|
||||
)
|
||||
urls.append(url)
|
||||
if urls:
|
||||
self.log(
|
||||
@@ -1809,11 +1816,15 @@ https://firefox-source-docs.mozilla.org/contributing/vcs/mercurial_bundles.html
|
||||
return self._install_from_hg_pushheads(pushheads, distdir)
|
||||
|
||||
def install_from_task(self, taskId, distdir):
|
||||
artifacts = list_artifacts(taskId)
|
||||
queue = get_taskcluster_client("queue")
|
||||
response = queue.listLatestArtifacts(taskId)
|
||||
artifacts = response["artifacts"]
|
||||
|
||||
urls = []
|
||||
for artifact_name in self._artifact_job.find_candidate_artifacts(artifacts):
|
||||
url = get_artifact_url(taskId, artifact_name)
|
||||
url = (
|
||||
f"{get_root_url()}/api/queue/v1/task/{taskId}/artifacts/{artifact_name}"
|
||||
)
|
||||
urls.append(url)
|
||||
if not urls:
|
||||
raise ValueError(f"Task {taskId} existed, but no artifacts found!")
|
||||
|
||||
@@ -1407,3 +1407,29 @@ def ensure_l10n_central(command_context):
|
||||
raise NotAGitRepositoryError(
|
||||
f"Directory is not a git repository: {l10n_base_dir}"
|
||||
)
|
||||
|
||||
|
||||
# Taskcluster API root URL (Firefox's production instance)
|
||||
TASKCLUSTER_ROOT_URL = "https://firefox-ci-tc.services.mozilla.com"
|
||||
|
||||
|
||||
def get_root_url(block_proxy=False):
|
||||
if "TASKCLUSTER_PROXY_URL" in os.environ and not block_proxy:
|
||||
return os.environ["TASKCLUSTER_PROXY_URL"].rstrip("/")
|
||||
|
||||
if "TASKCLUSTER_ROOT_URL" in os.environ:
|
||||
return os.environ["TASKCLUSTER_ROOT_URL"].rstrip("/")
|
||||
|
||||
return TASKCLUSTER_ROOT_URL
|
||||
|
||||
|
||||
def get_taskcluster_client(service: str, block_proxy=False):
|
||||
import taskcluster
|
||||
|
||||
if "TASKCLUSTER_PROXY_URL" in os.environ and not block_proxy:
|
||||
options = {"rootUrl": os.environ["TASKCLUSTER_PROXY_URL"].rstrip("/")}
|
||||
else:
|
||||
options = taskcluster.optionsFromEnvironment({
|
||||
"rootUrl": get_root_url(block_proxy)
|
||||
})
|
||||
return getattr(taskcluster, service[0].upper() + service[1:])(options)
|
||||
|
||||
+1
-28
@@ -1,5 +1,6 @@
|
||||
requires-python:>=3.9
|
||||
pth:third_party/python/vsdownload
|
||||
pypi-optional:taskcluster-taskgraph==18.0.3:toolchain artifacts will not be bootstrapped
|
||||
vendored:testing/web-platform/tests/tools/third_party/h2/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/hpack/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/html5lib
|
||||
@@ -8,53 +9,25 @@ vendored:testing/web-platform/tests/tools/third_party/pywebsocket3
|
||||
vendored:testing/web-platform/tests/tools/third_party/webencodings
|
||||
vendored:testing/web-platform/tests/tools/wptrunner
|
||||
vendored:testing/web-platform/tests/tools/wptserve
|
||||
vendored:third_party/python/aiohappyeyeballs
|
||||
vendored:third_party/python/aiohttp
|
||||
vendored:third_party/python/aiosignal
|
||||
vendored:third_party/python/arrow
|
||||
vendored:third_party/python/async_timeout
|
||||
vendored:third_party/python/binaryornot
|
||||
vendored:third_party/python/compare_locales
|
||||
vendored:third_party/python/cookiecutter
|
||||
vendored:third_party/python/dlmanager
|
||||
vendored:third_party/python/ecdsa
|
||||
vendored:third_party/python/fluent.migrate
|
||||
vendored:third_party/python/fluent.syntax
|
||||
vendored:third_party/python/frozenlist
|
||||
vendored:third_party/python/gitignorant
|
||||
vendored:third_party/python/giturlparse
|
||||
vendored:third_party/python/gyp/pylib
|
||||
vendored:third_party/python/iniparse
|
||||
vendored:third_party/python/json_e
|
||||
vendored:third_party/python/mako
|
||||
vendored:third_party/python/markdown_it_py
|
||||
vendored:third_party/python/mdurl
|
||||
vendored:third_party/python/mohawk
|
||||
vendored:third_party/python/moz_l10n
|
||||
vendored:third_party/python/mozilla_repo_urls
|
||||
vendored:third_party/python/mozilla_taskgraph
|
||||
vendored:third_party/python/multidict
|
||||
vendored:third_party/python/pathspec
|
||||
vendored:third_party/python/ply
|
||||
vendored:third_party/python/polib
|
||||
vendored:third_party/python/propcache
|
||||
vendored:third_party/python/pyasn1
|
||||
vendored:third_party/python/pyasn1_modules
|
||||
vendored:third_party/python/pygments
|
||||
vendored:third_party/python/pylru
|
||||
vendored:third_party/python/python_dateutil
|
||||
vendored:third_party/python/python_slugify
|
||||
vendored:third_party/python/pyyaml/lib/
|
||||
vendored:third_party/python/redo
|
||||
vendored:third_party/python/requests_unixsocket
|
||||
vendored:third_party/python/rich
|
||||
vendored:third_party/python/rsa
|
||||
vendored:third_party/python/slugid
|
||||
vendored:third_party/python/taskcluster
|
||||
vendored:third_party/python/taskcluster_taskgraph
|
||||
vendored:third_party/python/taskcluster_urls
|
||||
vendored:third_party/python/text_unidecode
|
||||
vendored:third_party/python/types_python_dateutil
|
||||
vendored:third_party/python/voluptuous
|
||||
vendored:third_party/python/yamllint
|
||||
vendored:third_party/python/yarl
|
||||
|
||||
@@ -10,11 +10,7 @@ vendored:testing/web-platform/tests/tools/wptserve
|
||||
vendored:third_party/python/aiohappyeyeballs
|
||||
vendored:third_party/python/aiohttp
|
||||
vendored:third_party/python/aiosignal
|
||||
vendored:third_party/python/arrow
|
||||
vendored:third_party/python/async_timeout
|
||||
vendored:third_party/python/binaryornot
|
||||
vendored:third_party/python/compare_locales
|
||||
vendored:third_party/python/cookiecutter
|
||||
vendored:third_party/python/cookies
|
||||
vendored:third_party/python/dlmanager
|
||||
vendored:third_party/python/ecdsa
|
||||
@@ -25,33 +21,24 @@ vendored:third_party/python/frozenlist
|
||||
vendored:third_party/python/giturlparse
|
||||
vendored:third_party/python/gyp/pylib
|
||||
vendored:third_party/python/json_e
|
||||
vendored:third_party/python/markdown_it_py
|
||||
vendored:third_party/python/mdurl
|
||||
vendored:third_party/python/mohawk
|
||||
vendored:third_party/python/mozilla_repo_urls
|
||||
vendored:third_party/python/mozilla_taskgraph
|
||||
vendored:third_party/python/multidict
|
||||
vendored:third_party/python/pathspec
|
||||
vendored:third_party/python/ply
|
||||
vendored:third_party/python/propcache
|
||||
vendored:third_party/python/pyasn1
|
||||
vendored:third_party/python/pyasn1_modules
|
||||
vendored:third_party/python/pygments
|
||||
vendored:third_party/python/pylru
|
||||
vendored:third_party/python/python_dateutil
|
||||
vendored:third_party/python/python_slugify
|
||||
vendored:third_party/python/pyyaml/lib/
|
||||
vendored:third_party/python/redo
|
||||
vendored:third_party/python/requests_unixsocket
|
||||
vendored:third_party/python/responses
|
||||
vendored:third_party/python/rich
|
||||
vendored:third_party/python/rsa
|
||||
vendored:third_party/python/slugid
|
||||
vendored:third_party/python/taskcluster
|
||||
vendored:third_party/python/taskcluster_taskgraph
|
||||
vendored:third_party/python/taskcluster_urls
|
||||
vendored:third_party/python/text_unidecode
|
||||
vendored:third_party/python/types_python_dateutil
|
||||
vendored:third_party/python/voluptuous
|
||||
vendored:third_party/python/yamllint
|
||||
vendored:third_party/python/yarl
|
||||
|
||||
+1
-10
@@ -21,13 +21,10 @@ pypi:sphinx-markdown-tables==0.0.17
|
||||
pypi:sphinx-rtd-theme==2.0.0
|
||||
pypi:sphinx-tabs==3.4.7
|
||||
pypi:sphinxcontrib-mermaid==1.0.0
|
||||
requirements-txt:taskcluster/requirements.txt
|
||||
vendored:third_party/python/aiohappyeyeballs
|
||||
vendored:third_party/python/aiohttp
|
||||
vendored:third_party/python/aiosignal
|
||||
vendored:third_party/python/arrow
|
||||
vendored:third_party/python/async_timeout
|
||||
vendored:third_party/python/binaryornot
|
||||
vendored:third_party/python/cookiecutter
|
||||
vendored:third_party/python/dlmanager
|
||||
vendored:third_party/python/fluent.migrate
|
||||
vendored:third_party/python/fluent.syntax
|
||||
@@ -35,21 +32,15 @@ vendored:third_party/python/frozenlist
|
||||
vendored:third_party/python/giturlparse
|
||||
vendored:third_party/python/gyp/pylib
|
||||
vendored:third_party/python/json_e
|
||||
vendored:third_party/python/markdown_it_py
|
||||
vendored:third_party/python/mdurl
|
||||
vendored:third_party/python/mohawk
|
||||
vendored:third_party/python/mozilla_repo_urls
|
||||
vendored:third_party/python/propcache
|
||||
vendored:third_party/python/pygments
|
||||
vendored:third_party/python/pylru
|
||||
vendored:third_party/python/pyyaml/lib/
|
||||
vendored:third_party/python/redo
|
||||
vendored:third_party/python/requests_unixsocket
|
||||
vendored:third_party/python/responses
|
||||
vendored:third_party/python/rich
|
||||
vendored:third_party/python/slugid
|
||||
vendored:third_party/python/taskcluster
|
||||
vendored:third_party/python/taskcluster_taskgraph
|
||||
vendored:third_party/python/taskcluster_urls
|
||||
vendored:third_party/python/types_python_dateutil
|
||||
vendored:third_party/python/voluptuous
|
||||
|
||||
+2
-14
@@ -21,40 +21,28 @@ pypi:sphinxcontrib-htmlhelp==2.0.1
|
||||
pypi:sphinxcontrib-mermaid==1.0.0
|
||||
pypi:tox==2.7.0
|
||||
pypi:virtualenv==20.24.7
|
||||
requirements-txt:taskcluster/requirements.txt
|
||||
vendored:third_party/python/aiohappyeyeballs
|
||||
vendored:third_party/python/aiohttp
|
||||
vendored:third_party/python/aiosignal
|
||||
vendored:third_party/python/arrow
|
||||
vendored:third_party/python/async_timeout
|
||||
vendored:third_party/python/binaryornot
|
||||
vendored:third_party/python/compare_locales
|
||||
vendored:third_party/python/cookiecutter
|
||||
vendored:third_party/python/esprima
|
||||
vendored:third_party/python/fluent.syntax
|
||||
vendored:third_party/python/frozenlist
|
||||
vendored:third_party/python/giturlparse
|
||||
vendored:third_party/python/json_e
|
||||
vendored:third_party/python/markdown_it_py
|
||||
vendored:third_party/python/mdurl
|
||||
vendored:third_party/python/mohawk
|
||||
vendored:third_party/python/mozilla_repo_urls
|
||||
vendored:third_party/python/mozilla_taskgraph
|
||||
vendored:third_party/python/multidict
|
||||
vendored:third_party/python/pathspec
|
||||
vendored:third_party/python/propcache
|
||||
vendored:third_party/python/pygments
|
||||
vendored:third_party/python/python_dateutil
|
||||
vendored:third_party/python/python_slugify
|
||||
vendored:third_party/python/pyyaml/lib/
|
||||
vendored:third_party/python/redo
|
||||
vendored:third_party/python/requests_unixsocket
|
||||
vendored:third_party/python/rich
|
||||
vendored:third_party/python/slugid
|
||||
vendored:third_party/python/taskcluster
|
||||
vendored:third_party/python/taskcluster_taskgraph
|
||||
vendored:third_party/python/taskcluster_urls
|
||||
vendored:third_party/python/text_unidecode
|
||||
vendored:third_party/python/types_python_dateutil
|
||||
vendored:third_party/python/voluptuous
|
||||
vendored:third_party/python/yamllint
|
||||
vendored:third_party/python/yarl
|
||||
vendored:third_party/python/yarl
|
||||
@@ -63,18 +63,18 @@ pypi-optional:orjson>=3.10:json operations will be slower in various tools
|
||||
# Mach gracefully handles the case where `psutil` is unavailable.
|
||||
# We aren't (yet) able to pin packages in automation, so we have to
|
||||
# support down to the oldest locally-installed version (5.4.2).
|
||||
pypi-optional:psutil>=5.4.2:telemetry will be missing some data
|
||||
pypi-optional:psutil>=5.4.2,<=5.9.4:telemetry will be missing some data
|
||||
pypi-optional:rtoml>=0.11.0:toml operations will be slower in various tools
|
||||
pypi-optional:zstandard>=0.11.1,<=0.24.0:zstd archives will not be possible to extract
|
||||
pypi-optional:zstandard>=0.11.1,<=0.25.0:zstd archives will not be possible to extract
|
||||
vendored-fallback:pyyaml:third_party/python/pyyaml/lib/:faster native loading is disabled
|
||||
vendored:third_party/python/ansicon
|
||||
vendored:third_party/python/appdirs
|
||||
vendored:third_party/python/async_timeout
|
||||
vendored:third_party/python/attrs
|
||||
vendored:third_party/python/blessed
|
||||
vendored:third_party/python/build
|
||||
vendored:third_party/python/cbor2
|
||||
vendored:third_party/python/certifi
|
||||
vendored:third_party/python/chardet
|
||||
vendored:third_party/python/charset_normalizer
|
||||
vendored:third_party/python/click
|
||||
vendored:third_party/python/colorama
|
||||
|
||||
@@ -2,16 +2,14 @@ requires-python:>=3.9
|
||||
pypi:coverage==5.1
|
||||
pypi:pytest-asyncio==1.2.0
|
||||
pypi:pytest==8.4.2
|
||||
requirements-txt:taskcluster/requirements.txt
|
||||
vendored:third_party/python/dlmanager
|
||||
vendored:third_party/python/esprima
|
||||
vendored:third_party/python/giturlparse
|
||||
vendored:third_party/python/mozilla_repo_urls
|
||||
vendored:third_party/python/mozilla_taskgraph
|
||||
vendored:third_party/python/mozilla_taskgraph
|
||||
vendored:third_party/python/pyyaml/lib/
|
||||
vendored:third_party/python/redo
|
||||
vendored:third_party/python/responses
|
||||
vendored:third_party/python/slugid
|
||||
vendored:third_party/python/taskcluster_taskgraph
|
||||
vendored:third_party/python/taskcluster_urls
|
||||
vendored:third_party/python/voluptuous
|
||||
|
||||
@@ -20,7 +20,8 @@ pypi:sphinxcontrib-applehelp==1.0.4
|
||||
pypi:sphinxcontrib-htmlhelp==2.0.1
|
||||
pypi:sphinxcontrib-mermaid==1.0.0
|
||||
pypi:werkzeug==2.3.8
|
||||
pypi:zstandard==0.24.0
|
||||
pypi:zstandard==0.25.0
|
||||
requirements-txt:taskcluster/requirements.txt
|
||||
vendored:testing/web-platform/tests/tools/third_party/h2/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/hpack/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/html5lib
|
||||
@@ -32,11 +33,7 @@ vendored:testing/web-platform/tests/tools/wptserve
|
||||
vendored:third_party/python/aiohappyeyeballs
|
||||
vendored:third_party/python/aiohttp
|
||||
vendored:third_party/python/aiosignal
|
||||
vendored:third_party/python/arrow
|
||||
vendored:third_party/python/async_timeout
|
||||
vendored:third_party/python/binaryornot
|
||||
vendored:third_party/python/compare_locales
|
||||
vendored:third_party/python/cookiecutter
|
||||
vendored:third_party/python/cookies
|
||||
vendored:third_party/python/dlmanager
|
||||
vendored:third_party/python/ecdsa
|
||||
@@ -47,33 +44,24 @@ vendored:third_party/python/frozenlist
|
||||
vendored:third_party/python/giturlparse
|
||||
vendored:third_party/python/gyp/pylib
|
||||
vendored:third_party/python/json_e
|
||||
vendored:third_party/python/markdown_it_py
|
||||
vendored:third_party/python/mdurl
|
||||
vendored:third_party/python/mohawk
|
||||
vendored:third_party/python/mozilla_repo_urls
|
||||
vendored:third_party/python/mozilla_taskgraph
|
||||
vendored:third_party/python/multidict
|
||||
vendored:third_party/python/pathspec
|
||||
vendored:third_party/python/ply
|
||||
vendored:third_party/python/propcache
|
||||
vendored:third_party/python/pyasn1
|
||||
vendored:third_party/python/pyasn1_modules
|
||||
vendored:third_party/python/pygments
|
||||
vendored:third_party/python/pylru
|
||||
vendored:third_party/python/python_dateutil
|
||||
vendored:third_party/python/python_slugify
|
||||
vendored:third_party/python/pyyaml/lib/
|
||||
vendored:third_party/python/redo
|
||||
vendored:third_party/python/requests_unixsocket
|
||||
vendored:third_party/python/responses
|
||||
vendored:third_party/python/rich
|
||||
vendored:third_party/python/rsa
|
||||
vendored:third_party/python/slugid
|
||||
vendored:third_party/python/taskcluster
|
||||
vendored:third_party/python/taskcluster_taskgraph
|
||||
vendored:third_party/python/taskcluster_urls
|
||||
vendored:third_party/python/text_unidecode
|
||||
vendored:third_party/python/types_python_dateutil
|
||||
vendored:third_party/python/voluptuous
|
||||
vendored:third_party/python/yamllint
|
||||
vendored:third_party/python/yarl
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
requires-python:>=3.9
|
||||
requirements-txt:taskcluster/requirements.txt
|
||||
vendored:testing/web-platform/tests/tools/third_party/h2/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/hpack/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/html5lib
|
||||
vendored:testing/web-platform/tests/tools/third_party/hyperframe/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/pywebsocket3
|
||||
vendored:testing/web-platform/tests/tools/third_party/webencodings
|
||||
vendored:testing/web-platform/tests/tools/wptrunner
|
||||
vendored:testing/web-platform/tests/tools/wptserve
|
||||
vendored:third_party/python/fluent.syntax
|
||||
vendored:third_party/python/pathspec
|
||||
+2
-14
@@ -2,6 +2,7 @@ requires-python:>=3.9
|
||||
pypi:Flask==2.1.3
|
||||
pypi:auth0-python==4.4.1
|
||||
pypi:werkzeug==2.3.8
|
||||
requirements-txt:taskcluster/requirements.txt
|
||||
vendored:testing/web-platform/tests/tools/third_party/h2/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/hpack/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/html5lib
|
||||
@@ -13,11 +14,7 @@ vendored:testing/web-platform/tests/tools/wptserve
|
||||
vendored:third_party/python/aiohappyeyeballs
|
||||
vendored:third_party/python/aiohttp
|
||||
vendored:third_party/python/aiosignal
|
||||
vendored:third_party/python/arrow
|
||||
vendored:third_party/python/async_timeout
|
||||
vendored:third_party/python/binaryornot
|
||||
vendored:third_party/python/compare_locales
|
||||
vendored:third_party/python/cookiecutter
|
||||
vendored:third_party/python/cookies
|
||||
vendored:third_party/python/dlmanager
|
||||
vendored:third_party/python/ecdsa
|
||||
@@ -28,33 +25,24 @@ vendored:third_party/python/frozenlist
|
||||
vendored:third_party/python/giturlparse
|
||||
vendored:third_party/python/gyp/pylib
|
||||
vendored:third_party/python/json_e
|
||||
vendored:third_party/python/markdown_it_py
|
||||
vendored:third_party/python/mdurl
|
||||
vendored:third_party/python/mohawk
|
||||
vendored:third_party/python/mozilla_repo_urls
|
||||
vendored:third_party/python/mozilla_taskgraph
|
||||
vendored:third_party/python/multidict
|
||||
vendored:third_party/python/pathspec
|
||||
vendored:third_party/python/ply
|
||||
vendored:third_party/python/propcache
|
||||
vendored:third_party/python/pyasn1
|
||||
vendored:third_party/python/pyasn1_modules
|
||||
vendored:third_party/python/pygments
|
||||
vendored:third_party/python/pylru
|
||||
vendored:third_party/python/python_dateutil
|
||||
vendored:third_party/python/python_slugify
|
||||
vendored:third_party/python/pyyaml/lib/
|
||||
vendored:third_party/python/redo
|
||||
vendored:third_party/python/requests_unixsocket
|
||||
vendored:third_party/python/responses
|
||||
vendored:third_party/python/rich
|
||||
vendored:third_party/python/rsa
|
||||
vendored:third_party/python/slugid
|
||||
vendored:third_party/python/taskcluster
|
||||
vendored:third_party/python/taskcluster_taskgraph
|
||||
vendored:third_party/python/taskcluster_urls
|
||||
vendored:third_party/python/text_unidecode
|
||||
vendored:third_party/python/types_python_dateutil
|
||||
vendored:third_party/python/voluptuous
|
||||
vendored:third_party/python/yamllint
|
||||
vendored:third_party/python/yarl
|
||||
vendored:third_party/python/yarl
|
||||
@@ -1,6 +1,7 @@
|
||||
requires-python:>=3.9
|
||||
pth:taskcluster/gecko_taskgraph
|
||||
pypi:psutil==5.9.4
|
||||
requirements-txt:taskcluster/requirements.txt
|
||||
vendored:testing/web-platform/tests/tools/third_party/h2/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/hpack/src
|
||||
vendored:testing/web-platform/tests/tools/third_party/html5lib
|
||||
@@ -10,15 +11,12 @@ vendored:testing/web-platform/tests/tools/third_party/webencodings
|
||||
vendored:testing/web-platform/tests/tools/wptrunner
|
||||
vendored:testing/web-platform/tests/tools/wptserve
|
||||
vendored:third_party/python/aiohttp
|
||||
vendored:third_party/python/async_timeout
|
||||
vendored:third_party/python/giturlparse
|
||||
vendored:third_party/python/mohawk
|
||||
vendored:third_party/python/mozilla_repo_urls
|
||||
vendored:third_party/python/mozilla_taskgraph
|
||||
vendored:third_party/python/python_dateutil
|
||||
vendored:third_party/python/redo
|
||||
vendored:third_party/python/slugid
|
||||
vendored:third_party/python/taskcluster
|
||||
vendored:third_party/python/taskcluster_taskgraph
|
||||
vendored:third_party/python/taskcluster_urls
|
||||
vendored:third_party/python/voluptuous
|
||||
|
||||
@@ -62,10 +62,8 @@ COPY --chown=worker topsrcdir/taskcluster/docker/recipes/dot-config /builds/work
|
||||
|
||||
# %include taskcluster/scripts/run-task
|
||||
COPY topsrcdir/taskcluster/scripts/run-task /builds/worker/bin/run-task-hg
|
||||
# %include third_party/python/taskcluster_taskgraph/taskgraph/run-task/run-task
|
||||
COPY topsrcdir/third_party/python/taskcluster_taskgraph/taskgraph/run-task/run-task /builds/worker/bin/run-task-git
|
||||
|
||||
# %include third_party/python/taskcluster_taskgraph/taskgraph/run-task/fetch-content
|
||||
ADD topsrcdir/third_party/python/taskcluster_taskgraph/taskgraph/run-task/fetch-content /builds/worker/bin/fetch-content
|
||||
# Setup upstream `run-task` and `fetch-content`
|
||||
# %include-run-task
|
||||
|
||||
RUN chown -R worker:worker /builds/worker/bin && chmod 755 /builds/worker/bin/*
|
||||
|
||||
@@ -8,12 +8,8 @@ apt-get update
|
||||
apt-get install \
|
||||
python-is-python3 \
|
||||
sudo \
|
||||
python3-yaml \
|
||||
python3-pip
|
||||
python3-yaml
|
||||
|
||||
pip install --break-system-packages --disable-pip-version-check --quiet --no-cache-dir orjson==3.10.15 rtoml==0.13.0
|
||||
|
||||
apt-get remove --purge python3-pip
|
||||
apt-get autoremove --purge
|
||||
apt-get clean
|
||||
apt-get autoclean
|
||||
|
||||
@@ -28,10 +28,7 @@ RUN /usr/local/sbin/setup_packages.sh $TASKCLUSTER_ROOT_URL $DOCKER_IMAGE_PACKAG
|
||||
|
||||
# %include taskcluster/scripts/run-task
|
||||
COPY topsrcdir/taskcluster/scripts/run-task /builds/worker/bin/run-task-hg
|
||||
# %include third_party/python/taskcluster_taskgraph/taskgraph/run-task/run-task
|
||||
COPY topsrcdir/third_party/python/taskcluster_taskgraph/taskgraph/run-task/run-task /builds/worker/bin/run-task-git
|
||||
|
||||
# %include third_party/python/taskcluster_taskgraph/taskgraph/run-task/fetch-content
|
||||
ADD topsrcdir/third_party/python/taskcluster_taskgraph/taskgraph/run-task/fetch-content /builds/worker/bin/fetch-content
|
||||
# %include-run-task
|
||||
|
||||
RUN pip3 install redo==2.0.4 --break-system-packages
|
||||
|
||||
@@ -64,10 +64,7 @@ COPY topsrcdir/taskcluster/docker/recipes/dot-config /builds/worker/.config
|
||||
|
||||
# %include taskcluster/scripts/run-task
|
||||
COPY topsrcdir/taskcluster/scripts/run-task /builds/worker/bin/run-task-hg
|
||||
# %include third_party/python/taskcluster_taskgraph/taskgraph/run-task/run-task
|
||||
COPY topsrcdir/third_party/python/taskcluster_taskgraph/taskgraph/run-task/run-task /builds/worker/bin/run-task-git
|
||||
|
||||
# %include third_party/python/taskcluster_taskgraph/taskgraph/run-task/fetch-content
|
||||
ADD topsrcdir/third_party/python/taskcluster_taskgraph/taskgraph/run-task/fetch-content /builds/worker/bin/fetch-content
|
||||
# %include-run-task
|
||||
|
||||
RUN chown -R worker:worker /builds/worker/bin && chmod 755 /builds/worker/bin/*
|
||||
|
||||
@@ -31,10 +31,10 @@ documentation.
|
||||
of being merged back together.
|
||||
|
||||
Today the version of Taskgraph under ``taskcluster/gecko_taskgraph`` depends
|
||||
on the standalone version, which is vendored under
|
||||
``third_party/python/taskcluster_taskgraph``. There is still a lot of
|
||||
duplication between these places, but ``gecko_taskgraph`` is slowly being
|
||||
re-written to consume standalone Taskgraph.
|
||||
on the upstream version, which is installed as part of
|
||||
``taskcluster/requirements.txt``. There is still a lot of duplication
|
||||
between these places, but ``gecko_taskgraph`` is slowly being re-written to
|
||||
consume upstream Taskgraph.
|
||||
|
||||
The ``taskcluster`` directory contains all the files needed to define the graph
|
||||
of tasks that must be executed to build and test the Gecko tree. This is more
|
||||
|
||||
@@ -10,7 +10,7 @@ from taskgraph import config as taskgraph_config
|
||||
from taskgraph import generator
|
||||
from taskgraph import morph as taskgraph_morph
|
||||
from taskgraph.transforms.task import payload_builders
|
||||
from taskgraph.util import schema
|
||||
from taskgraph.util import docker, schema
|
||||
from taskgraph.util import taskcluster as tc_util
|
||||
|
||||
from gecko_taskgraph.config import graph_config_schema
|
||||
@@ -21,6 +21,20 @@ TEST_CONFIGS = os.path.join(GECKO, "taskcluster", "test_configs")
|
||||
# Overwrite Taskgraph's default graph_config_schema with a custom one.
|
||||
taskgraph_config.graph_config_schema = graph_config_schema
|
||||
|
||||
# Overwrite Taskgraph's RUN_TASK_SNIPPET to place the binaries in Gecko
|
||||
# specific locations.
|
||||
docker.RUN_TASK_FILES = {
|
||||
f"run-task/{path}": os.path.join(docker.RUN_TASK_ROOT, path)
|
||||
for path in [
|
||||
"run-task",
|
||||
"fetch-content",
|
||||
]
|
||||
}
|
||||
docker.RUN_TASK_SNIPPET = [
|
||||
"COPY run-task/run-task /builds/worker/bin/run-task-git\n",
|
||||
"COPY run-task/fetch-content /builds/worker/bin/fetch-content\n",
|
||||
]
|
||||
|
||||
# Don't use any of the upstream morphs.
|
||||
# TODO Investigate merging our morphs with upstream.
|
||||
taskgraph_morph.registered_morphs = []
|
||||
|
||||
@@ -10,6 +10,7 @@ import time
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
import taskgraph
|
||||
import yaml
|
||||
from redo import retry
|
||||
from taskgraph import create
|
||||
@@ -243,9 +244,7 @@ def taskgraph_decision(options, parameters=None):
|
||||
# upload run-task, fetch-content, robustcheckout.py and more as artifacts
|
||||
mozharness_dir = Path(GECKO, "testing", "mozharness")
|
||||
scripts_dir = Path(GECKO, "taskcluster", "scripts")
|
||||
taskgraph_dir = Path(
|
||||
GECKO, "third_party", "python", "taskcluster_taskgraph", "taskgraph"
|
||||
)
|
||||
taskgraph_dir = Path(taskgraph.__file__).parent
|
||||
to_copy = {
|
||||
scripts_dir / "run-task": f"{ARTIFACTS_DIR}/run-task-hg",
|
||||
scripts_dir / "tester" / "test-linux.sh": ARTIFACTS_DIR,
|
||||
|
||||
@@ -356,7 +356,7 @@ def show_taskgraph(options):
|
||||
].endswith(("taskgraph", "mozbuild")):
|
||||
del sys.modules[mod]
|
||||
|
||||
# Ensure gecko_taskgraph is ahead of taskcluster_taskgraph in sys.path.
|
||||
# Ensure gecko_taskgraph is ahead of upstream Taskgraph in sys.path.
|
||||
# Without this, we may end up validating some things against the wrong
|
||||
# schema.
|
||||
import gecko_taskgraph # noqa
|
||||
|
||||
@@ -267,8 +267,7 @@ def add_try_task_duplicates(taskgraph, label_to_taskid, parameters, graph_config
|
||||
|
||||
|
||||
# this shim function exists so we can call it from the unittests.
|
||||
# this works around an issue with
|
||||
# third_party/python/taskcluster_taskgraph/taskgraph/morph.py#40
|
||||
# this works around an issue with the morph in upstream Taskgraph
|
||||
def _add_try_task_duplicates(taskgraph, label_to_taskid, parameters, graph_config):
|
||||
try_config = parameters.get("try_task_config", {})
|
||||
tasks = try_config.get("tasks", [])
|
||||
|
||||
@@ -13,7 +13,9 @@ import hashlib
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import taskgraph
|
||||
from mozbuild.util import memoize
|
||||
from mozilla_taskgraph.util.signed_artifacts import get_signed_artifacts
|
||||
from taskcluster.utils import fromNow
|
||||
@@ -43,16 +45,8 @@ from gecko_taskgraph.util.partners import get_partners_to_be_published
|
||||
from gecko_taskgraph.util.scriptworker import BALROG_ACTIONS, get_release_config
|
||||
from gecko_taskgraph.util.workertypes import get_worker_type, worker_type_implementation
|
||||
|
||||
RUN_TASK_HG = os.path.join(GECKO, "taskcluster", "scripts", "run-task")
|
||||
RUN_TASK_GIT = os.path.join(
|
||||
GECKO,
|
||||
"third_party",
|
||||
"python",
|
||||
"taskcluster_taskgraph",
|
||||
"taskgraph",
|
||||
"run-task",
|
||||
"run-task",
|
||||
)
|
||||
RUN_TASK_HG = Path(GECKO, "taskcluster", "scripts", "run-task")
|
||||
RUN_TASK_GIT = Path(taskgraph.__file__).parent / "run-task" / "run-task"
|
||||
|
||||
SCCACHE_GCS_PROJECT = "sccache-3"
|
||||
|
||||
@@ -61,8 +55,8 @@ SCCACHE_GCS_PROJECT = "sccache-3"
|
||||
def _run_task_suffix(repo_type):
|
||||
"""String to append to cache names under control of run-task."""
|
||||
if repo_type == "hg":
|
||||
return hash_path(RUN_TASK_HG)[0:20]
|
||||
return hash_path(RUN_TASK_GIT)[0:20]
|
||||
return hash_path(str(RUN_TASK_HG))[0:20]
|
||||
return hash_path(str(RUN_TASK_GIT))[0:20]
|
||||
|
||||
|
||||
def _compute_geckoview_version(app_version, moz_build_date):
|
||||
|
||||
@@ -58,7 +58,7 @@ firefox-ci:
|
||||
- '.taskcluster.yml'
|
||||
- 'taskcluster/kinds/**'
|
||||
- 'taskcluster/**/*.py'
|
||||
- 'third_party/python/taskcluster_taskgraph/**/*.py'
|
||||
- 'taskcluster/requirements.txt'
|
||||
- 'tools/tryselect/selectors/auto.py'
|
||||
|
||||
fog:
|
||||
@@ -354,7 +354,7 @@ taskgraph-tests:
|
||||
files-changed:
|
||||
- 'python/mach/**/*.py'
|
||||
- 'taskcluster/**/*.py'
|
||||
- 'third_party/python/taskcluster_taskgraph/**/*.py'
|
||||
- 'taskcluster/requirements.txt'
|
||||
|
||||
tryselect:
|
||||
description: tools/tryselect unit tests
|
||||
@@ -371,7 +371,7 @@ tryselect:
|
||||
- 'taskcluster/config.yml'
|
||||
- 'taskcluster/kinds/test/**'
|
||||
- 'taskcluster/gecko_taskgraph/transforms/**'
|
||||
- 'third_party/python/taskcluster_taskgraph/**/*.py'
|
||||
- 'taskcluster/requirements.txt'
|
||||
- 'tools/tryselect/**'
|
||||
|
||||
mozbuild:
|
||||
@@ -649,8 +649,6 @@ verify-decision:
|
||||
- .cron.yml
|
||||
- .taskcluster.yml
|
||||
- 'taskcluster/**'
|
||||
- 'third_party/python/taskcluster/**'
|
||||
- 'third_party/python/taskcluster_taskgraph/**'
|
||||
|
||||
webext:
|
||||
description: WebExtensions python utilities unit tests
|
||||
|
||||
@@ -42,4 +42,4 @@ diff:
|
||||
files-changed:
|
||||
- 'taskcluster/kinds/**'
|
||||
- 'taskcluster/**/*.py'
|
||||
- 'third_party/python/taskcluster_taskgraph/**/*.py'
|
||||
- 'taskcluster/requirements.txt'
|
||||
|
||||
@@ -13,17 +13,10 @@ import time
|
||||
import traceback
|
||||
from functools import partial
|
||||
|
||||
import gecko_taskgraph.main
|
||||
from gecko_taskgraph.files_changed import get_locally_changed_files
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
from mach.decorators import Command, CommandArgument, SubCommand
|
||||
from mach.util import strtobool
|
||||
from mozsystemmonitor.resourcemonitor import SystemResourceMonitor
|
||||
|
||||
# We're likely going to need the result of get_locally_changed_files, and it
|
||||
# takes time to finish, so prefetch it as soon as possible.
|
||||
get_locally_changed_files.preload(os.getcwd())
|
||||
|
||||
|
||||
def setup_logging(command_context, quiet=False, verbose=True):
|
||||
"""
|
||||
@@ -58,6 +51,8 @@ def get_taskgraph_command_parser(name):
|
||||
Returns:
|
||||
ArgumentParser: An ArgumentParser instance.
|
||||
"""
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
command = taskgraph_commands[name]
|
||||
parser = argparse.ArgumentParser()
|
||||
for arg in command.func.args:
|
||||
@@ -111,6 +106,7 @@ def get_taskgraph_decision_parser():
|
||||
"taskgraph",
|
||||
category="ci",
|
||||
description="Manipulate TaskCluster task graphs defined in-tree",
|
||||
virtualenv_name="taskgraph",
|
||||
)
|
||||
def taskgraph_command(command_context):
|
||||
"""The taskgraph subcommands all relate to the generation of task graphs
|
||||
@@ -127,6 +123,8 @@ def taskgraph_command(command_context):
|
||||
parser=partial(get_taskgraph_command_parser, "kind-graph"),
|
||||
)
|
||||
def taskgraph_kind_graph(command_context, **options):
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
try:
|
||||
setup_logging(command_context)
|
||||
return taskgraph_commands["kind-graph"].func(options)
|
||||
@@ -196,6 +194,8 @@ def taskgraph_morphed(command_context, **options):
|
||||
|
||||
|
||||
def run_show_taskgraph(command_context, **options):
|
||||
import gecko_taskgraph.main
|
||||
|
||||
# There are cases where we don't want to set up mach logging (e.g logs
|
||||
# are being redirected to disk). By monkeypatching the 'setup_logging'
|
||||
# function we can let 'taskgraph.main' decide whether or not to log to
|
||||
@@ -269,6 +269,8 @@ def taskgraph_decision(command_context, **options):
|
||||
and requires a great many arguments. Commands like `mach taskgraph
|
||||
optimized` are better suited to use on the command line, and can take
|
||||
the parameters file generated by a decision task."""
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
try:
|
||||
setup_logging(command_context)
|
||||
|
||||
@@ -333,6 +335,8 @@ def taskgraph_decision(command_context, **options):
|
||||
parser=partial(get_taskgraph_command_parser, "action-callback"),
|
||||
)
|
||||
def action_callback(command_context, **options):
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
setup_logging(command_context)
|
||||
taskgraph_commands["action-callback"].func(options)
|
||||
|
||||
@@ -344,6 +348,8 @@ def action_callback(command_context, **options):
|
||||
parser=partial(get_taskgraph_command_parser, "test-action-callback"),
|
||||
)
|
||||
def test_action_callback(command_context, **options):
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
setup_logging(command_context)
|
||||
|
||||
if not options["parameters"]:
|
||||
@@ -360,6 +366,8 @@ def test_action_callback(command_context, **options):
|
||||
parser=partial(get_taskgraph_command_parser, "load-image"),
|
||||
)
|
||||
def load_image(command_context, **kwargs):
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
setup_logging(command_context)
|
||||
taskgraph_commands["load-image"].func(kwargs)
|
||||
|
||||
@@ -371,6 +379,8 @@ def load_image(command_context, **kwargs):
|
||||
parser=partial(get_taskgraph_command_parser, "build-image"),
|
||||
)
|
||||
def build_image(command_context, **kwargs):
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
setup_logging(command_context)
|
||||
try:
|
||||
taskgraph_commands["build-image"].func(kwargs)
|
||||
@@ -387,6 +397,8 @@ def build_image(command_context, **kwargs):
|
||||
parser=partial(get_taskgraph_command_parser, "build-image"),
|
||||
)
|
||||
def image_digest(command_context, **kwargs):
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
setup_logging(command_context)
|
||||
taskgraph_commands["image-digest"].func(kwargs)
|
||||
|
||||
@@ -401,6 +413,8 @@ def image_digest(command_context, **kwargs):
|
||||
parser=partial(get_taskgraph_command_parser, "load-task"),
|
||||
)
|
||||
def load_task(command_context, **kwargs):
|
||||
from gecko_taskgraph.main import commands as taskgraph_commands
|
||||
|
||||
setup_logging(command_context)
|
||||
taskgraph_commands["load-task"].func(kwargs)
|
||||
|
||||
@@ -409,6 +423,7 @@ def load_task(command_context, **kwargs):
|
||||
"release-history",
|
||||
category="ci",
|
||||
description="Query balrog for release history used by enable partials generation",
|
||||
virtualenv_name="try",
|
||||
)
|
||||
@CommandArgument(
|
||||
"-b",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
-c ../third_party/python/requirements.txt
|
||||
|
||||
mozilla-taskgraph==4.0.1
|
||||
requests-unixsocket
|
||||
taskcluster-taskgraph[orjson,load-image]==18.0.3
|
||||
rtoml==0.13.0; python_version >= "3.10"
|
||||
# Temporarily resolve conflict with myst-parser 2.0.0 in python/sites/docs.txt
|
||||
markdown-it-py < 4
|
||||
File diff suppressed because it is too large
Load Diff
@@ -577,16 +577,6 @@ def fetch_artifacts():
|
||||
print_line(b"fetches", b"fetching artifacts\n")
|
||||
|
||||
fetch_content = shutil.which("fetch-content")
|
||||
if not fetch_content and os.environ.get("GECKO_PATH"):
|
||||
fetch_content = os.path.join(
|
||||
os.environ["GECKO_PATH"],
|
||||
"third_party",
|
||||
"python",
|
||||
"taskcluster_taskgraph",
|
||||
"taskgraph",
|
||||
"run-task",
|
||||
"fetch-content",
|
||||
)
|
||||
|
||||
if not fetch_content or not os.path.isfile(fetch_content):
|
||||
fetch_content = os.path.join(os.path.dirname(__file__), "fetch-content")
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
#
|
||||
# For each key, if it is a string the task's attribute must match exactly. If
|
||||
# it is a list, the task's attribute must be a value contained in the list.
|
||||
# For more information, see the `attrmatch` function in:
|
||||
# https://searchfox.org/mozilla-central/source/third_party/python/taskcluster_taskgraph/taskgraph/util/attributes.py
|
||||
# For more information, see the `attrmatch` function in `taskgraph.util.attributes`.
|
||||
|
||||
### Unittests
|
||||
- kind: &unittest-kinds
|
||||
|
||||
@@ -183,6 +183,7 @@ class PlatformInfo:
|
||||
filename = (
|
||||
os.environ.get("GECKO_PATH", ".") + "/taskcluster/test_configs/variants.yml"
|
||||
)
|
||||
|
||||
with open(filename) as f:
|
||||
PlatformInfo.variant_data = yaml.safe_load(f.read())
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from os import environ, makedirs
|
||||
from pathlib import Path
|
||||
from shutil import copytree, unpack_archive
|
||||
@@ -16,12 +17,22 @@ from shutil import copytree, unpack_archive
|
||||
import mozinfo
|
||||
import mozinstall
|
||||
import requests
|
||||
from gecko_taskgraph.transforms.update_test import ReleaseType
|
||||
from mach.decorators import Command, CommandArgument
|
||||
from mozbuild.base import BinaryNotFoundException
|
||||
from mozlog.structured import commandline
|
||||
from mozrelease.update_verify import UpdateVerifyConfig
|
||||
|
||||
|
||||
class ReleaseType(Enum):
|
||||
"""Release type - duplicated from gecko_taskgraph.transforms.update_test
|
||||
to avoid importing taskgraph dependencies at mach command load time."""
|
||||
|
||||
release = 0
|
||||
beta = 1
|
||||
esr = 2
|
||||
other = 3
|
||||
|
||||
|
||||
STAGING_POLICY_PAYLOAD = {
|
||||
"policies": {
|
||||
"AppUpdateURL": "https://stage.balrog.nonprod.cloudops.mozgcp.net/update/6/Firefox/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml"
|
||||
|
||||
+450
-1
@@ -10,6 +10,455 @@
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
3.13.3 (2026-01-03)
|
||||
===================
|
||||
|
||||
This release contains fixes for several vulnerabilities. It is advised to
|
||||
upgrade as soon as possible.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixed proxy authorization headers not being passed when reusing a connection, which caused 407 (Proxy authentication required) errors
|
||||
-- by :user:`GLeurquin`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`2596`.
|
||||
|
||||
|
||||
|
||||
- Fixed multipart reading failing when encountering an empty body part -- by :user:`Dreamsorcerer`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11857`.
|
||||
|
||||
|
||||
|
||||
- Fixed a case where the parser wasn't raising an exception for a websocket continuation frame when there was no initial frame in context.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11862`.
|
||||
|
||||
|
||||
|
||||
|
||||
Removals and backward incompatible breaking changes
|
||||
---------------------------------------------------
|
||||
|
||||
- ``Brotli`` and ``brotlicffi`` minimum version is now 1.2.
|
||||
Decompression now has a default maximum output size of 32MiB per decompress call -- by :user:`Dreamsorcerer`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11898`.
|
||||
|
||||
|
||||
|
||||
|
||||
Packaging updates and notes for downstreams
|
||||
-------------------------------------------
|
||||
|
||||
- Moved dependency metadata from :file:`setup.cfg` to :file:`pyproject.toml` per :pep:`621`
|
||||
-- by :user:`cdce8p`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11643`.
|
||||
|
||||
|
||||
|
||||
|
||||
Contributor-facing changes
|
||||
--------------------------
|
||||
|
||||
- Removed unused ``update-pre-commit`` github action workflow -- by :user:`Cycloctane`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11689`.
|
||||
|
||||
|
||||
|
||||
|
||||
Miscellaneous internal changes
|
||||
------------------------------
|
||||
|
||||
- Optimized web server performance when access logging is disabled by reducing time syscalls -- by :user:`bdraco`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`10713`.
|
||||
|
||||
|
||||
|
||||
- Added regression test for cached logging status -- by :user:`meehand`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11778`.
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
3.13.2 (2025-10-28)
|
||||
===================
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixed cookie parser to continue parsing subsequent cookies when encountering a malformed cookie that fails regex validation, such as Google's ``g_state`` cookie with unescaped quotes -- by :user:`bdraco`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11632`.
|
||||
|
||||
|
||||
|
||||
- Fixed loading netrc credentials from the default :file:`~/.netrc` (:file:`~/_netrc` on Windows) location when the :envvar:`NETRC` environment variable is not set -- by :user:`bdraco`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11713`, :issue:`11714`.
|
||||
|
||||
|
||||
|
||||
- Fixed WebSocket compressed sends to be cancellation safe. Tasks are now shielded during compression to prevent compressor state corruption. This ensures that the stateful compressor remains consistent even when send operations are cancelled -- by :user:`bdraco`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11725`.
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
3.13.1 (2025-10-17)
|
||||
===================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Make configuration options in ``AppRunner`` also available in ``run_app()``
|
||||
-- by :user:`Cycloctane`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11633`.
|
||||
|
||||
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Switched to `backports.zstd` for Python <3.14 and fixed zstd decompression for chunked zstd streams -- by :user:`ZhaoMJ`.
|
||||
|
||||
Note: Users who installed ``zstandard`` for support on Python <3.14 will now need to install
|
||||
``backports.zstd`` instead (installing ``aiohttp[speedups]`` will do this automatically).
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11623`.
|
||||
|
||||
|
||||
|
||||
- Updated ``Content-Type`` header parsing to return ``application/octet-stream`` when header contains invalid syntax.
|
||||
See :rfc:`9110#section-8.3-5`.
|
||||
|
||||
-- by :user:`sgaist`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`10889`.
|
||||
|
||||
|
||||
|
||||
- Fixed Python 3.14 support when built without ``zstd`` support -- by :user:`JacobHenner`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11603`.
|
||||
|
||||
|
||||
|
||||
- Fixed blocking I/O in the event loop when using netrc authentication by moving netrc file lookup to an executor -- by :user:`bdraco`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11634`.
|
||||
|
||||
|
||||
|
||||
- Fixed routing to a sub-application added via ``.add_domain()`` not working
|
||||
if the same path exists on the parent app. -- by :user:`Dreamsorcerer`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11673`.
|
||||
|
||||
|
||||
|
||||
|
||||
Packaging updates and notes for downstreams
|
||||
-------------------------------------------
|
||||
|
||||
- Moved core packaging metadata from :file:`setup.cfg` to :file:`pyproject.toml` per :pep:`621`
|
||||
-- by :user:`cdce8p`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`9951`.
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
3.13.0 (2025-10-06)
|
||||
===================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Added support for Python 3.14.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`10851`, :issue:`10872`.
|
||||
|
||||
|
||||
|
||||
- Added support for free-threading in Python 3.14+ -- by :user:`kumaraditya303`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11466`, :issue:`11464`.
|
||||
|
||||
|
||||
|
||||
- Added support for Zstandard (aka Zstd) compression
|
||||
-- by :user:`KGuillaume-chaps`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11161`.
|
||||
|
||||
|
||||
|
||||
- Added ``StreamReader.total_raw_bytes`` to check the number of bytes downloaded
|
||||
-- by :user:`robpats`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11483`.
|
||||
|
||||
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixed pytest plugin to not use deprecated :py:mod:`asyncio` policy APIs.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`10851`.
|
||||
|
||||
|
||||
|
||||
- Updated `Content-Disposition` header parsing to handle trailing semicolons and empty parts
|
||||
-- by :user:`PLPeeters`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11243`.
|
||||
|
||||
|
||||
|
||||
- Fixed saved ``CookieJar`` failing to be loaded if cookies have ``partitioned`` flag when
|
||||
``http.cookie`` does not have partitioned cookies supports. -- by :user:`Cycloctane`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11523`.
|
||||
|
||||
|
||||
|
||||
|
||||
Improved documentation
|
||||
----------------------
|
||||
|
||||
- Added ``Wireup`` to third-party libraries -- by :user:`maldoinc`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11233`.
|
||||
|
||||
|
||||
|
||||
|
||||
Packaging updates and notes for downstreams
|
||||
-------------------------------------------
|
||||
|
||||
- The `blockbuster` test dependency is now optional; the corresponding test fixture is disabled when it is unavailable
|
||||
-- by :user:`musicinybrain`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11363`.
|
||||
|
||||
|
||||
|
||||
- Added ``riscv64`` build to releases -- by :user:`eshattow`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11425`.
|
||||
|
||||
|
||||
|
||||
|
||||
Contributor-facing changes
|
||||
--------------------------
|
||||
|
||||
|
||||
|
||||
- Fixed ``test_send_compress_text`` failing when alternative zlib implementation
|
||||
is used. (``zlib-ng`` in python 3.14 windows build) -- by :user:`Cycloctane`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11546`.
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
3.12.15 (2025-07-28)
|
||||
====================
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixed :class:`~aiohttp.DigestAuthMiddleware` to preserve the algorithm case from the server's challenge in the authorization response. This improves compatibility with servers that perform case-sensitive algorithm matching (e.g., servers expecting ``algorithm=MD5-sess`` instead of ``algorithm=MD5-SESS``)
|
||||
-- by :user:`bdraco`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11352`.
|
||||
|
||||
|
||||
|
||||
|
||||
Improved documentation
|
||||
----------------------
|
||||
|
||||
- Remove outdated contents of ``aiohttp-devtools`` and ``aiohttp-swagger``
|
||||
from Web_advanced docs.
|
||||
-- by :user:`Cycloctane`
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11347`.
|
||||
|
||||
|
||||
|
||||
|
||||
Packaging updates and notes for downstreams
|
||||
-------------------------------------------
|
||||
|
||||
- Started including the ``llhttp`` :file:`LICENSE` file in wheels by adding ``vendor/llhttp/LICENSE`` to ``license-files`` in :file:`setup.cfg` -- by :user:`threexc`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11226`.
|
||||
|
||||
|
||||
|
||||
|
||||
Contributor-facing changes
|
||||
--------------------------
|
||||
|
||||
- Updated a regex in `test_aiohttp_request_coroutine` for Python 3.14.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11271`.
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
3.12.14 (2025-07-10)
|
||||
====================
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixed file uploads failing with HTTP 422 errors when encountering 307/308 redirects, and 301/302 redirects for non-POST methods, by preserving the request body when appropriate per :rfc:`9110#section-15.4.3-3.1` -- by :user:`bdraco`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11270`.
|
||||
|
||||
|
||||
|
||||
- Fixed :py:meth:`ClientSession.close() <aiohttp.ClientSession.close>` hanging indefinitely when using HTTPS requests through HTTP proxies -- by :user:`bdraco`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11273`.
|
||||
|
||||
|
||||
|
||||
- Bumped minimum version of aiosignal to 1.4+ to resolve typing issues -- by :user:`Dreamsorcerer`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11280`.
|
||||
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Added initial trailer parsing logic to Python HTTP parser -- by :user:`Dreamsorcerer`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11269`.
|
||||
|
||||
|
||||
|
||||
|
||||
Improved documentation
|
||||
----------------------
|
||||
|
||||
- Clarified exceptions raised by ``WebSocketResponse.send_frame`` et al.
|
||||
-- by :user:`DoctorJohn`.
|
||||
|
||||
|
||||
*Related issues and pull requests on GitHub:*
|
||||
:issue:`11234`.
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
3.12.13 (2025-06-14)
|
||||
====================
|
||||
|
||||
@@ -4309,7 +4758,7 @@ Bugfixes
|
||||
`#5853 <https://github.com/aio-libs/aiohttp/issues/5853>`_
|
||||
- Added ``params`` keyword argument to ``ClientSession.ws_connect``. -- :user:`hoh`.
|
||||
`#5868 <https://github.com/aio-libs/aiohttp/issues/5868>`_
|
||||
- Uses :py:class:`~asyncio.ThreadedChildWatcher` under POSIX to allow setting up test loop in non-main thread.
|
||||
- Uses ``asyncio.ThreadedChildWatcher`` under POSIX to allow setting up test loop in non-main thread.
|
||||
`#5877 <https://github.com/aio-libs/aiohttp/issues/5877>`_
|
||||
- Fix the error in handling the return value of `getaddrinfo`.
|
||||
`getaddrinfo` will return an `(int, bytes)` tuple, if CPython could not handle the address family.
|
||||
|
||||
+10
@@ -57,6 +57,7 @@ Arthur Darcet
|
||||
Austin Scola
|
||||
Bai Haoran
|
||||
Ben Bader
|
||||
Ben Beasley
|
||||
Ben Greiner
|
||||
Ben Kallus
|
||||
Ben Timby
|
||||
@@ -142,6 +143,7 @@ Gennady Andreyev
|
||||
Georges Dubus
|
||||
Greg Holt
|
||||
Gregory Haynes
|
||||
Guillaume Leurquin
|
||||
Gus Goulart
|
||||
Gustavo Carneiro
|
||||
Günther Jena
|
||||
@@ -169,6 +171,7 @@ Ivan Lakovic
|
||||
Ivan Larin
|
||||
J. Nick Koston
|
||||
Jacob Champion
|
||||
Jacob Henner
|
||||
Jaesung Lee
|
||||
Jake Davis
|
||||
Jakob Ackermann
|
||||
@@ -211,9 +214,11 @@ Justin Foo
|
||||
Justin Turner Arthur
|
||||
Kay Zheng
|
||||
Kevin Samuel
|
||||
Kilian Guillaume
|
||||
Kimmo Parviainen-Jalanko
|
||||
Kirill Klenov
|
||||
Kirill Malovitsa
|
||||
Kirill Potapenko
|
||||
Konstantin Shutkin
|
||||
Konstantin Valetov
|
||||
Krzysztof Blazewicz
|
||||
@@ -238,6 +243,7 @@ Marco Paolini
|
||||
Marcus Stojcevich
|
||||
Mariano Anaya
|
||||
Mariusz Masztalerczuk
|
||||
Mark Larah
|
||||
Marko Kohtala
|
||||
Martijn Pieters
|
||||
Martin Melka
|
||||
@@ -259,6 +265,7 @@ Mikhail Burshteyn
|
||||
Mikhail Kashkin
|
||||
Mikhail Lukyanchenko
|
||||
Mikhail Nacharov
|
||||
Mingjie Zhao
|
||||
Misha Behersky
|
||||
Mitchell Ferree
|
||||
Morgan Delahaye-Prat
|
||||
@@ -276,6 +283,7 @@ Pahaz Blinov
|
||||
Panagiotis Kolokotronis
|
||||
Pankaj Pandey
|
||||
Parag Jain
|
||||
Patrick Lee
|
||||
Pau Freixes
|
||||
Paul Colomiets
|
||||
Paul J. Dorn
|
||||
@@ -305,6 +313,7 @@ Roman Postnov
|
||||
Rong Zhang
|
||||
Samir Akarioh
|
||||
Samuel Colvin
|
||||
Samuel Gaist
|
||||
Sean Hunt
|
||||
Sebastian Acuna
|
||||
Sebastian Hanula
|
||||
@@ -343,6 +352,7 @@ Tim Menninger
|
||||
Tolga Tezel
|
||||
Tomasz Trebski
|
||||
Toshiaki Tanaka
|
||||
Trevor Gamblin
|
||||
Trinh Hoang Nhu
|
||||
Tymofii Tsiapa
|
||||
Vadim Suharnikov
|
||||
|
||||
+1
-2
@@ -9,8 +9,7 @@ graft examples
|
||||
graft tests
|
||||
graft tools
|
||||
graft requirements
|
||||
recursive-include vendor *
|
||||
global-include aiohttp *.pyi
|
||||
graft vendor
|
||||
global-exclude *.pyc
|
||||
global-exclude *.pyd
|
||||
global-exclude *.so
|
||||
|
||||
Vendored
+4
-4
@@ -59,14 +59,14 @@ aiohttp/_find_header.c: $(call to-hash,aiohttp/hdrs.py ./tools/gen.py)
|
||||
# Special case for reader since we want to be able to disable
|
||||
# the extension with AIOHTTP_NO_EXTENSIONS
|
||||
aiohttp/_websocket/reader_c.c: aiohttp/_websocket/reader_c.py
|
||||
cython -3 -o $@ $< -I aiohttp -Werror
|
||||
cython -3 -X freethreading_compatible=True -o $@ $< -I aiohttp -Werror
|
||||
|
||||
# _find_headers generator creates _headers.pyi as well
|
||||
aiohttp/%.c: aiohttp/%.pyx $(call to-hash,$(CYS)) aiohttp/_find_header.c
|
||||
cython -3 -o $@ $< -I aiohttp -Werror
|
||||
cython -3 -X freethreading_compatible=True -o $@ $< -I aiohttp -Werror
|
||||
|
||||
aiohttp/_websocket/%.c: aiohttp/_websocket/%.pyx $(call to-hash,$(CYS))
|
||||
cython -3 -o $@ $< -I aiohttp -Werror
|
||||
cython -3 -X freethreading_compatible=True -o $@ $< -I aiohttp -Werror
|
||||
|
||||
vendor/llhttp/node_modules: vendor/llhttp/package.json
|
||||
cd vendor/llhttp; npm ci
|
||||
@@ -189,5 +189,5 @@ install-dev: .develop
|
||||
|
||||
.PHONY: sync-direct-runtime-deps
|
||||
sync-direct-runtime-deps:
|
||||
@echo Updating 'requirements/runtime-deps.in' from 'setup.cfg'... >&2
|
||||
@echo Updating 'requirements/runtime-deps.in' from 'pyproject.toml'... >&2
|
||||
@python requirements/sync-direct-runtime-deps.py
|
||||
|
||||
Vendored
+31
-19
@@ -1,11 +1,10 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: aiohttp
|
||||
Version: 3.12.13
|
||||
Version: 3.13.3
|
||||
Summary: Async http client/server framework (asyncio)
|
||||
Home-page: https://github.com/aio-libs/aiohttp
|
||||
Maintainer: aiohttp team <team@aiohttp.org>
|
||||
Maintainer-email: team@aiohttp.org
|
||||
License: Apache-2.0
|
||||
Maintainer-email: aiohttp team <team@aiohttp.org>
|
||||
License: Apache-2.0 AND MIT
|
||||
Project-URL: Homepage, https://github.com/aio-libs/aiohttp
|
||||
Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org
|
||||
Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org
|
||||
Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI
|
||||
@@ -27,12 +26,14 @@ Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Classifier: Topic :: Internet :: WWW/HTTP
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE.txt
|
||||
License-File: vendor/llhttp/LICENSE
|
||||
Requires-Dist: aiohappyeyeballs>=2.5.0
|
||||
Requires-Dist: aiosignal>=1.1.2
|
||||
Requires-Dist: aiosignal>=1.4.0
|
||||
Requires-Dist: async-timeout<6.0,>=4.0; python_version < "3.11"
|
||||
Requires-Dist: attrs>=17.3.0
|
||||
Requires-Dist: frozenlist>=1.1.1
|
||||
@@ -41,8 +42,9 @@ Requires-Dist: propcache>=0.2.0
|
||||
Requires-Dist: yarl<2.0,>=1.17.0
|
||||
Provides-Extra: speedups
|
||||
Requires-Dist: aiodns>=3.3.0; extra == "speedups"
|
||||
Requires-Dist: Brotli; platform_python_implementation == "CPython" and extra == "speedups"
|
||||
Requires-Dist: brotlicffi; platform_python_implementation != "CPython" and extra == "speedups"
|
||||
Requires-Dist: Brotli>=1.2; platform_python_implementation == "CPython" and extra == "speedups"
|
||||
Requires-Dist: brotlicffi>=1.2; platform_python_implementation != "CPython" and extra == "speedups"
|
||||
Requires-Dist: backports.zstd; (platform_python_implementation == "CPython" and python_version < "3.14") and extra == "speedups"
|
||||
Dynamic: license-file
|
||||
|
||||
==================================
|
||||
@@ -64,25 +66,21 @@ Async http client/server framework
|
||||
:target: https://codecov.io/gh/aio-libs/aiohttp
|
||||
:alt: codecov.io status for master branch
|
||||
|
||||
.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
|
||||
:target: https://codspeed.io/aio-libs/aiohttp
|
||||
:alt: Codspeed.io status for aiohttp
|
||||
|
||||
.. image:: https://badge.fury.io/py/aiohttp.svg
|
||||
:target: https://pypi.org/project/aiohttp
|
||||
:alt: Latest PyPI package version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/aiohttp
|
||||
:target: https://pypistats.org/packages/aiohttp
|
||||
:alt: Downloads count
|
||||
|
||||
.. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest
|
||||
:target: https://docs.aiohttp.org/
|
||||
:alt: Latest Read The Docs
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs:matrix.org
|
||||
:alt: Matrix Room — #aio-libs:matrix.org
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs-space:matrix.org
|
||||
:alt: Matrix Space — #aio-libs-space:matrix.org
|
||||
.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
|
||||
:target: https://codspeed.io/aio-libs/aiohttp
|
||||
:alt: Codspeed.io status for aiohttp
|
||||
|
||||
|
||||
Key Features
|
||||
@@ -248,3 +246,17 @@ Benchmarks
|
||||
If you are interested in efficiency, the AsyncIO community maintains a
|
||||
list of benchmarks on the official wiki:
|
||||
https://github.com/python/asyncio/wiki/Benchmarks
|
||||
|
||||
--------
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs:matrix.org
|
||||
:alt: Matrix Room — #aio-libs:matrix.org
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs-space:matrix.org
|
||||
:alt: Matrix Space — #aio-libs-space:matrix.org
|
||||
|
||||
.. image:: https://insights.linuxfoundation.org/api/badge/health-score?project=aiohttp
|
||||
:target: https://insights.linuxfoundation.org/project/aiohttp
|
||||
:alt: LFX Health Score
|
||||
|
||||
+21
-11
@@ -17,25 +17,21 @@ Async http client/server framework
|
||||
:target: https://codecov.io/gh/aio-libs/aiohttp
|
||||
:alt: codecov.io status for master branch
|
||||
|
||||
.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
|
||||
:target: https://codspeed.io/aio-libs/aiohttp
|
||||
:alt: Codspeed.io status for aiohttp
|
||||
|
||||
.. image:: https://badge.fury.io/py/aiohttp.svg
|
||||
:target: https://pypi.org/project/aiohttp
|
||||
:alt: Latest PyPI package version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/aiohttp
|
||||
:target: https://pypistats.org/packages/aiohttp
|
||||
:alt: Downloads count
|
||||
|
||||
.. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest
|
||||
:target: https://docs.aiohttp.org/
|
||||
:alt: Latest Read The Docs
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs:matrix.org
|
||||
:alt: Matrix Room — #aio-libs:matrix.org
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs-space:matrix.org
|
||||
:alt: Matrix Space — #aio-libs-space:matrix.org
|
||||
.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
|
||||
:target: https://codspeed.io/aio-libs/aiohttp
|
||||
:alt: Codspeed.io status for aiohttp
|
||||
|
||||
|
||||
Key Features
|
||||
@@ -201,3 +197,17 @@ Benchmarks
|
||||
If you are interested in efficiency, the AsyncIO community maintains a
|
||||
list of benchmarks on the official wiki:
|
||||
https://github.com/python/asyncio/wiki/Benchmarks
|
||||
|
||||
--------
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs:matrix.org
|
||||
:alt: Matrix Room — #aio-libs:matrix.org
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs-space:matrix.org
|
||||
:alt: Matrix Space — #aio-libs-space:matrix.org
|
||||
|
||||
.. image:: https://insights.linuxfoundation.org/api/badge/health-score?project=aiohttp
|
||||
:target: https://insights.linuxfoundation.org/project/aiohttp
|
||||
:alt: LFX Health Score
|
||||
|
||||
+31
-19
@@ -1,11 +1,10 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: aiohttp
|
||||
Version: 3.12.13
|
||||
Version: 3.13.3
|
||||
Summary: Async http client/server framework (asyncio)
|
||||
Home-page: https://github.com/aio-libs/aiohttp
|
||||
Maintainer: aiohttp team <team@aiohttp.org>
|
||||
Maintainer-email: team@aiohttp.org
|
||||
License: Apache-2.0
|
||||
Maintainer-email: aiohttp team <team@aiohttp.org>
|
||||
License: Apache-2.0 AND MIT
|
||||
Project-URL: Homepage, https://github.com/aio-libs/aiohttp
|
||||
Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org
|
||||
Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org
|
||||
Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI
|
||||
@@ -27,12 +26,14 @@ Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Classifier: Topic :: Internet :: WWW/HTTP
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE.txt
|
||||
License-File: vendor/llhttp/LICENSE
|
||||
Requires-Dist: aiohappyeyeballs>=2.5.0
|
||||
Requires-Dist: aiosignal>=1.1.2
|
||||
Requires-Dist: aiosignal>=1.4.0
|
||||
Requires-Dist: async-timeout<6.0,>=4.0; python_version < "3.11"
|
||||
Requires-Dist: attrs>=17.3.0
|
||||
Requires-Dist: frozenlist>=1.1.1
|
||||
@@ -41,8 +42,9 @@ Requires-Dist: propcache>=0.2.0
|
||||
Requires-Dist: yarl<2.0,>=1.17.0
|
||||
Provides-Extra: speedups
|
||||
Requires-Dist: aiodns>=3.3.0; extra == "speedups"
|
||||
Requires-Dist: Brotli; platform_python_implementation == "CPython" and extra == "speedups"
|
||||
Requires-Dist: brotlicffi; platform_python_implementation != "CPython" and extra == "speedups"
|
||||
Requires-Dist: Brotli>=1.2; platform_python_implementation == "CPython" and extra == "speedups"
|
||||
Requires-Dist: brotlicffi>=1.2; platform_python_implementation != "CPython" and extra == "speedups"
|
||||
Requires-Dist: backports.zstd; (platform_python_implementation == "CPython" and python_version < "3.14") and extra == "speedups"
|
||||
Dynamic: license-file
|
||||
|
||||
==================================
|
||||
@@ -64,25 +66,21 @@ Async http client/server framework
|
||||
:target: https://codecov.io/gh/aio-libs/aiohttp
|
||||
:alt: codecov.io status for master branch
|
||||
|
||||
.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
|
||||
:target: https://codspeed.io/aio-libs/aiohttp
|
||||
:alt: Codspeed.io status for aiohttp
|
||||
|
||||
.. image:: https://badge.fury.io/py/aiohttp.svg
|
||||
:target: https://pypi.org/project/aiohttp
|
||||
:alt: Latest PyPI package version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/aiohttp
|
||||
:target: https://pypistats.org/packages/aiohttp
|
||||
:alt: Downloads count
|
||||
|
||||
.. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest
|
||||
:target: https://docs.aiohttp.org/
|
||||
:alt: Latest Read The Docs
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs:matrix.org
|
||||
:alt: Matrix Room — #aio-libs:matrix.org
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs-space:matrix.org
|
||||
:alt: Matrix Space — #aio-libs-space:matrix.org
|
||||
.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
|
||||
:target: https://codspeed.io/aio-libs/aiohttp
|
||||
:alt: Codspeed.io status for aiohttp
|
||||
|
||||
|
||||
Key Features
|
||||
@@ -248,3 +246,17 @@ Benchmarks
|
||||
If you are interested in efficiency, the AsyncIO community maintains a
|
||||
list of benchmarks on the official wiki:
|
||||
https://github.com/python/asyncio/wiki/Benchmarks
|
||||
|
||||
--------
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs:matrix.org
|
||||
:alt: Matrix Room — #aio-libs:matrix.org
|
||||
|
||||
.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
|
||||
:target: https://matrix.to/#/%23aio-libs-space:matrix.org
|
||||
:alt: Matrix Space — #aio-libs-space:matrix.org
|
||||
|
||||
.. image:: https://insights.linuxfoundation.org/api/badge/health-score?project=aiohttp
|
||||
:target: https://insights.linuxfoundation.org/project/aiohttp
|
||||
:alt: LFX Health Score
|
||||
|
||||
+6
-1
@@ -68,7 +68,6 @@ aiohttp/worker.py
|
||||
aiohttp.egg-info/PKG-INFO
|
||||
aiohttp.egg-info/SOURCES.txt
|
||||
aiohttp.egg-info/dependency_links.txt
|
||||
aiohttp.egg-info/not-zip-safe
|
||||
aiohttp.egg-info/requires.txt
|
||||
aiohttp.egg-info/top_level.txt
|
||||
aiohttp/.hash/_cparser.pxd.hash
|
||||
@@ -172,6 +171,8 @@ examples/web_srv_route_deco.py
|
||||
examples/web_srv_route_table.py
|
||||
examples/web_ws.py
|
||||
examples/websocket.html
|
||||
requirements/base-ft.in
|
||||
requirements/base-ft.txt
|
||||
requirements/base.in
|
||||
requirements/base.txt
|
||||
requirements/constraints.in
|
||||
@@ -191,6 +192,10 @@ requirements/multidict.txt
|
||||
requirements/runtime-deps.in
|
||||
requirements/runtime-deps.txt
|
||||
requirements/sync-direct-runtime-deps.py
|
||||
requirements/test-common.in
|
||||
requirements/test-common.txt
|
||||
requirements/test-ft.in
|
||||
requirements/test-ft.txt
|
||||
requirements/test.in
|
||||
requirements/test.txt
|
||||
requirements/.hash/cython.txt.hash
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
aiohappyeyeballs>=2.5.0
|
||||
aiosignal>=1.1.2
|
||||
aiosignal>=1.4.0
|
||||
attrs>=17.3.0
|
||||
frozenlist>=1.1.1
|
||||
multidict<7.0,>=4.5
|
||||
@@ -13,7 +13,10 @@ async-timeout<6.0,>=4.0
|
||||
aiodns>=3.3.0
|
||||
|
||||
[speedups:platform_python_implementation != "CPython"]
|
||||
brotlicffi
|
||||
brotlicffi>=1.2
|
||||
|
||||
[speedups:platform_python_implementation == "CPython"]
|
||||
Brotli
|
||||
Brotli>=1.2
|
||||
|
||||
[speedups:platform_python_implementation == "CPython" and python_version < "3.14"]
|
||||
backports.zstd
|
||||
|
||||
@@ -1 +1 @@
|
||||
d4bd3b3cab898e00c642eaa59b2f7ae5ae5aa1374e698597f7d805a302f23e21 /home/runner/work/aiohttp/aiohttp/aiohttp/_http_parser.pyx
|
||||
f9823c608638b8a77fec1c2bd28dc5c043f08c775ec59e7e262dd74b4ebb7384 /home/runner/work/aiohttp/aiohttp/aiohttp/_http_parser.pyx
|
||||
|
||||
@@ -1 +1 @@
|
||||
f7ab1e2628277b82772d59c1dc3033c13495d769df67b1d1d49b1a474a75dd52 /home/runner/work/aiohttp/aiohttp/aiohttp/_http_writer.pyx
|
||||
56514404ce87a15bfc6b400026d73a270165b2fdbe70313cfa007de29ddd7e14 /home/runner/work/aiohttp/aiohttp/aiohttp/_http_writer.pyx
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
__version__ = "3.12.13"
|
||||
__version__ = "3.13.3"
|
||||
|
||||
from typing import TYPE_CHECKING, Tuple
|
||||
|
||||
|
||||
+40
-11
@@ -6,7 +6,6 @@ These are not part of the public API and may change without notice.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from http.cookies import Morsel
|
||||
from typing import List, Optional, Sequence, Tuple, cast
|
||||
|
||||
@@ -52,7 +51,7 @@ _COOKIE_PATTERN = re.compile(
|
||||
\s* # Optional whitespace at start of cookie
|
||||
(?P<key> # Start of group 'key'
|
||||
# aiohttp has extended to include [] for compatibility with real-world cookies
|
||||
[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=\[\]]+? # Any word of at least one letter
|
||||
[\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\[\]]+ # Any word of at least one letter
|
||||
) # End of group 'key'
|
||||
( # Optional group: there may not be a value.
|
||||
\s*=\s* # Equal Sign
|
||||
@@ -166,7 +165,10 @@ def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
|
||||
attribute names (like 'path' or 'secure') should be treated as cookies.
|
||||
|
||||
This parser uses the same regex-based approach as parse_set_cookie_headers
|
||||
to properly handle quoted values that may contain semicolons.
|
||||
to properly handle quoted values that may contain semicolons. When the
|
||||
regex fails to match a malformed cookie, it falls back to simple parsing
|
||||
to ensure subsequent cookies are not lost
|
||||
https://github.com/aio-libs/aiohttp/issues/11632
|
||||
|
||||
Args:
|
||||
header: The Cookie header value to parse
|
||||
@@ -178,14 +180,39 @@ def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
|
||||
return []
|
||||
|
||||
cookies: List[Tuple[str, Morsel[str]]] = []
|
||||
morsel: Morsel[str]
|
||||
i = 0
|
||||
n = len(header)
|
||||
|
||||
invalid_names = []
|
||||
while i < n:
|
||||
# Use the same pattern as parse_set_cookie_headers to find cookies
|
||||
match = _COOKIE_PATTERN.match(header, i)
|
||||
if not match:
|
||||
break
|
||||
# Fallback for malformed cookies https://github.com/aio-libs/aiohttp/issues/11632
|
||||
# Find next semicolon to skip or attempt simple key=value parsing
|
||||
next_semi = header.find(";", i)
|
||||
eq_pos = header.find("=", i)
|
||||
|
||||
# Try to extract key=value if '=' comes before ';'
|
||||
if eq_pos != -1 and (next_semi == -1 or eq_pos < next_semi):
|
||||
end_pos = next_semi if next_semi != -1 else n
|
||||
key = header[i:eq_pos].strip()
|
||||
value = header[eq_pos + 1 : end_pos].strip()
|
||||
|
||||
# Validate the name (same as regex path)
|
||||
if not _COOKIE_NAME_RE.match(key):
|
||||
invalid_names.append(key)
|
||||
else:
|
||||
morsel = Morsel()
|
||||
morsel.__setstate__( # type: ignore[attr-defined]
|
||||
{"key": key, "value": _unquote(value), "coded_value": value}
|
||||
)
|
||||
cookies.append((key, morsel))
|
||||
|
||||
# Move to next cookie or end
|
||||
i = next_semi + 1 if next_semi != -1 else n
|
||||
continue
|
||||
|
||||
key = match.group("key")
|
||||
value = match.group("val") or ""
|
||||
@@ -193,11 +220,11 @@ def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
|
||||
|
||||
# Validate the name
|
||||
if not key or not _COOKIE_NAME_RE.match(key):
|
||||
internal_logger.warning("Can not load cookie: Illegal cookie name %r", key)
|
||||
invalid_names.append(key)
|
||||
continue
|
||||
|
||||
# Create new morsel
|
||||
morsel: Morsel[str] = Morsel()
|
||||
morsel = Morsel()
|
||||
# Preserve the original value as coded_value (with quotes if present)
|
||||
# We use __setstate__ instead of the public set() API because it allows us to
|
||||
# bypass validation and set already validated state. This is more stable than
|
||||
@@ -209,6 +236,11 @@ def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]:
|
||||
|
||||
cookies.append((key, morsel))
|
||||
|
||||
if invalid_names:
|
||||
internal_logger.debug(
|
||||
"Cannot load cookie. Illegal cookie names: %r", invalid_names
|
||||
)
|
||||
|
||||
return cookies
|
||||
|
||||
|
||||
@@ -270,11 +302,8 @@ def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[s
|
||||
break
|
||||
if lower_key in _COOKIE_BOOL_ATTRS:
|
||||
# Boolean attribute with any value should be True
|
||||
if current_morsel is not None:
|
||||
if lower_key == "partitioned" and sys.version_info < (3, 14):
|
||||
dict.__setitem__(current_morsel, lower_key, True)
|
||||
else:
|
||||
current_morsel[lower_key] = True
|
||||
if current_morsel is not None and current_morsel.isReservedKey(key):
|
||||
current_morsel[lower_key] = True
|
||||
elif value is None:
|
||||
# Invalid cookie string - non-boolean attribute without value
|
||||
break
|
||||
|
||||
+2060
-1840
File diff suppressed because it is too large
Load Diff
+3
-5
@@ -1,5 +1,3 @@
|
||||
#cython: language_level=3
|
||||
#
|
||||
# Based on https://github.com/MagicStack/httptools
|
||||
#
|
||||
|
||||
@@ -421,7 +419,8 @@ cdef class HttpParser:
|
||||
headers = CIMultiDictProxy(CIMultiDict(self._headers))
|
||||
|
||||
if self._cparser.type == cparser.HTTP_REQUEST:
|
||||
allowed = upgrade and headers.get("upgrade", "").lower() in ALLOWED_UPGRADES
|
||||
h_upg = headers.get("upgrade", "")
|
||||
allowed = upgrade and h_upg.isascii() and h_upg.lower() in ALLOWED_UPGRADES
|
||||
if allowed or self._cparser.method == cparser.HTTP_CONNECT:
|
||||
self._upgraded = True
|
||||
else:
|
||||
@@ -436,8 +435,7 @@ cdef class HttpParser:
|
||||
enc = self._content_encoding
|
||||
if enc is not None:
|
||||
self._content_encoding = None
|
||||
enc = enc.lower()
|
||||
if enc in ('gzip', 'deflate', 'br'):
|
||||
if enc.isascii() and enc.lower() in {"gzip", "deflate", "br", "zstd"}:
|
||||
encoding = enc
|
||||
|
||||
if self._cparser.type == cparser.HTTP_REQUEST:
|
||||
|
||||
+604
-388
File diff suppressed because it is too large
Load Diff
+9
-7
@@ -8,7 +8,6 @@ from libc.string cimport memcpy
|
||||
from multidict import istr
|
||||
|
||||
DEF BUF_SIZE = 16 * 1024 # 16KiB
|
||||
cdef char BUFFER[BUF_SIZE]
|
||||
|
||||
cdef object _istr = istr
|
||||
|
||||
@@ -19,16 +18,17 @@ cdef struct Writer:
|
||||
char *buf
|
||||
Py_ssize_t size
|
||||
Py_ssize_t pos
|
||||
bint heap_allocated
|
||||
|
||||
|
||||
cdef inline void _init_writer(Writer* writer):
|
||||
writer.buf = &BUFFER[0]
|
||||
cdef inline void _init_writer(Writer* writer, char *buf):
|
||||
writer.buf = buf
|
||||
writer.size = BUF_SIZE
|
||||
writer.pos = 0
|
||||
writer.heap_allocated = 0
|
||||
|
||||
|
||||
cdef inline void _release_writer(Writer* writer):
|
||||
if writer.buf != BUFFER:
|
||||
if writer.heap_allocated:
|
||||
PyMem_Free(writer.buf)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ cdef inline int _write_byte(Writer* writer, uint8_t ch):
|
||||
if writer.pos == writer.size:
|
||||
# reallocate
|
||||
size = writer.size + BUF_SIZE
|
||||
if writer.buf == BUFFER:
|
||||
if not writer.heap_allocated:
|
||||
buf = <char*>PyMem_Malloc(size)
|
||||
if buf == NULL:
|
||||
PyErr_NoMemory()
|
||||
@@ -52,6 +52,7 @@ cdef inline int _write_byte(Writer* writer, uint8_t ch):
|
||||
return -1
|
||||
writer.buf = buf
|
||||
writer.size = size
|
||||
writer.heap_allocated = 1
|
||||
writer.buf[writer.pos] = <char>ch
|
||||
writer.pos += 1
|
||||
return 0
|
||||
@@ -125,8 +126,9 @@ def _serialize_headers(str status_line, headers):
|
||||
cdef Writer writer
|
||||
cdef object key
|
||||
cdef object val
|
||||
cdef char buf[BUF_SIZE]
|
||||
|
||||
_init_writer(&writer)
|
||||
_init_writer(&writer, buf)
|
||||
|
||||
try:
|
||||
if _write_str(&writer, status_line) < 0:
|
||||
|
||||
+256
-68
@@ -1,4 +1,4 @@
|
||||
/* Generated by Cython 3.1.1 */
|
||||
/* Generated by Cython 3.1.4 */
|
||||
|
||||
#ifndef PY_SSIZE_T_CLEAN
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
@@ -14,8 +14,8 @@
|
||||
#elif PY_VERSION_HEX < 0x03080000
|
||||
#error Cython requires Python 3.8+.
|
||||
#else
|
||||
#define __PYX_ABI_VERSION "3_1_1"
|
||||
#define CYTHON_HEX_VERSION 0x030101F0
|
||||
#define __PYX_ABI_VERSION "3_1_4"
|
||||
#define CYTHON_HEX_VERSION 0x030104F0
|
||||
#define CYTHON_FUTURE_DIVISION 1
|
||||
/* CModulePreamble */
|
||||
#include <stddef.h>
|
||||
@@ -378,6 +378,9 @@
|
||||
enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
|
||||
#endif
|
||||
#endif
|
||||
#ifndef CYTHON_LOCK_AND_GIL_DEADLOCK_AVOIDANCE_TIME
|
||||
#define CYTHON_LOCK_AND_GIL_DEADLOCK_AVOIDANCE_TIME 100
|
||||
#endif
|
||||
#ifndef __has_attribute
|
||||
#define __has_attribute(x) 0
|
||||
#endif
|
||||
@@ -430,7 +433,7 @@
|
||||
#define CYTHON_MAYBE_UNUSED_VAR(x) CYTHON_UNUSED_VAR(x)
|
||||
#endif
|
||||
#ifndef CYTHON_NCP_UNUSED
|
||||
# if CYTHON_COMPILING_IN_CPYTHON
|
||||
# if CYTHON_COMPILING_IN_CPYTHON && !CYTHON_COMPILING_IN_CPYTHON_FREETHREADING
|
||||
# define CYTHON_NCP_UNUSED
|
||||
# else
|
||||
# define CYTHON_NCP_UNUSED CYTHON_UNUSED
|
||||
@@ -875,11 +878,19 @@ static CYTHON_INLINE PyObject * __Pyx_PyDict_GetItemStrWithError(PyObject *dict,
|
||||
#define __Pyx_SET_REFCNT(obj, refcnt) Py_REFCNT(obj) = (refcnt)
|
||||
#define __Pyx_SET_SIZE(obj, size) Py_SIZE(obj) = (size)
|
||||
#endif
|
||||
#if CYTHON_COMPILING_IN_LIMITED_API || CYTHON_AVOID_BORROWED_REFS || CYTHON_AVOID_THREAD_UNSAFE_BORROWED_REFS || !CYTHON_ASSUME_SAFE_MACROS
|
||||
#if CYTHON_AVOID_BORROWED_REFS || CYTHON_AVOID_THREAD_UNSAFE_BORROWED_REFS
|
||||
#if __PYX_LIMITED_VERSION_HEX >= 0x030d0000
|
||||
#define __Pyx_PyList_GetItemRef(o, i) PyList_GetItemRef(o, i)
|
||||
#elif CYTHON_COMPILING_IN_LIMITED_API || !CYTHON_ASSUME_SAFE_MACROS
|
||||
#define __Pyx_PyList_GetItemRef(o, i) (likely((i) >= 0) ? PySequence_GetItem(o, i) : (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL))
|
||||
#else
|
||||
#define __Pyx_PyList_GetItemRef(o, i) PySequence_ITEM(o, i)
|
||||
#endif
|
||||
#elif CYTHON_COMPILING_IN_LIMITED_API || !CYTHON_ASSUME_SAFE_MACROS
|
||||
#if __PYX_LIMITED_VERSION_HEX >= 0x030d0000
|
||||
#define __Pyx_PyList_GetItemRef(o, i) PyList_GetItemRef(o, i)
|
||||
#else
|
||||
#define __Pyx_PyList_GetItemRef(o, i) PySequence_GetItem(o, i)
|
||||
#define __Pyx_PyList_GetItemRef(o, i) __Pyx_XNewRef(PyList_GetItem(o, i))
|
||||
#endif
|
||||
#else
|
||||
#define __Pyx_PyList_GetItemRef(o, i) __Pyx_NewRef(PyList_GET_ITEM(o, i))
|
||||
@@ -1331,7 +1342,9 @@ static CYTHON_INLINE Py_hash_t __Pyx_PyIndex_AsHash_t(PyObject*);
|
||||
#endif /* __GNUC__ */
|
||||
/* PretendToInitialize */
|
||||
#ifdef __cplusplus
|
||||
#if __cplusplus > 201103L
|
||||
#include <type_traits>
|
||||
#endif
|
||||
template <typename T>
|
||||
static void __Pyx_pretend_to_initialize(T* ptr) {
|
||||
#if __cplusplus > 201103L
|
||||
@@ -1897,24 +1910,33 @@ static void __Pyx_RaiseArgtupleInvalid(const char* func_name, int exact,
|
||||
static int __Pyx__ArgTypeTest(PyObject *obj, PyTypeObject *type, const char *name, int exact);
|
||||
|
||||
/* TypeImport.proto */
|
||||
#ifndef __PYX_HAVE_RT_ImportType_proto_3_1_1
|
||||
#define __PYX_HAVE_RT_ImportType_proto_3_1_1
|
||||
#ifndef __PYX_HAVE_RT_ImportType_proto_3_1_4
|
||||
#define __PYX_HAVE_RT_ImportType_proto_3_1_4
|
||||
#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||
#include <stdalign.h>
|
||||
#endif
|
||||
#if (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || __cplusplus >= 201103L
|
||||
#define __PYX_GET_STRUCT_ALIGNMENT_3_1_1(s) alignof(s)
|
||||
#define __PYX_GET_STRUCT_ALIGNMENT_3_1_4(s) alignof(s)
|
||||
#else
|
||||
#define __PYX_GET_STRUCT_ALIGNMENT_3_1_1(s) sizeof(void*)
|
||||
#define __PYX_GET_STRUCT_ALIGNMENT_3_1_4(s) sizeof(void*)
|
||||
#endif
|
||||
enum __Pyx_ImportType_CheckSize_3_1_1 {
|
||||
__Pyx_ImportType_CheckSize_Error_3_1_1 = 0,
|
||||
__Pyx_ImportType_CheckSize_Warn_3_1_1 = 1,
|
||||
__Pyx_ImportType_CheckSize_Ignore_3_1_1 = 2
|
||||
enum __Pyx_ImportType_CheckSize_3_1_4 {
|
||||
__Pyx_ImportType_CheckSize_Error_3_1_4 = 0,
|
||||
__Pyx_ImportType_CheckSize_Warn_3_1_4 = 1,
|
||||
__Pyx_ImportType_CheckSize_Ignore_3_1_4 = 2
|
||||
};
|
||||
static PyTypeObject *__Pyx_ImportType_3_1_1(PyObject* module, const char *module_name, const char *class_name, size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize_3_1_1 check_size);
|
||||
static PyTypeObject *__Pyx_ImportType_3_1_4(PyObject* module, const char *module_name, const char *class_name, size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize_3_1_4 check_size);
|
||||
#endif
|
||||
|
||||
/* LimitedApiGetTypeDict.proto */
|
||||
#if CYTHON_COMPILING_IN_LIMITED_API
|
||||
static PyObject *__Pyx_GetTypeDict(PyTypeObject *tp);
|
||||
#endif
|
||||
|
||||
/* SetItemOnTypeDict.proto */
|
||||
static int __Pyx__SetItemOnTypeDict(PyTypeObject *tp, PyObject *k, PyObject *v);
|
||||
#define __Pyx_SetItemOnTypeDict(tp, k, v) __Pyx__SetItemOnTypeDict((PyTypeObject*)tp, k, v)
|
||||
|
||||
/* FixUpExtensionType.proto */
|
||||
static CYTHON_INLINE int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject *type);
|
||||
|
||||
@@ -1925,7 +1947,11 @@ static PyObject *__Pyx_FetchSharedCythonABIModule(void);
|
||||
static CYTHON_INLINE PyObject *__Pyx_PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *default_value, int is_safe_type);
|
||||
|
||||
/* FetchCommonType.proto */
|
||||
static PyTypeObject* __Pyx_FetchCommonTypeFromSpec(PyObject *module, PyType_Spec *spec, PyObject *bases);
|
||||
static PyTypeObject* __Pyx_FetchCommonTypeFromSpec(PyTypeObject *metaclass, PyObject *module, PyType_Spec *spec, PyObject *bases);
|
||||
|
||||
/* CommonTypesMetaclass.proto */
|
||||
static int __pyx_CommonTypesMetaclass_init(PyObject *module);
|
||||
#define __Pyx_CommonTypesMetaclass_USED
|
||||
|
||||
/* CallTypeTraverse.proto */
|
||||
#if !CYTHON_USE_TYPE_SPECS || (!CYTHON_COMPILING_IN_LIMITED_API && PY_VERSION_HEX < 0x03090000)
|
||||
@@ -2340,6 +2366,7 @@ static const char __pyx_k_range[] = "range";
|
||||
static const char __pyx_k_module[] = "__module__";
|
||||
static const char __pyx_k_add_note[] = "add_note";
|
||||
static const char __pyx_k_qualname[] = "__qualname__";
|
||||
static const char __pyx_k_set_name[] = "__set_name__";
|
||||
static const char __pyx_k_is_coroutine[] = "_is_coroutine";
|
||||
static const char __pyx_k_AssertionError[] = "AssertionError";
|
||||
static const char __pyx_k_asyncio_coroutines[] = "asyncio.coroutines";
|
||||
@@ -2347,7 +2374,7 @@ static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback";
|
||||
static const char __pyx_k_websocket_mask_cython[] = "_websocket_mask_cython";
|
||||
static const char __pyx_k_aiohttp__websocket_mask[] = "aiohttp._websocket.mask";
|
||||
static const char __pyx_k_aiohttp__websocket_mask_pyx[] = "aiohttp/_websocket/mask.pyx";
|
||||
static const char __pyx_k_3avS_s_1_1_5Qa_Yaq_c_Q_k_D_is_q[] = "\200\001\360\026\000\005\014\2103\210a\210v\220S\230\001\340\004\017\210s\220!\2201\330\004\r\320\r\035\320\0351\260\021\260!\330\004\017\320\017%\320%5\260Q\260a\330\004\022\220+\230Y\240a\240q\360\n\000\005\010\200\220c\230\021\330\010\025\220Q\330\010\026\220k\240\023\240D\250\002\250!\340\010\016\210i\220s\230!\330\r\030\230\007\230q\240\006\240a\330\014\026\220a\330\014\030\230\001\360\006\000\005\013\210)\2203\220a\330\t\024\220G\2301\230F\240!\330\010\022\220!\330\010\024\220A\340\004\010\210\005\210U\220!\2203\220a\330\010\016\210a\210v\220X\230Q\230a";
|
||||
static const char __pyx_k_3avS_s_1_1_5Qa_Yaq_c_Q_k_D_is_q[] = "\200\001\360\026\000\005\014\2103\210a\210v\220S\230\001\340\004\017\210s\220!\2201\330\004\r\320\r\035\320\0351\260\021\260!\330\004\017\320\017%\320%5\260Q\260a\330\004\022\220+\230Y\240a\240q\360\n\000\005\010\200\177\220c\230\021\330\010\025\220Q\330\010\026\220k\240\023\240D\250\002\250!\340\010\016\210i\220s\230!\330\r\030\230\007\230q\240\006\240a\330\014\026\220a\330\014\030\230\001\360\006\000\005\013\210)\2203\220a\330\t\024\220G\2301\230F\240!\330\010\022\220!\330\010\024\220A\340\004\010\210\005\210U\220!\2203\220a\330\010\016\210a\210v\220X\230Q\230a";
|
||||
static const char __pyx_k_Note_that_Cython_is_deliberately[] = "Note that Cython is deliberately stricter than PEP-484 and rejects subclasses of builtin types. If you need to pass subclasses then set the 'annotation_typing' directive to False.";
|
||||
/* #### Code section: decls ### */
|
||||
static PyObject *__pyx_pf_7aiohttp_10_websocket_4mask__websocket_mask_cython(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_mask, PyObject *__pyx_v_data); /* proto */
|
||||
@@ -2394,8 +2421,11 @@ typedef struct {
|
||||
PyTypeObject *__pyx_ptype_7cpython_7complex_complex;
|
||||
__Pyx_CachedCFunction __pyx_umethod_PyDict_Type_pop;
|
||||
PyObject *__pyx_codeobj_tab[1];
|
||||
PyObject *__pyx_string_tab[20];
|
||||
PyObject *__pyx_string_tab[21];
|
||||
/* #### Code section: module_state_contents ### */
|
||||
/* CommonTypesMetaclass.module_state_decls */
|
||||
PyTypeObject *__pyx_CommonTypesMetaclassType;
|
||||
|
||||
/* CachedMethodType.module_state_decls */
|
||||
#if CYTHON_COMPILING_IN_LIMITED_API
|
||||
PyObject *__Pyx_CachedMethodType;
|
||||
@@ -2447,8 +2477,9 @@ static __pyx_mstatetype * const __pyx_mstate_global = &__pyx_mstate_global_stati
|
||||
#define __pyx_n_u_pop __pyx_string_tab[15]
|
||||
#define __pyx_n_u_qualname __pyx_string_tab[16]
|
||||
#define __pyx_n_u_range __pyx_string_tab[17]
|
||||
#define __pyx_n_u_test __pyx_string_tab[18]
|
||||
#define __pyx_n_u_websocket_mask_cython __pyx_string_tab[19]
|
||||
#define __pyx_n_u_set_name __pyx_string_tab[18]
|
||||
#define __pyx_n_u_test __pyx_string_tab[19]
|
||||
#define __pyx_n_u_websocket_mask_cython __pyx_string_tab[20]
|
||||
/* #### Code section: module_state_clear ### */
|
||||
#if CYTHON_USE_MODULE_STATE
|
||||
static CYTHON_SMALL_CODE int __pyx_m_clear(PyObject *m) {
|
||||
@@ -2473,7 +2504,7 @@ static CYTHON_SMALL_CODE int __pyx_m_clear(PyObject *m) {
|
||||
Py_CLEAR(clear_module_state->__pyx_ptype_7cpython_4bool_bool);
|
||||
Py_CLEAR(clear_module_state->__pyx_ptype_7cpython_7complex_complex);
|
||||
for (int i=0; i<1; ++i) { Py_CLEAR(clear_module_state->__pyx_codeobj_tab[i]); }
|
||||
for (int i=0; i<20; ++i) { Py_CLEAR(clear_module_state->__pyx_string_tab[i]); }
|
||||
for (int i=0; i<21; ++i) { Py_CLEAR(clear_module_state->__pyx_string_tab[i]); }
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -2498,7 +2529,7 @@ static CYTHON_SMALL_CODE int __pyx_m_traverse(PyObject *m, visitproc visit, void
|
||||
Py_VISIT(traverse_module_state->__pyx_ptype_7cpython_4bool_bool);
|
||||
Py_VISIT(traverse_module_state->__pyx_ptype_7cpython_7complex_complex);
|
||||
for (int i=0; i<1; ++i) { __Pyx_VISIT_CONST(traverse_module_state->__pyx_codeobj_tab[i]); }
|
||||
for (int i=0; i<20; ++i) { __Pyx_VISIT_CONST(traverse_module_state->__pyx_string_tab[i]); }
|
||||
for (int i=0; i<21; ++i) { __Pyx_VISIT_CONST(traverse_module_state->__pyx_string_tab[i]); }
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -3282,39 +3313,39 @@ static int __Pyx_modinit_type_import_code(__pyx_mstatetype *__pyx_mstate) {
|
||||
/*--- Type import code ---*/
|
||||
__pyx_t_1 = PyImport_ImportModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_t_1)) __PYX_ERR(2, 9, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_1);
|
||||
__pyx_mstate->__pyx_ptype_7cpython_4type_type = __Pyx_ImportType_3_1_1(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type",
|
||||
__pyx_mstate->__pyx_ptype_7cpython_4type_type = __Pyx_ImportType_3_1_4(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "type",
|
||||
#if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x050B0000
|
||||
sizeof(PyTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_1(PyTypeObject),
|
||||
sizeof(PyTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_4(PyTypeObject),
|
||||
#elif CYTHON_COMPILING_IN_LIMITED_API
|
||||
0, 0,
|
||||
#else
|
||||
sizeof(PyHeapTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_1(PyHeapTypeObject),
|
||||
sizeof(PyHeapTypeObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_4(PyHeapTypeObject),
|
||||
#endif
|
||||
__Pyx_ImportType_CheckSize_Warn_3_1_1); if (!__pyx_mstate->__pyx_ptype_7cpython_4type_type) __PYX_ERR(2, 9, __pyx_L1_error)
|
||||
__Pyx_ImportType_CheckSize_Warn_3_1_4); if (!__pyx_mstate->__pyx_ptype_7cpython_4type_type) __PYX_ERR(2, 9, __pyx_L1_error)
|
||||
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
|
||||
__pyx_t_1 = PyImport_ImportModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_t_1)) __PYX_ERR(3, 8, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_1);
|
||||
__pyx_mstate->__pyx_ptype_7cpython_4bool_bool = __Pyx_ImportType_3_1_1(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "bool",
|
||||
__pyx_mstate->__pyx_ptype_7cpython_4bool_bool = __Pyx_ImportType_3_1_4(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "bool",
|
||||
#if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x050B0000
|
||||
sizeof(PyLongObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_1(PyLongObject),
|
||||
sizeof(PyLongObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_4(PyLongObject),
|
||||
#elif CYTHON_COMPILING_IN_LIMITED_API
|
||||
0, 0,
|
||||
#else
|
||||
sizeof(PyLongObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_1(PyLongObject),
|
||||
sizeof(PyLongObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_4(PyLongObject),
|
||||
#endif
|
||||
__Pyx_ImportType_CheckSize_Warn_3_1_1); if (!__pyx_mstate->__pyx_ptype_7cpython_4bool_bool) __PYX_ERR(3, 8, __pyx_L1_error)
|
||||
__Pyx_ImportType_CheckSize_Warn_3_1_4); if (!__pyx_mstate->__pyx_ptype_7cpython_4bool_bool) __PYX_ERR(3, 8, __pyx_L1_error)
|
||||
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
|
||||
__pyx_t_1 = PyImport_ImportModule(__Pyx_BUILTIN_MODULE_NAME); if (unlikely(!__pyx_t_1)) __PYX_ERR(4, 16, __pyx_L1_error)
|
||||
__Pyx_GOTREF(__pyx_t_1);
|
||||
__pyx_mstate->__pyx_ptype_7cpython_7complex_complex = __Pyx_ImportType_3_1_1(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "complex",
|
||||
__pyx_mstate->__pyx_ptype_7cpython_7complex_complex = __Pyx_ImportType_3_1_4(__pyx_t_1, __Pyx_BUILTIN_MODULE_NAME, "complex",
|
||||
#if defined(PYPY_VERSION_NUM) && PYPY_VERSION_NUM < 0x050B0000
|
||||
sizeof(PyComplexObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_1(PyComplexObject),
|
||||
sizeof(PyComplexObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_4(PyComplexObject),
|
||||
#elif CYTHON_COMPILING_IN_LIMITED_API
|
||||
0, 0,
|
||||
#else
|
||||
sizeof(PyComplexObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_1(PyComplexObject),
|
||||
sizeof(PyComplexObject), __PYX_GET_STRUCT_ALIGNMENT_3_1_4(PyComplexObject),
|
||||
#endif
|
||||
__Pyx_ImportType_CheckSize_Warn_3_1_1); if (!__pyx_mstate->__pyx_ptype_7cpython_7complex_complex) __PYX_ERR(4, 16, __pyx_L1_error)
|
||||
__Pyx_ImportType_CheckSize_Warn_3_1_4); if (!__pyx_mstate->__pyx_ptype_7cpython_7complex_complex) __PYX_ERR(4, 16, __pyx_L1_error)
|
||||
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
|
||||
__Pyx_RefNannyFinishContext();
|
||||
return 0;
|
||||
@@ -3349,7 +3380,7 @@ static PyModuleDef_Slot __pyx_moduledef_slots[] = {
|
||||
{Py_mod_create, (void*)__pyx_pymod_create},
|
||||
{Py_mod_exec, (void*)__pyx_pymod_exec_mask},
|
||||
#if CYTHON_COMPILING_IN_CPYTHON_FREETHREADING
|
||||
{Py_mod_gil, Py_MOD_GIL_USED},
|
||||
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||
#endif
|
||||
#if PY_VERSION_HEX >= 0x030C0000 && CYTHON_USE_MODULE_STATE
|
||||
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
|
||||
@@ -3549,7 +3580,7 @@ static CYTHON_SMALL_CODE int __pyx_pymod_exec_mask(PyObject *__pyx_pyinit_module
|
||||
__pyx_m = __pyx_t_1;
|
||||
#endif
|
||||
#if CYTHON_COMPILING_IN_CPYTHON_FREETHREADING
|
||||
PyUnstable_Module_SetGIL(__pyx_m, Py_MOD_GIL_USED);
|
||||
PyUnstable_Module_SetGIL(__pyx_m, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
__pyx_mstate = __pyx_mstate_global;
|
||||
CYTHON_UNUSED_VAR(__pyx_t_1);
|
||||
@@ -3577,6 +3608,13 @@ __Pyx_RefNannySetupContext("PyInit_mask", 0);
|
||||
__pyx_mstate->__pyx_empty_tuple = PyTuple_New(0); if (unlikely(!__pyx_mstate->__pyx_empty_tuple)) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
__pyx_mstate->__pyx_empty_bytes = PyBytes_FromStringAndSize("", 0); if (unlikely(!__pyx_mstate->__pyx_empty_bytes)) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
__pyx_mstate->__pyx_empty_unicode = PyUnicode_FromStringAndSize("", 0); if (unlikely(!__pyx_mstate->__pyx_empty_unicode)) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
/*--- Initialize various global constants etc. ---*/
|
||||
if (__Pyx_InitConstants(__pyx_mstate) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
stringtab_initialized = 1;
|
||||
if (__Pyx_InitGlobals() < 0) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
#if 0 || defined(__Pyx_CyFunction_USED) || defined(__Pyx_FusedFunction_USED) || defined(__Pyx_Coroutine_USED) || defined(__Pyx_Generator_USED) || defined(__Pyx_AsyncGen_USED)
|
||||
if (__pyx_CommonTypesMetaclass_init(__pyx_m) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
#endif
|
||||
#ifdef __Pyx_CyFunction_USED
|
||||
if (__pyx_CyFunction_init(__pyx_m) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
#endif
|
||||
@@ -3593,10 +3631,6 @@ __Pyx_RefNannySetupContext("PyInit_mask", 0);
|
||||
if (__pyx_AsyncGen_init(__pyx_m) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
#endif
|
||||
/*--- Library function declarations ---*/
|
||||
/*--- Initialize various global constants etc. ---*/
|
||||
if (__Pyx_InitConstants(__pyx_mstate) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
stringtab_initialized = 1;
|
||||
if (__Pyx_InitGlobals() < 0) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
if (__pyx_module_is_main_aiohttp___websocket__mask) {
|
||||
if (PyObject_SetAttr(__pyx_m, __pyx_mstate_global->__pyx_n_u_name, __pyx_mstate_global->__pyx_n_u_main) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
|
||||
}
|
||||
@@ -3719,6 +3753,7 @@ static const __Pyx_StringTabEntry __pyx_string_tab[] = {
|
||||
{__pyx_k_pop, sizeof(__pyx_k_pop), 0, 1, 1}, /* PyObject cname: __pyx_n_u_pop */
|
||||
{__pyx_k_qualname, sizeof(__pyx_k_qualname), 0, 1, 1}, /* PyObject cname: __pyx_n_u_qualname */
|
||||
{__pyx_k_range, sizeof(__pyx_k_range), 0, 1, 1}, /* PyObject cname: __pyx_n_u_range */
|
||||
{__pyx_k_set_name, sizeof(__pyx_k_set_name), 0, 1, 1}, /* PyObject cname: __pyx_n_u_set_name */
|
||||
{__pyx_k_test, sizeof(__pyx_k_test), 0, 1, 1}, /* PyObject cname: __pyx_n_u_test */
|
||||
{__pyx_k_websocket_mask_cython, sizeof(__pyx_k_websocket_mask_cython), 0, 1, 1}, /* PyObject cname: __pyx_n_u_websocket_mask_cython */
|
||||
{0, 0, 0, 0, 0}
|
||||
@@ -5195,15 +5230,15 @@ static int __Pyx__ArgTypeTest(PyObject *obj, PyTypeObject *type, const char *nam
|
||||
}
|
||||
|
||||
/* TypeImport */
|
||||
#ifndef __PYX_HAVE_RT_ImportType_3_1_1
|
||||
#define __PYX_HAVE_RT_ImportType_3_1_1
|
||||
static PyTypeObject *__Pyx_ImportType_3_1_1(PyObject *module, const char *module_name, const char *class_name,
|
||||
size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize_3_1_1 check_size)
|
||||
#ifndef __PYX_HAVE_RT_ImportType_3_1_4
|
||||
#define __PYX_HAVE_RT_ImportType_3_1_4
|
||||
static PyTypeObject *__Pyx_ImportType_3_1_4(PyObject *module, const char *module_name, const char *class_name,
|
||||
size_t size, size_t alignment, enum __Pyx_ImportType_CheckSize_3_1_4 check_size)
|
||||
{
|
||||
PyObject *result = 0;
|
||||
Py_ssize_t basicsize;
|
||||
Py_ssize_t itemsize;
|
||||
#if CYTHON_COMPILING_IN_LIMITED_API
|
||||
#ifdef Py_LIMITED_API
|
||||
PyObject *py_basicsize;
|
||||
PyObject *py_itemsize;
|
||||
#endif
|
||||
@@ -5216,7 +5251,7 @@ static PyTypeObject *__Pyx_ImportType_3_1_1(PyObject *module, const char *module
|
||||
module_name, class_name);
|
||||
goto bad;
|
||||
}
|
||||
#if !CYTHON_COMPILING_IN_LIMITED_API
|
||||
#ifndef Py_LIMITED_API
|
||||
basicsize = ((PyTypeObject *)result)->tp_basicsize;
|
||||
itemsize = ((PyTypeObject *)result)->tp_itemsize;
|
||||
#else
|
||||
@@ -5254,7 +5289,7 @@ static PyTypeObject *__Pyx_ImportType_3_1_1(PyObject *module, const char *module
|
||||
module_name, class_name, size, basicsize+itemsize);
|
||||
goto bad;
|
||||
}
|
||||
if (check_size == __Pyx_ImportType_CheckSize_Error_3_1_1 &&
|
||||
if (check_size == __Pyx_ImportType_CheckSize_Error_3_1_4 &&
|
||||
((size_t)basicsize > size || (size_t)(basicsize + itemsize) < size)) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"%.200s.%.200s size changed, may indicate binary incompatibility. "
|
||||
@@ -5262,7 +5297,7 @@ static PyTypeObject *__Pyx_ImportType_3_1_1(PyObject *module, const char *module
|
||||
module_name, class_name, size, basicsize, basicsize+itemsize);
|
||||
goto bad;
|
||||
}
|
||||
else if (check_size == __Pyx_ImportType_CheckSize_Warn_3_1_1 && (size_t)basicsize > size) {
|
||||
else if (check_size == __Pyx_ImportType_CheckSize_Warn_3_1_4 && (size_t)basicsize > size) {
|
||||
if (PyErr_WarnFormat(NULL, 0,
|
||||
"%.200s.%.200s size changed, may indicate binary incompatibility. "
|
||||
"Expected %zd from C header, got %zd from PyObject",
|
||||
@@ -5277,24 +5312,83 @@ bad:
|
||||
}
|
||||
#endif
|
||||
|
||||
/* LimitedApiGetTypeDict */
|
||||
#if CYTHON_COMPILING_IN_LIMITED_API
|
||||
static Py_ssize_t __Pyx_GetTypeDictOffset(void) {
|
||||
PyObject *tp_dictoffset_o;
|
||||
Py_ssize_t tp_dictoffset;
|
||||
tp_dictoffset_o = PyObject_GetAttrString((PyObject*)(&PyType_Type), "__dictoffset__");
|
||||
if (unlikely(!tp_dictoffset_o)) return -1;
|
||||
tp_dictoffset = PyLong_AsSsize_t(tp_dictoffset_o);
|
||||
Py_DECREF(tp_dictoffset_o);
|
||||
if (unlikely(tp_dictoffset == 0)) {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
"'type' doesn't have a dictoffset");
|
||||
return -1;
|
||||
} else if (unlikely(tp_dictoffset < 0)) {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
"'type' has an unexpected negative dictoffset. "
|
||||
"Please report this as Cython bug");
|
||||
return -1;
|
||||
}
|
||||
return tp_dictoffset;
|
||||
}
|
||||
static PyObject *__Pyx_GetTypeDict(PyTypeObject *tp) {
|
||||
static Py_ssize_t tp_dictoffset = 0;
|
||||
if (unlikely(tp_dictoffset == 0)) {
|
||||
tp_dictoffset = __Pyx_GetTypeDictOffset();
|
||||
if (unlikely(tp_dictoffset == -1 && PyErr_Occurred())) {
|
||||
tp_dictoffset = 0; // try again next time?
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return *(PyObject**)((char*)tp + tp_dictoffset);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* SetItemOnTypeDict */
|
||||
static int __Pyx__SetItemOnTypeDict(PyTypeObject *tp, PyObject *k, PyObject *v) {
|
||||
int result;
|
||||
PyObject *tp_dict;
|
||||
#if CYTHON_COMPILING_IN_LIMITED_API
|
||||
tp_dict = __Pyx_GetTypeDict(tp);
|
||||
if (unlikely(!tp_dict)) return -1;
|
||||
#else
|
||||
tp_dict = tp->tp_dict;
|
||||
#endif
|
||||
result = PyDict_SetItem(tp_dict, k, v);
|
||||
if (likely(!result)) {
|
||||
PyType_Modified(tp);
|
||||
if (unlikely(PyObject_HasAttr(v, __pyx_mstate_global->__pyx_n_u_set_name))) {
|
||||
PyObject *setNameResult = PyObject_CallMethodObjArgs(v, __pyx_mstate_global->__pyx_n_u_set_name, (PyObject *) tp, k, NULL);
|
||||
if (!setNameResult) return -1;
|
||||
Py_DECREF(setNameResult);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* FixUpExtensionType */
|
||||
static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject *type) {
|
||||
#if PY_VERSION_HEX > 0x030900B1 || CYTHON_COMPILING_IN_LIMITED_API
|
||||
#if __PYX_LIMITED_VERSION_HEX > 0x030900B1
|
||||
CYTHON_UNUSED_VAR(spec);
|
||||
CYTHON_UNUSED_VAR(type);
|
||||
CYTHON_UNUSED_VAR(__Pyx__SetItemOnTypeDict);
|
||||
#else
|
||||
const PyType_Slot *slot = spec->slots;
|
||||
int changed = 0;
|
||||
#if !CYTHON_COMPILING_IN_LIMITED_API
|
||||
while (slot && slot->slot && slot->slot != Py_tp_members)
|
||||
slot++;
|
||||
if (slot && slot->slot == Py_tp_members) {
|
||||
int changed = 0;
|
||||
#if !(PY_VERSION_HEX <= 0x030900b1 && CYTHON_COMPILING_IN_CPYTHON)
|
||||
#if !CYTHON_COMPILING_IN_CPYTHON
|
||||
const
|
||||
#endif
|
||||
#endif // !CYTHON_COMPILING_IN_CPYTHON)
|
||||
PyMemberDef *memb = (PyMemberDef*) slot->pfunc;
|
||||
while (memb && memb->name) {
|
||||
if (memb->name[0] == '_' && memb->name[1] == '_') {
|
||||
#if PY_VERSION_HEX < 0x030900b1
|
||||
if (strcmp(memb->name, "__weaklistoffset__") == 0) {
|
||||
assert(memb->type == T_PYSSIZET);
|
||||
assert(memb->flags == READONLY);
|
||||
@@ -5318,11 +5412,8 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
|
||||
#endif
|
||||
changed = 1;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
if ((0));
|
||||
#endif
|
||||
#if PY_VERSION_HEX <= 0x030900b1 && CYTHON_COMPILING_IN_CPYTHON
|
||||
#endif // CYTHON_METH_FASTCALL
|
||||
#if !CYTHON_COMPILING_IN_PYPY
|
||||
else if (strcmp(memb->name, "__module__") == 0) {
|
||||
PyObject *descr;
|
||||
assert(memb->type == T_OBJECT);
|
||||
@@ -5330,21 +5421,55 @@ static int __Pyx_fix_up_extension_type_from_spec(PyType_Spec *spec, PyTypeObject
|
||||
descr = PyDescr_NewMember(type, memb);
|
||||
if (unlikely(!descr))
|
||||
return -1;
|
||||
if (unlikely(PyDict_SetItem(type->tp_dict, PyDescr_NAME(descr), descr) < 0)) {
|
||||
Py_DECREF(descr);
|
||||
int set_item_result = PyDict_SetItem(type->tp_dict, PyDescr_NAME(descr), descr);
|
||||
Py_DECREF(descr);
|
||||
if (unlikely(set_item_result < 0)) {
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(descr);
|
||||
changed = 1;
|
||||
}
|
||||
#endif
|
||||
#endif // !CYTHON_COMPILING_IN_PYPY
|
||||
}
|
||||
memb++;
|
||||
}
|
||||
if (changed)
|
||||
PyType_Modified(type);
|
||||
}
|
||||
#endif
|
||||
#endif // !CYTHON_COMPILING_IN_LIMITED_API
|
||||
#if !CYTHON_COMPILING_IN_PYPY
|
||||
slot = spec->slots;
|
||||
while (slot && slot->slot && slot->slot != Py_tp_getset)
|
||||
slot++;
|
||||
if (slot && slot->slot == Py_tp_getset) {
|
||||
PyGetSetDef *getset = (PyGetSetDef*) slot->pfunc;
|
||||
while (getset && getset->name) {
|
||||
if (getset->name[0] == '_' && getset->name[1] == '_' && strcmp(getset->name, "__module__") == 0) {
|
||||
PyObject *descr = PyDescr_NewGetSet(type, getset);
|
||||
if (unlikely(!descr))
|
||||
return -1;
|
||||
#if CYTHON_COMPILING_IN_LIMITED_API
|
||||
PyObject *pyname = PyUnicode_FromString(getset->name);
|
||||
if (unlikely(!pyname)) {
|
||||
Py_DECREF(descr);
|
||||
return -1;
|
||||
}
|
||||
int set_item_result = __Pyx_SetItemOnTypeDict(type, pyname, descr);
|
||||
Py_DECREF(pyname);
|
||||
#else
|
||||
CYTHON_UNUSED_VAR(__Pyx__SetItemOnTypeDict);
|
||||
int set_item_result = PyDict_SetItem(type->tp_dict, PyDescr_NAME(descr), descr);
|
||||
#endif
|
||||
Py_DECREF(descr);
|
||||
if (unlikely(set_item_result < 0)) {
|
||||
return -1;
|
||||
}
|
||||
changed = 1;
|
||||
}
|
||||
++getset;
|
||||
}
|
||||
}
|
||||
#endif // !CYTHON_COMPILING_IN_PYPY
|
||||
if (changed)
|
||||
PyType_Modified(type);
|
||||
#endif // PY_VERSION_HEX > 0x030900B1
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5371,6 +5496,24 @@ static CYTHON_INLINE PyObject *__Pyx_PyDict_SetDefault(PyObject *d, PyObject *ke
|
||||
}
|
||||
|
||||
/* FetchCommonType */
|
||||
#if __PYX_LIMITED_VERSION_HEX < 0x030C0000
|
||||
static PyObject* __Pyx_PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module, PyType_Spec *spec, PyObject *bases) {
|
||||
PyObject *result = __Pyx_PyType_FromModuleAndSpec(module, spec, bases);
|
||||
if (result && metaclass) {
|
||||
PyObject *old_tp = (PyObject*)Py_TYPE(result);
|
||||
Py_INCREF((PyObject*)metaclass);
|
||||
#if __PYX_LIMITED_VERSION_HEX >= 0x03090000
|
||||
Py_SET_TYPE(result, metaclass);
|
||||
#else
|
||||
result->ob_type = metaclass;
|
||||
#endif
|
||||
Py_DECREF(old_tp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
#define __Pyx_PyType_FromMetaclass(me, mo, s, b) PyType_FromMetaclass(me, mo, s, b)
|
||||
#endif
|
||||
static int __Pyx_VerifyCachedType(PyObject *cached_type,
|
||||
const char *name,
|
||||
Py_ssize_t expected_basicsize) {
|
||||
@@ -5380,6 +5523,9 @@ static int __Pyx_VerifyCachedType(PyObject *cached_type,
|
||||
"Shared Cython type %.200s is not a type object", name);
|
||||
return -1;
|
||||
}
|
||||
if (expected_basicsize == 0) {
|
||||
return 0; // size is inherited, nothing useful to check
|
||||
}
|
||||
#if CYTHON_COMPILING_IN_LIMITED_API
|
||||
PyObject *py_basicsize;
|
||||
py_basicsize = PyObject_GetAttrString(cached_type, "__basicsize__");
|
||||
@@ -5399,7 +5545,7 @@ static int __Pyx_VerifyCachedType(PyObject *cached_type,
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static PyTypeObject *__Pyx_FetchCommonTypeFromSpec(PyObject *module, PyType_Spec *spec, PyObject *bases) {
|
||||
static PyTypeObject *__Pyx_FetchCommonTypeFromSpec(PyTypeObject *metaclass, PyObject *module, PyType_Spec *spec, PyObject *bases) {
|
||||
PyObject *abi_module = NULL, *cached_type = NULL, *abi_module_dict, *new_cached_type, *py_object_name;
|
||||
int get_item_ref_result;
|
||||
const char* object_name = strrchr(spec->name, '.');
|
||||
@@ -5423,7 +5569,7 @@ static PyTypeObject *__Pyx_FetchCommonTypeFromSpec(PyObject *module, PyType_Spec
|
||||
goto bad;
|
||||
}
|
||||
CYTHON_UNUSED_VAR(module);
|
||||
cached_type = __Pyx_PyType_FromModuleAndSpec(abi_module, spec, bases);
|
||||
cached_type = __Pyx_PyType_FromMetaclass(metaclass, abi_module, spec, bases);
|
||||
if (unlikely(!cached_type)) goto bad;
|
||||
if (unlikely(__Pyx_fix_up_extension_type_from_spec(spec, (PyTypeObject *) cached_type) < 0)) goto bad;
|
||||
new_cached_type = __Pyx_PyDict_SetDefault(abi_module_dict, py_object_name, cached_type, 1);
|
||||
@@ -5452,6 +5598,43 @@ bad:
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* CommonTypesMetaclass */
|
||||
static PyObject* __pyx_CommonTypesMetaclass_get_module(CYTHON_UNUSED PyObject *self, CYTHON_UNUSED void* context) {
|
||||
return PyUnicode_FromString(__PYX_ABI_MODULE_NAME);
|
||||
}
|
||||
static PyGetSetDef __pyx_CommonTypesMetaclass_getset[] = {
|
||||
{"__module__", __pyx_CommonTypesMetaclass_get_module, NULL, NULL, NULL},
|
||||
{0, 0, 0, 0, 0}
|
||||
};
|
||||
static PyType_Slot __pyx_CommonTypesMetaclass_slots[] = {
|
||||
{Py_tp_getset, (void *)__pyx_CommonTypesMetaclass_getset},
|
||||
{0, 0}
|
||||
};
|
||||
static PyType_Spec __pyx_CommonTypesMetaclass_spec = {
|
||||
__PYX_TYPE_MODULE_PREFIX "_common_types_metatype",
|
||||
0,
|
||||
0,
|
||||
#if PY_VERSION_HEX >= 0x030A0000
|
||||
Py_TPFLAGS_IMMUTABLETYPE |
|
||||
Py_TPFLAGS_DISALLOW_INSTANTIATION |
|
||||
#endif
|
||||
Py_TPFLAGS_DEFAULT,
|
||||
__pyx_CommonTypesMetaclass_slots
|
||||
};
|
||||
static int __pyx_CommonTypesMetaclass_init(PyObject *module) {
|
||||
__pyx_mstatetype *mstate = __Pyx_PyModule_GetState(module);
|
||||
PyObject *bases = PyTuple_Pack(1, &PyType_Type);
|
||||
if (unlikely(!bases)) {
|
||||
return -1;
|
||||
}
|
||||
mstate->__pyx_CommonTypesMetaclassType = __Pyx_FetchCommonTypeFromSpec(NULL, module, &__pyx_CommonTypesMetaclass_spec, bases);
|
||||
Py_DECREF(bases);
|
||||
if (unlikely(mstate->__pyx_CommonTypesMetaclassType == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* CallTypeTraverse */
|
||||
#if !CYTHON_USE_TYPE_SPECS || (!CYTHON_COMPILING_IN_LIMITED_API && PY_VERSION_HEX < 0x03090000)
|
||||
#else
|
||||
@@ -6568,7 +6751,8 @@ static PyType_Spec __pyx_CyFunctionType_spec = {
|
||||
};
|
||||
static int __pyx_CyFunction_init(PyObject *module) {
|
||||
__pyx_mstatetype *mstate = __Pyx_PyModule_GetState(module);
|
||||
mstate->__pyx_CyFunctionType = __Pyx_FetchCommonTypeFromSpec(module, &__pyx_CyFunctionType_spec, NULL);
|
||||
mstate->__pyx_CyFunctionType = __Pyx_FetchCommonTypeFromSpec(
|
||||
mstate->__pyx_CommonTypesMetaclassType, module, &__pyx_CyFunctionType_spec, NULL);
|
||||
if (unlikely(mstate->__pyx_CyFunctionType == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -7843,6 +8027,10 @@ bad:
|
||||
PyCode_NewWithPosOnlyArgs
|
||||
#endif
|
||||
(a, p, k, l, s, f, code, c, n, v, fv, cell, fn, name, name, fline, lnos, __pyx_mstate_global->__pyx_empty_bytes);
|
||||
#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030c00A1
|
||||
if (likely(result))
|
||||
result->_co_firsttraceable = 0;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
#elif PY_VERSION_HEX >= 0x030800B2 && !CYTHON_COMPILING_IN_PYPY
|
||||
|
||||
+1187
-1037
File diff suppressed because it is too large
Load Diff
@@ -189,6 +189,13 @@ class WebSocketReader:
|
||||
) -> None:
|
||||
msg: WSMessage
|
||||
if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:
|
||||
# Validate continuation frames before processing
|
||||
if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET:
|
||||
raise WebSocketError(
|
||||
WSCloseCode.PROTOCOL_ERROR,
|
||||
"Continuation frame for non started message",
|
||||
)
|
||||
|
||||
# load text/binary
|
||||
if not fin:
|
||||
# got partial frame payload
|
||||
@@ -205,11 +212,6 @@ class WebSocketReader:
|
||||
|
||||
has_partial = bool(self._partial)
|
||||
if opcode == OP_CODE_CONTINUATION:
|
||||
if self._opcode == OP_CODE_NOT_SET:
|
||||
raise WebSocketError(
|
||||
WSCloseCode.PROTOCOL_ERROR,
|
||||
"Continuation frame for non started message",
|
||||
)
|
||||
opcode = self._opcode
|
||||
self._opcode = OP_CODE_NOT_SET
|
||||
# previous frame was non finished
|
||||
|
||||
@@ -189,6 +189,13 @@ class WebSocketReader:
|
||||
) -> None:
|
||||
msg: WSMessage
|
||||
if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}:
|
||||
# Validate continuation frames before processing
|
||||
if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET:
|
||||
raise WebSocketError(
|
||||
WSCloseCode.PROTOCOL_ERROR,
|
||||
"Continuation frame for non started message",
|
||||
)
|
||||
|
||||
# load text/binary
|
||||
if not fin:
|
||||
# got partial frame payload
|
||||
@@ -205,11 +212,6 @@ class WebSocketReader:
|
||||
|
||||
has_partial = bool(self._partial)
|
||||
if opcode == OP_CODE_CONTINUATION:
|
||||
if self._opcode == OP_CODE_NOT_SET:
|
||||
raise WebSocketError(
|
||||
WSCloseCode.PROTOCOL_ERROR,
|
||||
"Continuation frame for non started message",
|
||||
)
|
||||
opcode = self._opcode
|
||||
self._opcode = OP_CODE_NOT_SET
|
||||
# previous frame was non finished
|
||||
|
||||
+139
-55
@@ -2,8 +2,9 @@
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import sys
|
||||
from functools import partial
|
||||
from typing import Any, Final, Optional, Union
|
||||
from typing import Final, Optional, Set, Union
|
||||
|
||||
from ..base_protocol import BaseProtocol
|
||||
from ..client_exceptions import ClientConnectionResetError
|
||||
@@ -22,14 +23,18 @@ from .models import WS_DEFLATE_TRAILING, WSMsgType
|
||||
|
||||
DEFAULT_LIMIT: Final[int] = 2**16
|
||||
|
||||
# For websockets, keeping latency low is extremely important as implementations
|
||||
# generally expect to be able to send and receive messages quickly. We use a
|
||||
# larger chunk size than the default to reduce the number of executor calls
|
||||
# since the executor is a significant source of latency and overhead when
|
||||
# the chunks are small. A size of 5KiB was chosen because it is also the
|
||||
# same value python-zlib-ng choose to use as the threshold to release the GIL.
|
||||
# WebSocket opcode boundary: opcodes 0-7 are data frames, 8-15 are control frames
|
||||
# Control frames (ping, pong, close) are never compressed
|
||||
WS_CONTROL_FRAME_OPCODE: Final[int] = 8
|
||||
|
||||
WEBSOCKET_MAX_SYNC_CHUNK_SIZE = 5 * 1024
|
||||
# For websockets, keeping latency low is extremely important as implementations
|
||||
# generally expect to be able to send and receive messages quickly. We use a
|
||||
# larger chunk size to reduce the number of executor calls and avoid task
|
||||
# creation overhead, since both are significant sources of latency when chunks
|
||||
# are small. A size of 16KiB was chosen as a balance between avoiding task
|
||||
# overhead and not blocking the event loop too long with synchronous compression.
|
||||
|
||||
WEBSOCKET_MAX_SYNC_CHUNK_SIZE = 16 * 1024
|
||||
|
||||
|
||||
class WebSocketWriter:
|
||||
@@ -62,7 +67,9 @@ class WebSocketWriter:
|
||||
self._closing = False
|
||||
self._limit = limit
|
||||
self._output_size = 0
|
||||
self._compressobj: Any = None # actually compressobj
|
||||
self._compressobj: Optional[ZLibCompressor] = None
|
||||
self._send_lock = asyncio.Lock()
|
||||
self._background_tasks: Set[asyncio.Task[None]] = set()
|
||||
|
||||
async def send_frame(
|
||||
self, message: bytes, opcode: int, compress: Optional[int] = None
|
||||
@@ -71,39 +78,57 @@ class WebSocketWriter:
|
||||
if self._closing and not (opcode & WSMsgType.CLOSE):
|
||||
raise ClientConnectionResetError("Cannot write to closing transport")
|
||||
|
||||
# RSV are the reserved bits in the frame header. They are used to
|
||||
# indicate that the frame is using an extension.
|
||||
# https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
||||
rsv = 0
|
||||
# Only compress larger packets (disabled)
|
||||
# Does small packet needs to be compressed?
|
||||
# if self.compress and opcode < 8 and len(message) > 124:
|
||||
if (compress or self.compress) and opcode < 8:
|
||||
# RSV1 (rsv = 0x40) is set for compressed frames
|
||||
# https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1
|
||||
rsv = 0x40
|
||||
if not (compress or self.compress) or opcode >= WS_CONTROL_FRAME_OPCODE:
|
||||
# Non-compressed frames don't need lock or shield
|
||||
self._write_websocket_frame(message, opcode, 0)
|
||||
elif len(message) <= WEBSOCKET_MAX_SYNC_CHUNK_SIZE:
|
||||
# Small compressed payloads - compress synchronously in event loop
|
||||
# We need the lock even though sync compression has no await points.
|
||||
# This prevents small frames from interleaving with large frames that
|
||||
# compress in the executor, avoiding compressor state corruption.
|
||||
async with self._send_lock:
|
||||
self._send_compressed_frame_sync(message, opcode, compress)
|
||||
else:
|
||||
# Large compressed frames need shield to prevent corruption
|
||||
# For large compressed frames, the entire compress+send
|
||||
# operation must be atomic. If cancelled after compression but
|
||||
# before send, the compressor state would be advanced but data
|
||||
# not sent, corrupting subsequent frames.
|
||||
# Create a task to shield from cancellation
|
||||
# The lock is acquired inside the shielded task so the entire
|
||||
# operation (lock + compress + send) completes atomically.
|
||||
# Use eager_start on Python 3.12+ to avoid scheduling overhead
|
||||
loop = asyncio.get_running_loop()
|
||||
coro = self._send_compressed_frame_async_locked(message, opcode, compress)
|
||||
if sys.version_info >= (3, 12):
|
||||
send_task = asyncio.Task(coro, loop=loop, eager_start=True)
|
||||
else:
|
||||
send_task = loop.create_task(coro)
|
||||
# Keep a strong reference to prevent garbage collection
|
||||
self._background_tasks.add(send_task)
|
||||
send_task.add_done_callback(self._background_tasks.discard)
|
||||
await asyncio.shield(send_task)
|
||||
|
||||
if compress:
|
||||
# Do not set self._compress if compressing is for this frame
|
||||
compressobj = self._make_compress_obj(compress)
|
||||
else: # self.compress
|
||||
if not self._compressobj:
|
||||
self._compressobj = self._make_compress_obj(self.compress)
|
||||
compressobj = self._compressobj
|
||||
# It is safe to return control to the event loop when using compression
|
||||
# after this point as we have already sent or buffered all the data.
|
||||
# Once we have written output_size up to the limit, we call the
|
||||
# drain helper which waits for the transport to be ready to accept
|
||||
# more data. This is a flow control mechanism to prevent the buffer
|
||||
# from growing too large. The drain helper will return right away
|
||||
# if the writer is not paused.
|
||||
if self._output_size > self._limit:
|
||||
self._output_size = 0
|
||||
if self.protocol._paused:
|
||||
await self.protocol._drain_helper()
|
||||
|
||||
message = (
|
||||
await compressobj.compress(message)
|
||||
+ compressobj.flush(
|
||||
ZLibBackend.Z_FULL_FLUSH
|
||||
if self.notakeover
|
||||
else ZLibBackend.Z_SYNC_FLUSH
|
||||
)
|
||||
).removesuffix(WS_DEFLATE_TRAILING)
|
||||
# Its critical that we do not return control to the event
|
||||
# loop until we have finished sending all the compressed
|
||||
# data. Otherwise we could end up mixing compressed frames
|
||||
# if there are multiple coroutines compressing data.
|
||||
def _write_websocket_frame(self, message: bytes, opcode: int, rsv: int) -> None:
|
||||
"""
|
||||
Write a websocket frame to the transport.
|
||||
|
||||
This method handles frame header construction, masking, and writing to transport.
|
||||
It does not handle compression or flow control - those are the responsibility
|
||||
of the caller.
|
||||
"""
|
||||
msg_length = len(message)
|
||||
|
||||
use_mask = self.use_mask
|
||||
@@ -146,26 +171,85 @@ class WebSocketWriter:
|
||||
|
||||
self._output_size += header_len + msg_length
|
||||
|
||||
# It is safe to return control to the event loop when using compression
|
||||
# after this point as we have already sent or buffered all the data.
|
||||
def _get_compressor(self, compress: Optional[int]) -> ZLibCompressor:
|
||||
"""Get or create a compressor object for the given compression level."""
|
||||
if compress:
|
||||
# Do not set self._compress if compressing is for this frame
|
||||
return ZLibCompressor(
|
||||
level=ZLibBackend.Z_BEST_SPEED,
|
||||
wbits=-compress,
|
||||
max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,
|
||||
)
|
||||
if not self._compressobj:
|
||||
self._compressobj = ZLibCompressor(
|
||||
level=ZLibBackend.Z_BEST_SPEED,
|
||||
wbits=-self.compress,
|
||||
max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,
|
||||
)
|
||||
return self._compressobj
|
||||
|
||||
# Once we have written output_size up to the limit, we call the
|
||||
# drain helper which waits for the transport to be ready to accept
|
||||
# more data. This is a flow control mechanism to prevent the buffer
|
||||
# from growing too large. The drain helper will return right away
|
||||
# if the writer is not paused.
|
||||
if self._output_size > self._limit:
|
||||
self._output_size = 0
|
||||
if self.protocol._paused:
|
||||
await self.protocol._drain_helper()
|
||||
def _send_compressed_frame_sync(
|
||||
self, message: bytes, opcode: int, compress: Optional[int]
|
||||
) -> None:
|
||||
"""
|
||||
Synchronous send for small compressed frames.
|
||||
|
||||
def _make_compress_obj(self, compress: int) -> ZLibCompressor:
|
||||
return ZLibCompressor(
|
||||
level=ZLibBackend.Z_BEST_SPEED,
|
||||
wbits=-compress,
|
||||
max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,
|
||||
This is used for small compressed payloads that compress synchronously in the event loop.
|
||||
Since there are no await points, this is inherently cancellation-safe.
|
||||
"""
|
||||
# RSV are the reserved bits in the frame header. They are used to
|
||||
# indicate that the frame is using an extension.
|
||||
# https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
||||
compressobj = self._get_compressor(compress)
|
||||
# (0x40) RSV1 is set for compressed frames
|
||||
# https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1
|
||||
self._write_websocket_frame(
|
||||
(
|
||||
compressobj.compress_sync(message)
|
||||
+ compressobj.flush(
|
||||
ZLibBackend.Z_FULL_FLUSH
|
||||
if self.notakeover
|
||||
else ZLibBackend.Z_SYNC_FLUSH
|
||||
)
|
||||
).removesuffix(WS_DEFLATE_TRAILING),
|
||||
opcode,
|
||||
0x40,
|
||||
)
|
||||
|
||||
async def _send_compressed_frame_async_locked(
|
||||
self, message: bytes, opcode: int, compress: Optional[int]
|
||||
) -> None:
|
||||
"""
|
||||
Async send for large compressed frames with lock.
|
||||
|
||||
Acquires the lock and compresses large payloads asynchronously in
|
||||
the executor. The lock is held for the entire operation to ensure
|
||||
the compressor state is not corrupted by concurrent sends.
|
||||
|
||||
MUST be run shielded from cancellation. If cancelled after
|
||||
compression but before sending, the compressor state would be
|
||||
advanced but data not sent, corrupting subsequent frames.
|
||||
"""
|
||||
async with self._send_lock:
|
||||
# RSV are the reserved bits in the frame header. They are used to
|
||||
# indicate that the frame is using an extension.
|
||||
# https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
||||
compressobj = self._get_compressor(compress)
|
||||
# (0x40) RSV1 is set for compressed frames
|
||||
# https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1
|
||||
self._write_websocket_frame(
|
||||
(
|
||||
await compressobj.compress(message)
|
||||
+ compressobj.flush(
|
||||
ZLibBackend.Z_FULL_FLUSH
|
||||
if self.notakeover
|
||||
else ZLibBackend.Z_SYNC_FLUSH
|
||||
)
|
||||
).removesuffix(WS_DEFLATE_TRAILING),
|
||||
opcode,
|
||||
0x40,
|
||||
)
|
||||
|
||||
async def close(self, code: int = 1000, message: Union[bytes, str] = b"") -> None:
|
||||
"""Close the websocket, sending the specified code and message."""
|
||||
if isinstance(message, str):
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ class AbstractView(ABC):
|
||||
return self._request
|
||||
|
||||
@abstractmethod
|
||||
def __await__(self) -> Generator[Any, None, StreamResponse]:
|
||||
def __await__(self) -> Generator[None, None, StreamResponse]:
|
||||
"""Execute the view handler."""
|
||||
|
||||
|
||||
|
||||
+28
@@ -98,7 +98,9 @@ from .helpers import (
|
||||
EMPTY_BODY_METHODS,
|
||||
BasicAuth,
|
||||
TimeoutHandle,
|
||||
basicauth_from_netrc,
|
||||
get_env_proxy_for_url,
|
||||
netrc_from_env,
|
||||
sentinel,
|
||||
strip_auth_from_url,
|
||||
)
|
||||
@@ -657,6 +659,13 @@ class ClientSession:
|
||||
)
|
||||
):
|
||||
auth = self._default_auth
|
||||
|
||||
# Try netrc if auth is still None and trust_env is enabled.
|
||||
if auth is None and self._trust_env and url.host is not None:
|
||||
auth = await self._loop.run_in_executor(
|
||||
None, self._get_netrc_auth, url.host
|
||||
)
|
||||
|
||||
# It would be confusing if we support explicit
|
||||
# Authorization header with auth argument
|
||||
if (
|
||||
@@ -821,6 +830,12 @@ class ClientSession:
|
||||
data = None
|
||||
if headers.get(hdrs.CONTENT_LENGTH):
|
||||
headers.pop(hdrs.CONTENT_LENGTH)
|
||||
else:
|
||||
# For 307/308, always preserve the request body
|
||||
# For 301/302 with non-POST methods, preserve the request body
|
||||
# https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1
|
||||
# Use the existing payload to avoid recreating it from a potentially consumed file
|
||||
data = req._body
|
||||
|
||||
r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get(
|
||||
hdrs.URI
|
||||
@@ -1205,6 +1220,19 @@ class ClientSession:
|
||||
added_names.add(key)
|
||||
return result
|
||||
|
||||
def _get_netrc_auth(self, host: str) -> Optional[BasicAuth]:
|
||||
"""
|
||||
Get auth from netrc for the given host.
|
||||
|
||||
This method is designed to be called in an executor to avoid
|
||||
blocking I/O in the event loop.
|
||||
"""
|
||||
netrc_obj = netrc_from_env()
|
||||
try:
|
||||
return basicauth_from_netrc(netrc_obj, host)
|
||||
except LookupError:
|
||||
return None
|
||||
|
||||
if sys.version_info >= (3, 11) and TYPE_CHECKING:
|
||||
|
||||
def get(
|
||||
|
||||
@@ -10,6 +10,7 @@ variants, as well as both 'auth' and 'auth-int' quality of protection (qop) opti
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from typing import (
|
||||
Callable,
|
||||
@@ -60,24 +61,27 @@ DigestFunctions: Dict[str, Callable[[bytes], "hashlib._Hash"]] = {
|
||||
|
||||
# Compile the regex pattern once at module level for performance
|
||||
_HEADER_PAIRS_PATTERN = re.compile(
|
||||
r'(\w+)\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^\s,]+))'
|
||||
# | | | | | | | | | || |
|
||||
# +----|--|-|-|--|----|------|----|--||-----|--> alphanumeric key
|
||||
# +--|-|-|--|----|------|----|--||-----|--> maybe whitespace
|
||||
# | | | | | | | || |
|
||||
# +-|-|--|----|------|----|--||-----|--> = (delimiter)
|
||||
# +-|--|----|------|----|--||-----|--> maybe whitespace
|
||||
# | | | | | || |
|
||||
# +--|----|------|----|--||-----|--> group quoted or unquoted
|
||||
# | | | | || |
|
||||
# +----|------|----|--||-----|--> if quoted...
|
||||
# +------|----|--||-----|--> anything but " or \
|
||||
# +----|--||-----|--> escaped characters allowed
|
||||
# +--||-----|--> or can be empty string
|
||||
# || |
|
||||
# +|-----|--> if unquoted...
|
||||
# +-----|--> anything but , or <space>
|
||||
# +--> at least one char req'd
|
||||
r'(?:^|\s|,\s*)(\w+)\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^\s,]+))'
|
||||
if sys.version_info < (3, 11)
|
||||
else r'(?:^|\s|,\s*)((?>\w+))\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^\s,]+))'
|
||||
# +------------|--------|--|-|-|--|----|------|----|--||-----|-> Match valid start/sep
|
||||
# +--------|--|-|-|--|----|------|----|--||-----|-> alphanumeric key (atomic
|
||||
# | | | | | | | | || | group reduces backtracking)
|
||||
# +--|-|-|--|----|------|----|--||-----|-> maybe whitespace
|
||||
# | | | | | | | || |
|
||||
# +-|-|--|----|------|----|--||-----|-> = (delimiter)
|
||||
# +-|--|----|------|----|--||-----|-> maybe whitespace
|
||||
# | | | | | || |
|
||||
# +--|----|------|----|--||-----|-> group quoted or unquoted
|
||||
# | | | | || |
|
||||
# +----|------|----|--||-----|-> if quoted...
|
||||
# +------|----|--||-----|-> anything but " or \
|
||||
# +----|--||-----|-> escaped characters allowed
|
||||
# +--||-----|-> or can be empty string
|
||||
# || |
|
||||
# +|-----|-> if unquoted...
|
||||
# +-----|-> anything but , or <space>
|
||||
# +-> at least one char req'd
|
||||
)
|
||||
|
||||
|
||||
@@ -245,7 +249,9 @@ class DigestAuthMiddleware:
|
||||
)
|
||||
|
||||
qop_raw = challenge.get("qop", "")
|
||||
algorithm = challenge.get("algorithm", "MD5").upper()
|
||||
# Preserve original algorithm case for response while using uppercase for processing
|
||||
algorithm_original = challenge.get("algorithm", "MD5")
|
||||
algorithm = algorithm_original.upper()
|
||||
opaque = challenge.get("opaque", "")
|
||||
|
||||
# Convert string values to bytes once
|
||||
@@ -342,7 +348,7 @@ class DigestAuthMiddleware:
|
||||
"nonce": escape_quotes(nonce),
|
||||
"uri": path,
|
||||
"response": response_digest.decode(),
|
||||
"algorithm": algorithm,
|
||||
"algorithm": algorithm_original,
|
||||
}
|
||||
|
||||
# Optional fields
|
||||
|
||||
+14
-11
@@ -45,7 +45,7 @@ from .client_exceptions import (
|
||||
InvalidURL,
|
||||
ServerFingerprintMismatch,
|
||||
)
|
||||
from .compression_utils import HAS_BROTLI
|
||||
from .compression_utils import HAS_BROTLI, HAS_ZSTD
|
||||
from .formdata import FormData
|
||||
from .helpers import (
|
||||
_SENTINEL,
|
||||
@@ -53,10 +53,9 @@ from .helpers import (
|
||||
BasicAuth,
|
||||
HeadersMixin,
|
||||
TimerNoop,
|
||||
basicauth_from_netrc,
|
||||
netrc_from_env,
|
||||
noop,
|
||||
reify,
|
||||
sentinel,
|
||||
set_exception,
|
||||
set_result,
|
||||
)
|
||||
@@ -104,7 +103,15 @@ json_re = re.compile(r"^application/(?:[\w.+-]+?\+)?json")
|
||||
|
||||
|
||||
def _gen_default_accept_encoding() -> str:
|
||||
return "gzip, deflate, br" if HAS_BROTLI else "gzip, deflate"
|
||||
encodings = [
|
||||
"gzip",
|
||||
"deflate",
|
||||
]
|
||||
if HAS_BROTLI:
|
||||
encodings.append("br")
|
||||
if HAS_ZSTD:
|
||||
encodings.append("zstd")
|
||||
return ", ".join(encodings)
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True, frozen=True, slots=True)
|
||||
@@ -128,14 +135,14 @@ class RequestInfo(_RequestInfo):
|
||||
url: URL,
|
||||
method: str,
|
||||
headers: "CIMultiDictProxy[str]",
|
||||
real_url: URL = _SENTINEL, # type: ignore[assignment]
|
||||
real_url: Union[URL, _SENTINEL] = sentinel,
|
||||
) -> "RequestInfo":
|
||||
"""Create a new RequestInfo instance.
|
||||
|
||||
For backwards compatibility, the real_url parameter is optional.
|
||||
"""
|
||||
return tuple.__new__(
|
||||
cls, (url, method, headers, url if real_url is _SENTINEL else real_url)
|
||||
cls, (url, method, headers, url if real_url is sentinel else real_url)
|
||||
)
|
||||
|
||||
|
||||
@@ -1155,10 +1162,6 @@ class ClientRequest:
|
||||
"""Set basic auth."""
|
||||
if auth is None:
|
||||
auth = self.auth
|
||||
if auth is None and trust_env and self.url.host is not None:
|
||||
netrc_obj = netrc_from_env()
|
||||
with contextlib.suppress(LookupError):
|
||||
auth = basicauth_from_netrc(netrc_obj, self.url.host)
|
||||
if auth is None:
|
||||
return
|
||||
|
||||
@@ -1326,7 +1329,7 @@ class ClientRequest:
|
||||
self,
|
||||
writer: AbstractStreamWriter,
|
||||
conn: "Connection",
|
||||
content_length: Optional[int],
|
||||
content_length: Optional[int] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Write the request body to the connection stream.
|
||||
|
||||
+125
-55
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import sys
|
||||
import zlib
|
||||
from abc import ABC, abstractmethod
|
||||
from concurrent.futures import Executor
|
||||
from typing import Any, Final, Optional, Protocol, TypedDict, cast
|
||||
|
||||
@@ -21,7 +22,23 @@ try:
|
||||
except ImportError: # pragma: no cover
|
||||
HAS_BROTLI = False
|
||||
|
||||
MAX_SYNC_CHUNK_SIZE = 1024
|
||||
try:
|
||||
if sys.version_info >= (3, 14):
|
||||
from compression.zstd import ZstdDecompressor # noqa: I900
|
||||
else: # TODO(PY314): Remove mentions of backports.zstd across codebase
|
||||
from backports.zstd import ZstdDecompressor
|
||||
|
||||
HAS_ZSTD = True
|
||||
except ImportError:
|
||||
HAS_ZSTD = False
|
||||
|
||||
|
||||
MAX_SYNC_CHUNK_SIZE = 4096
|
||||
DEFAULT_MAX_DECOMPRESS_SIZE = 2**25 # 32MiB
|
||||
|
||||
# Unlimited decompression constants - different libraries use different conventions
|
||||
ZLIB_MAX_LENGTH_UNLIMITED = 0 # zlib uses 0 to mean unlimited
|
||||
ZSTD_MAX_LENGTH_UNLIMITED = -1 # zstd uses -1 to mean unlimited
|
||||
|
||||
|
||||
class ZLibCompressObjProtocol(Protocol):
|
||||
@@ -133,19 +150,37 @@ def encoding_to_mode(
|
||||
return -ZLibBackend.MAX_WBITS if suppress_deflate_header else ZLibBackend.MAX_WBITS
|
||||
|
||||
|
||||
class ZlibBaseHandler:
|
||||
class DecompressionBaseHandler(ABC):
|
||||
def __init__(
|
||||
self,
|
||||
mode: int,
|
||||
executor: Optional[Executor] = None,
|
||||
max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE,
|
||||
):
|
||||
self._mode = mode
|
||||
"""Base class for decompression handlers."""
|
||||
self._executor = executor
|
||||
self._max_sync_chunk_size = max_sync_chunk_size
|
||||
|
||||
@abstractmethod
|
||||
def decompress_sync(
|
||||
self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
|
||||
) -> bytes:
|
||||
"""Decompress the given data."""
|
||||
|
||||
class ZLibCompressor(ZlibBaseHandler):
|
||||
async def decompress(
|
||||
self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
|
||||
) -> bytes:
|
||||
"""Decompress the given data."""
|
||||
if (
|
||||
self._max_sync_chunk_size is not None
|
||||
and len(data) > self._max_sync_chunk_size
|
||||
):
|
||||
return await asyncio.get_event_loop().run_in_executor(
|
||||
self._executor, self.decompress_sync, data, max_length
|
||||
)
|
||||
return self.decompress_sync(data, max_length)
|
||||
|
||||
|
||||
class ZLibCompressor:
|
||||
def __init__(
|
||||
self,
|
||||
encoding: Optional[str] = None,
|
||||
@@ -156,14 +191,12 @@ class ZLibCompressor(ZlibBaseHandler):
|
||||
executor: Optional[Executor] = None,
|
||||
max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE,
|
||||
):
|
||||
super().__init__(
|
||||
mode=(
|
||||
encoding_to_mode(encoding, suppress_deflate_header)
|
||||
if wbits is None
|
||||
else wbits
|
||||
),
|
||||
executor=executor,
|
||||
max_sync_chunk_size=max_sync_chunk_size,
|
||||
self._executor = executor
|
||||
self._max_sync_chunk_size = max_sync_chunk_size
|
||||
self._mode = (
|
||||
encoding_to_mode(encoding, suppress_deflate_header)
|
||||
if wbits is None
|
||||
else wbits
|
||||
)
|
||||
self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend)
|
||||
|
||||
@@ -174,7 +207,6 @@ class ZLibCompressor(ZlibBaseHandler):
|
||||
if level is not None:
|
||||
kwargs["level"] = level
|
||||
self._compressor = self._zlib_backend.compressobj(**kwargs)
|
||||
self._compress_lock = asyncio.Lock()
|
||||
|
||||
def compress_sync(self, data: bytes) -> bytes:
|
||||
return self._compressor.compress(data)
|
||||
@@ -187,28 +219,43 @@ class ZLibCompressor(ZlibBaseHandler):
|
||||
If the data size is large than the max_sync_chunk_size, the compression
|
||||
will be done in the executor. Otherwise, the compression will be done
|
||||
in the event loop.
|
||||
|
||||
**WARNING: This method is NOT cancellation-safe when used with flush().**
|
||||
If this operation is cancelled, the compressor state may be corrupted.
|
||||
The connection MUST be closed after cancellation to avoid data corruption
|
||||
in subsequent compress operations.
|
||||
|
||||
For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap
|
||||
compress() + flush() + send operations in a shield and lock to ensure atomicity.
|
||||
"""
|
||||
async with self._compress_lock:
|
||||
# To ensure the stream is consistent in the event
|
||||
# there are multiple writers, we need to lock
|
||||
# the compressor so that only one writer can
|
||||
# compress at a time.
|
||||
if (
|
||||
self._max_sync_chunk_size is not None
|
||||
and len(data) > self._max_sync_chunk_size
|
||||
):
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
self._executor, self._compressor.compress, data
|
||||
)
|
||||
return self.compress_sync(data)
|
||||
# For large payloads, offload compression to executor to avoid blocking event loop
|
||||
should_use_executor = (
|
||||
self._max_sync_chunk_size is not None
|
||||
and len(data) > self._max_sync_chunk_size
|
||||
)
|
||||
if should_use_executor:
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
self._executor, self._compressor.compress, data
|
||||
)
|
||||
return self.compress_sync(data)
|
||||
|
||||
def flush(self, mode: Optional[int] = None) -> bytes:
|
||||
"""Flush the compressor synchronously.
|
||||
|
||||
**WARNING: This method is NOT cancellation-safe when called after compress().**
|
||||
The flush() operation accesses shared compressor state. If compress() was
|
||||
cancelled, calling flush() may result in corrupted data. The connection MUST
|
||||
be closed after compress() cancellation.
|
||||
|
||||
For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap
|
||||
compress() + flush() + send operations in a shield and lock to ensure atomicity.
|
||||
"""
|
||||
return self._compressor.flush(
|
||||
mode if mode is not None else self._zlib_backend.Z_FINISH
|
||||
)
|
||||
|
||||
|
||||
class ZLibDecompressor(ZlibBaseHandler):
|
||||
class ZLibDecompressor(DecompressionBaseHandler):
|
||||
def __init__(
|
||||
self,
|
||||
encoding: Optional[str] = None,
|
||||
@@ -216,33 +263,16 @@ class ZLibDecompressor(ZlibBaseHandler):
|
||||
executor: Optional[Executor] = None,
|
||||
max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE,
|
||||
):
|
||||
super().__init__(
|
||||
mode=encoding_to_mode(encoding, suppress_deflate_header),
|
||||
executor=executor,
|
||||
max_sync_chunk_size=max_sync_chunk_size,
|
||||
)
|
||||
super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)
|
||||
self._mode = encoding_to_mode(encoding, suppress_deflate_header)
|
||||
self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend)
|
||||
self._decompressor = self._zlib_backend.decompressobj(wbits=self._mode)
|
||||
|
||||
def decompress_sync(self, data: bytes, max_length: int = 0) -> bytes:
|
||||
def decompress_sync(
|
||||
self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
|
||||
) -> bytes:
|
||||
return self._decompressor.decompress(data, max_length)
|
||||
|
||||
async def decompress(self, data: bytes, max_length: int = 0) -> bytes:
|
||||
"""Decompress the data and return the decompressed bytes.
|
||||
|
||||
If the data size is large than the max_sync_chunk_size, the decompression
|
||||
will be done in the executor. Otherwise, the decompression will be done
|
||||
in the event loop.
|
||||
"""
|
||||
if (
|
||||
self._max_sync_chunk_size is not None
|
||||
and len(data) > self._max_sync_chunk_size
|
||||
):
|
||||
return await asyncio.get_running_loop().run_in_executor(
|
||||
self._executor, self._decompressor.decompress, data, max_length
|
||||
)
|
||||
return self.decompress_sync(data, max_length)
|
||||
|
||||
def flush(self, length: int = 0) -> bytes:
|
||||
return (
|
||||
self._decompressor.flush(length)
|
||||
@@ -255,24 +285,64 @@ class ZLibDecompressor(ZlibBaseHandler):
|
||||
return self._decompressor.eof
|
||||
|
||||
|
||||
class BrotliDecompressor:
|
||||
class BrotliDecompressor(DecompressionBaseHandler):
|
||||
# Supports both 'brotlipy' and 'Brotli' packages
|
||||
# since they share an import name. The top branches
|
||||
# are for 'brotlipy' and bottom branches for 'Brotli'
|
||||
def __init__(self) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
executor: Optional[Executor] = None,
|
||||
max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE,
|
||||
) -> None:
|
||||
"""Decompress data using the Brotli library."""
|
||||
if not HAS_BROTLI:
|
||||
raise RuntimeError(
|
||||
"The brotli decompression is not available. "
|
||||
"Please install `Brotli` module"
|
||||
)
|
||||
self._obj = brotli.Decompressor()
|
||||
super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)
|
||||
|
||||
def decompress_sync(self, data: bytes) -> bytes:
|
||||
def decompress_sync(
|
||||
self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
|
||||
) -> bytes:
|
||||
"""Decompress the given data."""
|
||||
if hasattr(self._obj, "decompress"):
|
||||
return cast(bytes, self._obj.decompress(data))
|
||||
return cast(bytes, self._obj.process(data))
|
||||
return cast(bytes, self._obj.decompress(data, max_length))
|
||||
return cast(bytes, self._obj.process(data, max_length))
|
||||
|
||||
def flush(self) -> bytes:
|
||||
"""Flush the decompressor."""
|
||||
if hasattr(self._obj, "flush"):
|
||||
return cast(bytes, self._obj.flush())
|
||||
return b""
|
||||
|
||||
|
||||
class ZSTDDecompressor(DecompressionBaseHandler):
|
||||
def __init__(
|
||||
self,
|
||||
executor: Optional[Executor] = None,
|
||||
max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE,
|
||||
) -> None:
|
||||
if not HAS_ZSTD:
|
||||
raise RuntimeError(
|
||||
"The zstd decompression is not available. "
|
||||
"Please install `backports.zstd` module"
|
||||
)
|
||||
self._obj = ZstdDecompressor()
|
||||
super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size)
|
||||
|
||||
def decompress_sync(
|
||||
self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED
|
||||
) -> bytes:
|
||||
# zstd uses -1 for unlimited, while zlib uses 0 for unlimited
|
||||
# Convert the zlib convention (0=unlimited) to zstd convention (-1=unlimited)
|
||||
zstd_max_length = (
|
||||
ZSTD_MAX_LENGTH_UNLIMITED
|
||||
if max_length == ZLIB_MAX_LENGTH_UNLIMITED
|
||||
else max_length
|
||||
)
|
||||
return self._obj.decompress(data, zstd_max_length)
|
||||
|
||||
def flush(self) -> bytes:
|
||||
return b""
|
||||
|
||||
+52
-24
@@ -229,6 +229,26 @@ class Connection:
|
||||
return self._protocol is None or not self._protocol.is_connected()
|
||||
|
||||
|
||||
class _ConnectTunnelConnection(Connection):
|
||||
"""Special connection wrapper for CONNECT tunnels that must never be pooled.
|
||||
|
||||
This connection wraps the proxy connection that will be upgraded with TLS.
|
||||
It must never be released to the pool because:
|
||||
1. Its 'closed' future will never complete, causing session.close() to hang
|
||||
2. It represents an intermediate state, not a reusable connection
|
||||
3. The real connection (with TLS) will be created separately
|
||||
"""
|
||||
|
||||
def release(self) -> None:
|
||||
"""Do nothing - don't pool or close the connection.
|
||||
|
||||
These connections are an intermediate state during the CONNECT tunnel
|
||||
setup and will be cleaned up naturally after the TLS upgrade. If they
|
||||
were to be pooled, they would never be properly closed, causing
|
||||
session.close() to wait forever for their 'closed' future.
|
||||
"""
|
||||
|
||||
|
||||
class _TransportPlaceholder:
|
||||
"""placeholder for BaseConnector.connect function"""
|
||||
|
||||
@@ -589,6 +609,32 @@ class BaseConnector:
|
||||
|
||||
return total_remain
|
||||
|
||||
def _update_proxy_auth_header_and_build_proxy_req(
|
||||
self, req: ClientRequest
|
||||
) -> ClientRequest:
|
||||
"""Set Proxy-Authorization header for non-SSL proxy requests and builds the proxy request for SSL proxy requests."""
|
||||
url = req.proxy
|
||||
assert url is not None
|
||||
headers: Dict[str, str] = {}
|
||||
if req.proxy_headers is not None:
|
||||
headers = req.proxy_headers # type: ignore[assignment]
|
||||
headers[hdrs.HOST] = req.headers[hdrs.HOST]
|
||||
proxy_req = ClientRequest(
|
||||
hdrs.METH_GET,
|
||||
url,
|
||||
headers=headers,
|
||||
auth=req.proxy_auth,
|
||||
loop=self._loop,
|
||||
ssl=req.ssl,
|
||||
)
|
||||
auth = proxy_req.headers.pop(hdrs.AUTHORIZATION, None)
|
||||
if auth is not None:
|
||||
if not req.is_ssl():
|
||||
req.headers[hdrs.PROXY_AUTHORIZATION] = auth
|
||||
else:
|
||||
proxy_req.headers[hdrs.PROXY_AUTHORIZATION] = auth
|
||||
return proxy_req
|
||||
|
||||
async def connect(
|
||||
self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout"
|
||||
) -> Connection:
|
||||
@@ -597,12 +643,16 @@ class BaseConnector:
|
||||
if (conn := await self._get(key, traces)) is not None:
|
||||
# If we do not have to wait and we can get a connection from the pool
|
||||
# we can avoid the timeout ceil logic and directly return the connection
|
||||
if req.proxy:
|
||||
self._update_proxy_auth_header_and_build_proxy_req(req)
|
||||
return conn
|
||||
|
||||
async with ceil_timeout(timeout.connect, timeout.ceil_threshold):
|
||||
if self._available_connections(key) <= 0:
|
||||
await self._wait_for_available_connection(key, traces)
|
||||
if (conn := await self._get(key, traces)) is not None:
|
||||
if req.proxy:
|
||||
self._update_proxy_auth_header_and_build_proxy_req(req)
|
||||
return conn
|
||||
|
||||
placeholder = cast(
|
||||
@@ -1565,35 +1615,13 @@ class TCPConnector(BaseConnector):
|
||||
) -> Tuple[asyncio.BaseTransport, ResponseHandler]:
|
||||
self._fail_on_no_start_tls(req)
|
||||
runtime_has_start_tls = self._loop_supports_start_tls()
|
||||
|
||||
headers: Dict[str, str] = {}
|
||||
if req.proxy_headers is not None:
|
||||
headers = req.proxy_headers # type: ignore[assignment]
|
||||
headers[hdrs.HOST] = req.headers[hdrs.HOST]
|
||||
|
||||
url = req.proxy
|
||||
assert url is not None
|
||||
proxy_req = ClientRequest(
|
||||
hdrs.METH_GET,
|
||||
url,
|
||||
headers=headers,
|
||||
auth=req.proxy_auth,
|
||||
loop=self._loop,
|
||||
ssl=req.ssl,
|
||||
)
|
||||
proxy_req = self._update_proxy_auth_header_and_build_proxy_req(req)
|
||||
|
||||
# create connection to proxy server
|
||||
transport, proto = await self._create_direct_connection(
|
||||
proxy_req, [], timeout, client_error=ClientProxyConnectionError
|
||||
)
|
||||
|
||||
auth = proxy_req.headers.pop(hdrs.AUTHORIZATION, None)
|
||||
if auth is not None:
|
||||
if not req.is_ssl():
|
||||
req.headers[hdrs.PROXY_AUTHORIZATION] = auth
|
||||
else:
|
||||
proxy_req.headers[hdrs.PROXY_AUTHORIZATION] = auth
|
||||
|
||||
if req.is_ssl():
|
||||
if runtime_has_start_tls:
|
||||
self._warn_about_tls_in_tls(transport, req)
|
||||
@@ -1612,7 +1640,7 @@ class TCPConnector(BaseConnector):
|
||||
key = req.connection_key._replace(
|
||||
proxy=None, proxy_auth=None, proxy_headers_hash=None
|
||||
)
|
||||
conn = Connection(self, key, proto, self._loop)
|
||||
conn = _ConnectTunnelConnection(self, key, proto, self._loop)
|
||||
proxy_resp = await proxy_req.send(conn)
|
||||
try:
|
||||
protocol = conn._protocol
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ class FormData:
|
||||
|
||||
elif isinstance(rec, (list, tuple)) and len(rec) == 2:
|
||||
k, fp = rec
|
||||
self.add_field(k, fp) # type: ignore[arg-type]
|
||||
self.add_field(k, fp)
|
||||
|
||||
else:
|
||||
raise TypeError(
|
||||
|
||||
+30
-2
@@ -17,7 +17,9 @@ import time
|
||||
import weakref
|
||||
from collections import namedtuple
|
||||
from contextlib import suppress
|
||||
from email.message import EmailMessage
|
||||
from email.parser import HeaderParser
|
||||
from email.policy import HTTP
|
||||
from email.utils import parsedate
|
||||
from math import ceil
|
||||
from pathlib import Path
|
||||
@@ -357,14 +359,40 @@ def parse_mimetype(mimetype: str) -> MimeType:
|
||||
)
|
||||
|
||||
|
||||
class EnsureOctetStream(EmailMessage):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
# https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5
|
||||
self.set_default_type("application/octet-stream")
|
||||
|
||||
def get_content_type(self) -> str:
|
||||
"""Re-implementation from Message
|
||||
|
||||
Returns application/octet-stream in place of plain/text when
|
||||
value is wrong.
|
||||
|
||||
The way this class is used guarantees that content-type will
|
||||
be present so simplify the checks wrt to the base implementation.
|
||||
"""
|
||||
value = self.get("content-type", "").lower()
|
||||
|
||||
# Based on the implementation of _splitparam in the standard library
|
||||
ctype, _, _ = value.partition(";")
|
||||
ctype = ctype.strip()
|
||||
if ctype.count("/") != 1:
|
||||
return self.get_default_type()
|
||||
return ctype
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=56)
|
||||
def parse_content_type(raw: str) -> Tuple[str, MappingProxyType[str, str]]:
|
||||
"""Parse Content-Type header.
|
||||
|
||||
Returns a tuple of the parsed content type and a
|
||||
MappingProxyType of parameters.
|
||||
MappingProxyType of parameters. The default returned value
|
||||
is `application/octet-stream`
|
||||
"""
|
||||
msg = HeaderParser().parsestr(f"Content-Type: {raw}")
|
||||
msg = HeaderParser(EnsureOctetStream, policy=HTTP).parsestr(f"Content-Type: {raw}")
|
||||
content_type = msg.get_content_type()
|
||||
params = msg.get_params(())
|
||||
content_dict = dict(params[1:]) # First element is content type again
|
||||
|
||||
@@ -74,6 +74,10 @@ class ContentLengthError(PayloadEncodingError):
|
||||
"""Not enough data to satisfy content length header."""
|
||||
|
||||
|
||||
class DecompressSizeError(PayloadEncodingError):
|
||||
"""Decompressed size exceeds the configured limit."""
|
||||
|
||||
|
||||
class LineTooLong(BadHttpMessage):
|
||||
def __init__(
|
||||
self, line: str, limit: str = "Unknown", actual_size: str = "Unknown"
|
||||
|
||||
+84
-44
@@ -26,7 +26,14 @@ from yarl import URL
|
||||
|
||||
from . import hdrs
|
||||
from .base_protocol import BaseProtocol
|
||||
from .compression_utils import HAS_BROTLI, BrotliDecompressor, ZLibDecompressor
|
||||
from .compression_utils import (
|
||||
DEFAULT_MAX_DECOMPRESS_SIZE,
|
||||
HAS_BROTLI,
|
||||
HAS_ZSTD,
|
||||
BrotliDecompressor,
|
||||
ZLibDecompressor,
|
||||
ZSTDDecompressor,
|
||||
)
|
||||
from .helpers import (
|
||||
_EXC_SENTINEL,
|
||||
DEBUG,
|
||||
@@ -42,6 +49,7 @@ from .http_exceptions import (
|
||||
BadStatusLine,
|
||||
ContentEncodingError,
|
||||
ContentLengthError,
|
||||
DecompressSizeError,
|
||||
InvalidHeader,
|
||||
InvalidURLError,
|
||||
LineTooLong,
|
||||
@@ -142,8 +150,8 @@ class HeadersParser:
|
||||
# note: "raw" does not mean inclusion of OWS before/after the field value
|
||||
raw_headers = []
|
||||
|
||||
lines_idx = 1
|
||||
line = lines[1]
|
||||
lines_idx = 0
|
||||
line = lines[lines_idx]
|
||||
line_count = len(lines)
|
||||
|
||||
while line:
|
||||
@@ -232,7 +240,9 @@ class HeadersParser:
|
||||
|
||||
def _is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool:
|
||||
"""Check if the upgrade header is supported."""
|
||||
return headers.get(hdrs.UPGRADE, "").lower() in {"tcp", "websocket"}
|
||||
u = headers.get(hdrs.UPGRADE, "")
|
||||
# .lower() can transform non-ascii characters.
|
||||
return u.isascii() and u.lower() in {"tcp", "websocket"}
|
||||
|
||||
|
||||
class HttpParser(abc.ABC, Generic[_MsgT]):
|
||||
@@ -400,6 +410,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
|
||||
response_with_body=self.response_with_body,
|
||||
auto_decompress=self._auto_decompress,
|
||||
lax=self.lax,
|
||||
headers_parser=self._headers_parser,
|
||||
)
|
||||
if not payload_parser.done:
|
||||
self._payload_parser = payload_parser
|
||||
@@ -418,6 +429,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
|
||||
compression=msg.compression,
|
||||
auto_decompress=self._auto_decompress,
|
||||
lax=self.lax,
|
||||
headers_parser=self._headers_parser,
|
||||
)
|
||||
elif not empty_body and length is None and self.read_until_eof:
|
||||
payload = StreamReader(
|
||||
@@ -436,6 +448,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
|
||||
response_with_body=self.response_with_body,
|
||||
auto_decompress=self._auto_decompress,
|
||||
lax=self.lax,
|
||||
headers_parser=self._headers_parser,
|
||||
)
|
||||
if not payload_parser.done:
|
||||
self._payload_parser = payload_parser
|
||||
@@ -473,6 +486,10 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
|
||||
|
||||
eof = True
|
||||
data = b""
|
||||
if isinstance(
|
||||
underlying_exc, (InvalidHeader, TransferEncodingError)
|
||||
):
|
||||
raise
|
||||
|
||||
if eof:
|
||||
start_pos = 0
|
||||
@@ -536,11 +553,9 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
|
||||
upgrade = True
|
||||
|
||||
# encoding
|
||||
enc = headers.get(hdrs.CONTENT_ENCODING)
|
||||
if enc:
|
||||
enc = enc.lower()
|
||||
if enc in ("gzip", "deflate", "br"):
|
||||
encoding = enc
|
||||
enc = headers.get(hdrs.CONTENT_ENCODING, "")
|
||||
if enc.isascii() and enc.lower() in {"gzip", "deflate", "br", "zstd"}:
|
||||
encoding = enc
|
||||
|
||||
# chunking
|
||||
te = headers.get(hdrs.TRANSFER_ENCODING)
|
||||
@@ -635,7 +650,7 @@ class HttpRequestParser(HttpParser[RawRequestMessage]):
|
||||
compression,
|
||||
upgrade,
|
||||
chunked,
|
||||
) = self.parse_headers(lines)
|
||||
) = self.parse_headers(lines[1:])
|
||||
|
||||
if close is None: # then the headers weren't set in the request
|
||||
if version_o <= HttpVersion10: # HTTP 1.0 must asks to not close
|
||||
@@ -657,7 +672,9 @@ class HttpRequestParser(HttpParser[RawRequestMessage]):
|
||||
)
|
||||
|
||||
def _is_chunked_te(self, te: str) -> bool:
|
||||
if te.rsplit(",", maxsplit=1)[-1].strip(" \t").lower() == "chunked":
|
||||
te = te.rsplit(",", maxsplit=1)[-1].strip(" \t")
|
||||
# .lower() transforms some non-ascii chars, so must check first.
|
||||
if te.isascii() and te.lower() == "chunked":
|
||||
return True
|
||||
# https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.3
|
||||
raise BadHttpMessage("Request has invalid `Transfer-Encoding`")
|
||||
@@ -721,7 +738,7 @@ class HttpResponseParser(HttpParser[RawResponseMessage]):
|
||||
compression,
|
||||
upgrade,
|
||||
chunked,
|
||||
) = self.parse_headers(lines)
|
||||
) = self.parse_headers(lines[1:])
|
||||
|
||||
if close is None:
|
||||
if version_o <= HttpVersion10:
|
||||
@@ -764,6 +781,8 @@ class HttpPayloadParser:
|
||||
response_with_body: bool = True,
|
||||
auto_decompress: bool = True,
|
||||
lax: bool = False,
|
||||
*,
|
||||
headers_parser: HeadersParser,
|
||||
) -> None:
|
||||
self._length = 0
|
||||
self._type = ParseState.PARSE_UNTIL_EOF
|
||||
@@ -772,6 +791,8 @@ class HttpPayloadParser:
|
||||
self._chunk_tail = b""
|
||||
self._auto_decompress = auto_decompress
|
||||
self._lax = lax
|
||||
self._headers_parser = headers_parser
|
||||
self._trailer_lines: list[bytes] = []
|
||||
self.done = False
|
||||
|
||||
# payload decompression wrapper
|
||||
@@ -848,7 +869,7 @@ class HttpPayloadParser:
|
||||
size_b = chunk[:i] # strip chunk-extensions
|
||||
# Verify no LF in the chunk-extension
|
||||
if b"\n" in (ext := chunk[i:pos]):
|
||||
exc = BadHttpMessage(
|
||||
exc = TransferEncodingError(
|
||||
f"Unexpected LF in chunk-extension: {ext!r}"
|
||||
)
|
||||
set_exception(self.payload, exc)
|
||||
@@ -869,7 +890,7 @@ class HttpPayloadParser:
|
||||
|
||||
chunk = chunk[pos + len(SEP) :]
|
||||
if size == 0: # eof marker
|
||||
self._chunk = ChunkState.PARSE_MAYBE_TRAILERS
|
||||
self._chunk = ChunkState.PARSE_TRAILERS
|
||||
if self._lax and chunk.startswith(b"\r"):
|
||||
chunk = chunk[1:]
|
||||
else:
|
||||
@@ -907,38 +928,31 @@ class HttpPayloadParser:
|
||||
self._chunk_tail = chunk
|
||||
return False, b""
|
||||
|
||||
# if stream does not contain trailer, after 0\r\n
|
||||
# we should get another \r\n otherwise
|
||||
# trailers needs to be skipped until \r\n\r\n
|
||||
if self._chunk == ChunkState.PARSE_MAYBE_TRAILERS:
|
||||
head = chunk[: len(SEP)]
|
||||
if head == SEP:
|
||||
# end of stream
|
||||
self.payload.feed_eof()
|
||||
return True, chunk[len(SEP) :]
|
||||
# Both CR and LF, or only LF may not be received yet. It is
|
||||
# expected that CRLF or LF will be shown at the very first
|
||||
# byte next time, otherwise trailers should come. The last
|
||||
# CRLF which marks the end of response might not be
|
||||
# contained in the same TCP segment which delivered the
|
||||
# size indicator.
|
||||
if not head:
|
||||
return False, b""
|
||||
if head == SEP[:1]:
|
||||
self._chunk_tail = head
|
||||
return False, b""
|
||||
self._chunk = ChunkState.PARSE_TRAILERS
|
||||
|
||||
# read and discard trailer up to the CRLF terminator
|
||||
if self._chunk == ChunkState.PARSE_TRAILERS:
|
||||
pos = chunk.find(SEP)
|
||||
if pos >= 0:
|
||||
chunk = chunk[pos + len(SEP) :]
|
||||
self._chunk = ChunkState.PARSE_MAYBE_TRAILERS
|
||||
else:
|
||||
if pos < 0: # No line found
|
||||
self._chunk_tail = chunk
|
||||
return False, b""
|
||||
|
||||
line = chunk[:pos]
|
||||
chunk = chunk[pos + len(SEP) :]
|
||||
if SEP == b"\n": # For lax response parsing
|
||||
line = line.rstrip(b"\r")
|
||||
self._trailer_lines.append(line)
|
||||
|
||||
# \r\n\r\n found, end of stream
|
||||
if self._trailer_lines[-1] == b"":
|
||||
# Headers and trailers are defined the same way,
|
||||
# so we reuse the HeadersParser here.
|
||||
try:
|
||||
trailers, raw_trailers = self._headers_parser.parse_headers(
|
||||
self._trailer_lines
|
||||
)
|
||||
finally:
|
||||
self._trailer_lines.clear()
|
||||
self.payload.feed_eof()
|
||||
return True, chunk
|
||||
|
||||
# Read all bytes until eof
|
||||
elif self._type == ParseState.PARSE_UNTIL_EOF:
|
||||
self.payload.feed_data(chunk, len(chunk))
|
||||
@@ -951,13 +965,19 @@ class DeflateBuffer:
|
||||
|
||||
decompressor: Any
|
||||
|
||||
def __init__(self, out: StreamReader, encoding: Optional[str]) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
out: StreamReader,
|
||||
encoding: Optional[str],
|
||||
max_decompress_size: int = DEFAULT_MAX_DECOMPRESS_SIZE,
|
||||
) -> None:
|
||||
self.out = out
|
||||
self.size = 0
|
||||
out.total_compressed_bytes = self.size
|
||||
self.encoding = encoding
|
||||
self._started_decoding = False
|
||||
|
||||
self.decompressor: Union[BrotliDecompressor, ZLibDecompressor]
|
||||
self.decompressor: Union[BrotliDecompressor, ZLibDecompressor, ZSTDDecompressor]
|
||||
if encoding == "br":
|
||||
if not HAS_BROTLI: # pragma: no cover
|
||||
raise ContentEncodingError(
|
||||
@@ -965,9 +985,18 @@ class DeflateBuffer:
|
||||
"Please install `Brotli`"
|
||||
)
|
||||
self.decompressor = BrotliDecompressor()
|
||||
elif encoding == "zstd":
|
||||
if not HAS_ZSTD:
|
||||
raise ContentEncodingError(
|
||||
"Can not decode content-encoding: zstandard (zstd). "
|
||||
"Please install `backports.zstd`"
|
||||
)
|
||||
self.decompressor = ZSTDDecompressor()
|
||||
else:
|
||||
self.decompressor = ZLibDecompressor(encoding=encoding)
|
||||
|
||||
self._max_decompress_size = max_decompress_size
|
||||
|
||||
def set_exception(
|
||||
self,
|
||||
exc: BaseException,
|
||||
@@ -980,6 +1009,7 @@ class DeflateBuffer:
|
||||
return
|
||||
|
||||
self.size += size
|
||||
self.out.total_compressed_bytes = self.size
|
||||
|
||||
# RFC1950
|
||||
# bits 0..3 = CM = 0b1000 = 8 = "deflate"
|
||||
@@ -996,7 +1026,10 @@ class DeflateBuffer:
|
||||
)
|
||||
|
||||
try:
|
||||
chunk = self.decompressor.decompress_sync(chunk)
|
||||
# Decompress with limit + 1 so we can detect if output exceeds limit
|
||||
chunk = self.decompressor.decompress_sync(
|
||||
chunk, max_length=self._max_decompress_size + 1
|
||||
)
|
||||
except Exception:
|
||||
raise ContentEncodingError(
|
||||
"Can not decode content-encoding: %s" % self.encoding
|
||||
@@ -1004,6 +1037,13 @@ class DeflateBuffer:
|
||||
|
||||
self._started_decoding = True
|
||||
|
||||
# Check if decompression limit was exceeded
|
||||
if len(chunk) > self._max_decompress_size:
|
||||
raise DecompressSizeError(
|
||||
"Decompressed data exceeds the configured limit of %d bytes"
|
||||
% self._max_decompress_size
|
||||
)
|
||||
|
||||
if chunk:
|
||||
self.out.feed_data(chunk, len(chunk))
|
||||
|
||||
|
||||
+37
-25
@@ -25,7 +25,12 @@ from urllib.parse import parse_qsl, unquote, urlencode
|
||||
|
||||
from multidict import CIMultiDict, CIMultiDictProxy
|
||||
|
||||
from .compression_utils import ZLibCompressor, ZLibDecompressor
|
||||
from .abc import AbstractStreamWriter
|
||||
from .compression_utils import (
|
||||
DEFAULT_MAX_DECOMPRESS_SIZE,
|
||||
ZLibCompressor,
|
||||
ZLibDecompressor,
|
||||
)
|
||||
from .hdrs import (
|
||||
CONTENT_DISPOSITION,
|
||||
CONTENT_ENCODING,
|
||||
@@ -114,6 +119,10 @@ def parse_content_disposition(
|
||||
while parts:
|
||||
item = parts.pop(0)
|
||||
|
||||
if not item: # To handle trailing semicolons
|
||||
warnings.warn(BadContentDispositionHeader(header))
|
||||
continue
|
||||
|
||||
if "=" not in item:
|
||||
warnings.warn(BadContentDispositionHeader(header))
|
||||
return None, {}
|
||||
@@ -269,6 +278,7 @@ class BodyPartReader:
|
||||
*,
|
||||
subtype: str = "mixed",
|
||||
default_charset: Optional[str] = None,
|
||||
max_decompress_size: int = DEFAULT_MAX_DECOMPRESS_SIZE,
|
||||
) -> None:
|
||||
self.headers = headers
|
||||
self._boundary = boundary
|
||||
@@ -285,6 +295,7 @@ class BodyPartReader:
|
||||
self._prev_chunk: Optional[bytes] = None
|
||||
self._content_eof = 0
|
||||
self._cache: Dict[str, Any] = {}
|
||||
self._max_decompress_size = max_decompress_size
|
||||
|
||||
def __aiter__(self: Self) -> Self:
|
||||
return self
|
||||
@@ -314,7 +325,7 @@ class BodyPartReader:
|
||||
while not self._at_eof:
|
||||
data.extend(await self.read_chunk(self.chunk_size))
|
||||
if decode:
|
||||
return self.decode(data)
|
||||
return await self.decode(data)
|
||||
return data
|
||||
|
||||
async def read_chunk(self, size: int = chunk_size) -> bytes:
|
||||
@@ -357,11 +368,8 @@ class BodyPartReader:
|
||||
self._read_bytes += len(chunk)
|
||||
if self._read_bytes == self._length:
|
||||
self._at_eof = True
|
||||
if self._at_eof:
|
||||
clrf = await self._content.readline()
|
||||
assert (
|
||||
b"\r\n" == clrf
|
||||
), "reader did not read all the data or it is malformed"
|
||||
if self._at_eof and await self._content.readline() != b"\r\n":
|
||||
raise ValueError("Reader did not read all the data or it is malformed")
|
||||
return chunk
|
||||
|
||||
async def _read_chunk_from_length(self, size: int) -> bytes:
|
||||
@@ -382,7 +390,8 @@ class BodyPartReader:
|
||||
), "Chunk size must be greater or equal than boundary length + 2"
|
||||
first_chunk = self._prev_chunk is None
|
||||
if first_chunk:
|
||||
self._prev_chunk = await self._content.read(size)
|
||||
# We need to re-add the CRLF that got removed from headers parsing.
|
||||
self._prev_chunk = b"\r\n" + await self._content.read(size)
|
||||
|
||||
chunk = b""
|
||||
# content.read() may return less than size, so we need to loop to ensure
|
||||
@@ -390,7 +399,8 @@ class BodyPartReader:
|
||||
while len(chunk) < self._boundary_len:
|
||||
chunk += await self._content.read(size)
|
||||
self._content_eof += int(self._content.at_eof())
|
||||
assert self._content_eof < 3, "Reading after EOF"
|
||||
if self._content_eof > 2:
|
||||
raise ValueError("Reading after EOF")
|
||||
if self._content_eof:
|
||||
break
|
||||
if len(chunk) > size:
|
||||
@@ -409,12 +419,11 @@ class BodyPartReader:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
self._content.unread_data(window[idx:])
|
||||
if size > idx:
|
||||
self._prev_chunk = self._prev_chunk[:idx]
|
||||
self._prev_chunk = self._prev_chunk[:idx]
|
||||
chunk = window[len(self._prev_chunk) : idx]
|
||||
if not chunk:
|
||||
self._at_eof = True
|
||||
result = self._prev_chunk
|
||||
result = self._prev_chunk[2 if first_chunk else 0 :] # Strip initial CRLF
|
||||
self._prev_chunk = chunk
|
||||
return result
|
||||
|
||||
@@ -494,7 +503,7 @@ class BodyPartReader:
|
||||
"""Returns True if the boundary was reached or False otherwise."""
|
||||
return self._at_eof
|
||||
|
||||
def decode(self, data: bytes) -> bytes:
|
||||
async def decode(self, data: bytes) -> bytes:
|
||||
"""Decodes data.
|
||||
|
||||
Decoding is done according the specified Content-Encoding
|
||||
@@ -504,18 +513,18 @@ class BodyPartReader:
|
||||
data = self._decode_content_transfer(data)
|
||||
# https://datatracker.ietf.org/doc/html/rfc7578#section-4.8
|
||||
if not self._is_form_data and CONTENT_ENCODING in self.headers:
|
||||
return self._decode_content(data)
|
||||
return await self._decode_content(data)
|
||||
return data
|
||||
|
||||
def _decode_content(self, data: bytes) -> bytes:
|
||||
async def _decode_content(self, data: bytes) -> bytes:
|
||||
encoding = self.headers.get(CONTENT_ENCODING, "").lower()
|
||||
if encoding == "identity":
|
||||
return data
|
||||
if encoding in {"deflate", "gzip"}:
|
||||
return ZLibDecompressor(
|
||||
return await ZLibDecompressor(
|
||||
encoding=encoding,
|
||||
suppress_deflate_header=True,
|
||||
).decompress_sync(data)
|
||||
).decompress(data, max_length=self._max_decompress_size)
|
||||
|
||||
raise RuntimeError(f"unknown content encoding: {encoding}")
|
||||
|
||||
@@ -586,11 +595,11 @@ class BodyPartReaderPayload(Payload):
|
||||
"""
|
||||
raise TypeError("Unable to read body part as bytes. Use write() to consume.")
|
||||
|
||||
async def write(self, writer: Any) -> None:
|
||||
async def write(self, writer: AbstractStreamWriter) -> None:
|
||||
field = self._value
|
||||
chunk = await field.read_chunk(size=2**16)
|
||||
while chunk:
|
||||
await writer.write(field.decode(chunk))
|
||||
await writer.write(await field.decode(chunk))
|
||||
chunk = await field.read_chunk(size=2**16)
|
||||
|
||||
|
||||
@@ -777,10 +786,10 @@ class MultipartReader:
|
||||
raise ValueError(f"Invalid boundary {chunk!r}, expected {self._boundary!r}")
|
||||
|
||||
async def _read_headers(self) -> "CIMultiDictProxy[str]":
|
||||
lines = [b""]
|
||||
lines = []
|
||||
while True:
|
||||
chunk = await self._content.readline()
|
||||
chunk = chunk.strip()
|
||||
chunk = chunk.rstrip(b"\r\n")
|
||||
lines.append(chunk)
|
||||
if not chunk:
|
||||
break
|
||||
@@ -972,14 +981,15 @@ class MultipartWriter(Payload):
|
||||
"""Size of the payload."""
|
||||
total = 0
|
||||
for part, encoding, te_encoding in self._parts:
|
||||
if encoding or te_encoding or part.size is None:
|
||||
part_size = part.size
|
||||
if encoding or te_encoding or part_size is None:
|
||||
return None
|
||||
|
||||
total += int(
|
||||
2
|
||||
+ len(self._boundary)
|
||||
+ 2
|
||||
+ part.size # b'--'+self._boundary+b'\r\n'
|
||||
+ part_size # b'--'+self._boundary+b'\r\n'
|
||||
+ len(part._binary_headers)
|
||||
+ 2 # b'\r\n'
|
||||
)
|
||||
@@ -1029,7 +1039,9 @@ class MultipartWriter(Payload):
|
||||
|
||||
return b"".join(parts)
|
||||
|
||||
async def write(self, writer: Any, close_boundary: bool = True) -> None:
|
||||
async def write(
|
||||
self, writer: AbstractStreamWriter, close_boundary: bool = True
|
||||
) -> None:
|
||||
"""Write body."""
|
||||
for part, encoding, te_encoding in self._parts:
|
||||
if self._is_form_data:
|
||||
@@ -1083,7 +1095,7 @@ class MultipartWriter(Payload):
|
||||
|
||||
|
||||
class MultipartPayloadWriter:
|
||||
def __init__(self, writer: Any) -> None:
|
||||
def __init__(self, writer: AbstractStreamWriter) -> None:
|
||||
self._writer = writer
|
||||
self._encoding: Optional[str] = None
|
||||
self._compress: Optional[ZLibCompressor] = None
|
||||
|
||||
+23
-4
@@ -486,10 +486,14 @@ class IOBasePayload(Payload):
|
||||
if self._start_position is None:
|
||||
try:
|
||||
self._start_position = self._value.tell()
|
||||
except OSError:
|
||||
except (OSError, AttributeError):
|
||||
self._consumed = True # Cannot seek, mark as consumed
|
||||
return
|
||||
self._value.seek(self._start_position)
|
||||
try:
|
||||
self._value.seek(self._start_position)
|
||||
except (OSError, AttributeError):
|
||||
# Failed to seek back - mark as consumed since we've already read
|
||||
self._consumed = True
|
||||
|
||||
def _read_and_available_len(
|
||||
self, remaining_content_len: Optional[int]
|
||||
@@ -540,11 +544,26 @@ class IOBasePayload(Payload):
|
||||
"""
|
||||
Size of the payload in bytes.
|
||||
|
||||
Returns the number of bytes remaining to be read from the file.
|
||||
Returns the total size of the payload content from the initial position.
|
||||
This ensures consistent Content-Length for requests, including 307/308 redirects
|
||||
where the same payload instance is reused.
|
||||
|
||||
Returns None if the size cannot be determined (e.g., for unseekable streams).
|
||||
"""
|
||||
try:
|
||||
return os.fstat(self._value.fileno()).st_size - self._value.tell()
|
||||
# Store the start position on first access.
|
||||
# This is critical when the same payload instance is reused (e.g., 307/308
|
||||
# redirects). Without storing the initial position, after the payload is
|
||||
# read once, the file position would be at EOF, which would cause the
|
||||
# size calculation to return 0 (file_size - EOF position).
|
||||
# By storing the start position, we ensure the size calculation always
|
||||
# returns the correct total size for any subsequent use.
|
||||
if self._start_position is None:
|
||||
self._start_position = self._value.tell()
|
||||
|
||||
# Return the total size from the start position
|
||||
# This ensures Content-Length is correct even after reading
|
||||
return os.fstat(self._value.fileno()).st_size - self._start_position
|
||||
except (AttributeError, OSError):
|
||||
return None
|
||||
|
||||
|
||||
+36
-5
@@ -116,6 +116,8 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
"_protocol",
|
||||
"_low_water",
|
||||
"_high_water",
|
||||
"_low_water_chunks",
|
||||
"_high_water_chunks",
|
||||
"_loop",
|
||||
"_size",
|
||||
"_cursor",
|
||||
@@ -130,6 +132,7 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
"_eof_callbacks",
|
||||
"_eof_counter",
|
||||
"total_bytes",
|
||||
"total_compressed_bytes",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
@@ -145,10 +148,15 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
self._high_water = limit * 2
|
||||
if loop is None:
|
||||
loop = asyncio.get_event_loop()
|
||||
# Ensure high_water_chunks >= 3 so it's always > low_water_chunks.
|
||||
self._high_water_chunks = max(3, limit // 4)
|
||||
# Use max(2, ...) because there's always at least 1 chunk split remaining
|
||||
# (the current position), so we need low_water >= 2 to allow resume.
|
||||
self._low_water_chunks = max(2, self._high_water_chunks // 2)
|
||||
self._loop = loop
|
||||
self._size = 0
|
||||
self._cursor = 0
|
||||
self._http_chunk_splits: Optional[List[int]] = None
|
||||
self._http_chunk_splits: Optional[Deque[int]] = None
|
||||
self._buffer: Deque[bytes] = collections.deque()
|
||||
self._buffer_offset = 0
|
||||
self._eof = False
|
||||
@@ -159,6 +167,7 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
self._eof_callbacks: List[Callable[[], None]] = []
|
||||
self._eof_counter = 0
|
||||
self.total_bytes = 0
|
||||
self.total_compressed_bytes: Optional[int] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
info = [self.__class__.__name__]
|
||||
@@ -250,6 +259,12 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
finally:
|
||||
self._eof_waiter = None
|
||||
|
||||
@property
|
||||
def total_raw_bytes(self) -> int:
|
||||
if self.total_compressed_bytes is None:
|
||||
return self.total_bytes
|
||||
return self.total_compressed_bytes
|
||||
|
||||
def unread_data(self, data: bytes) -> None:
|
||||
"""rollback reading some data from stream, inserting it to buffer head."""
|
||||
warnings.warn(
|
||||
@@ -295,7 +310,7 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
raise RuntimeError(
|
||||
"Called begin_http_chunk_receiving when some data was already fed"
|
||||
)
|
||||
self._http_chunk_splits = []
|
||||
self._http_chunk_splits = collections.deque()
|
||||
|
||||
def end_http_chunk_receiving(self) -> None:
|
||||
if self._http_chunk_splits is None:
|
||||
@@ -321,6 +336,15 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
|
||||
self._http_chunk_splits.append(self.total_bytes)
|
||||
|
||||
# If we get too many small chunks before self._high_water is reached, then any
|
||||
# .read() call becomes computationally expensive, and could block the event loop
|
||||
# for too long, hence an additional self._high_water_chunks here.
|
||||
if (
|
||||
len(self._http_chunk_splits) > self._high_water_chunks
|
||||
and not self._protocol._reading_paused
|
||||
):
|
||||
self._protocol.pause_reading()
|
||||
|
||||
# wake up readchunk when end of http chunk received
|
||||
waiter = self._waiter
|
||||
if waiter is not None:
|
||||
@@ -454,7 +478,7 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
raise self._exception
|
||||
|
||||
while self._http_chunk_splits:
|
||||
pos = self._http_chunk_splits.pop(0)
|
||||
pos = self._http_chunk_splits.popleft()
|
||||
if pos == self._cursor:
|
||||
return (b"", True)
|
||||
if pos > self._cursor:
|
||||
@@ -527,9 +551,16 @@ class StreamReader(AsyncStreamReaderMixin):
|
||||
chunk_splits = self._http_chunk_splits
|
||||
# Prevent memory leak: drop useless chunk splits
|
||||
while chunk_splits and chunk_splits[0] < self._cursor:
|
||||
chunk_splits.pop(0)
|
||||
chunk_splits.popleft()
|
||||
|
||||
if self._size < self._low_water and self._protocol._reading_paused:
|
||||
if (
|
||||
self._protocol._reading_paused
|
||||
and self._size < self._low_water
|
||||
and (
|
||||
self._http_chunk_splits is None
|
||||
or len(self._http_chunk_splits) < self._low_water_chunks
|
||||
)
|
||||
):
|
||||
self._protocol.resume_reading()
|
||||
return data
|
||||
|
||||
|
||||
+43
-58
@@ -1,5 +1,5 @@
|
||||
from types import SimpleNamespace
|
||||
from typing import TYPE_CHECKING, Awaitable, Mapping, Optional, Protocol, Type, TypeVar
|
||||
from typing import TYPE_CHECKING, Mapping, Optional, Type, TypeVar
|
||||
|
||||
import attr
|
||||
from aiosignal import Signal
|
||||
@@ -12,14 +12,7 @@ if TYPE_CHECKING:
|
||||
from .client import ClientSession
|
||||
|
||||
_ParamT_contra = TypeVar("_ParamT_contra", contravariant=True)
|
||||
|
||||
class _SignalCallback(Protocol[_ParamT_contra]):
|
||||
def __call__(
|
||||
self,
|
||||
__client_session: ClientSession,
|
||||
__trace_config_ctx: SimpleNamespace,
|
||||
__params: _ParamT_contra,
|
||||
) -> Awaitable[None]: ...
|
||||
_TracingSignal = Signal[ClientSession, SimpleNamespace, _ParamT_contra]
|
||||
|
||||
|
||||
__all__ = (
|
||||
@@ -49,54 +42,46 @@ class TraceConfig:
|
||||
def __init__(
|
||||
self, trace_config_ctx_factory: Type[SimpleNamespace] = SimpleNamespace
|
||||
) -> None:
|
||||
self._on_request_start: Signal[_SignalCallback[TraceRequestStartParams]] = (
|
||||
self._on_request_start: _TracingSignal[TraceRequestStartParams] = Signal(self)
|
||||
self._on_request_chunk_sent: _TracingSignal[TraceRequestChunkSentParams] = (
|
||||
Signal(self)
|
||||
)
|
||||
self._on_request_chunk_sent: Signal[
|
||||
_SignalCallback[TraceRequestChunkSentParams]
|
||||
self._on_response_chunk_received: _TracingSignal[
|
||||
TraceResponseChunkReceivedParams
|
||||
] = Signal(self)
|
||||
self._on_response_chunk_received: Signal[
|
||||
_SignalCallback[TraceResponseChunkReceivedParams]
|
||||
] = Signal(self)
|
||||
self._on_request_end: Signal[_SignalCallback[TraceRequestEndParams]] = Signal(
|
||||
self._on_request_end: _TracingSignal[TraceRequestEndParams] = Signal(self)
|
||||
self._on_request_exception: _TracingSignal[TraceRequestExceptionParams] = (
|
||||
Signal(self)
|
||||
)
|
||||
self._on_request_redirect: _TracingSignal[TraceRequestRedirectParams] = Signal(
|
||||
self
|
||||
)
|
||||
self._on_request_exception: Signal[
|
||||
_SignalCallback[TraceRequestExceptionParams]
|
||||
self._on_connection_queued_start: _TracingSignal[
|
||||
TraceConnectionQueuedStartParams
|
||||
] = Signal(self)
|
||||
self._on_request_redirect: Signal[
|
||||
_SignalCallback[TraceRequestRedirectParams]
|
||||
self._on_connection_queued_end: _TracingSignal[
|
||||
TraceConnectionQueuedEndParams
|
||||
] = Signal(self)
|
||||
self._on_connection_queued_start: Signal[
|
||||
_SignalCallback[TraceConnectionQueuedStartParams]
|
||||
self._on_connection_create_start: _TracingSignal[
|
||||
TraceConnectionCreateStartParams
|
||||
] = Signal(self)
|
||||
self._on_connection_queued_end: Signal[
|
||||
_SignalCallback[TraceConnectionQueuedEndParams]
|
||||
self._on_connection_create_end: _TracingSignal[
|
||||
TraceConnectionCreateEndParams
|
||||
] = Signal(self)
|
||||
self._on_connection_create_start: Signal[
|
||||
_SignalCallback[TraceConnectionCreateStartParams]
|
||||
self._on_connection_reuseconn: _TracingSignal[
|
||||
TraceConnectionReuseconnParams
|
||||
] = Signal(self)
|
||||
self._on_connection_create_end: Signal[
|
||||
_SignalCallback[TraceConnectionCreateEndParams]
|
||||
self._on_dns_resolvehost_start: _TracingSignal[
|
||||
TraceDnsResolveHostStartParams
|
||||
] = Signal(self)
|
||||
self._on_connection_reuseconn: Signal[
|
||||
_SignalCallback[TraceConnectionReuseconnParams]
|
||||
] = Signal(self)
|
||||
self._on_dns_resolvehost_start: Signal[
|
||||
_SignalCallback[TraceDnsResolveHostStartParams]
|
||||
] = Signal(self)
|
||||
self._on_dns_resolvehost_end: Signal[
|
||||
_SignalCallback[TraceDnsResolveHostEndParams]
|
||||
] = Signal(self)
|
||||
self._on_dns_cache_hit: Signal[_SignalCallback[TraceDnsCacheHitParams]] = (
|
||||
self._on_dns_resolvehost_end: _TracingSignal[TraceDnsResolveHostEndParams] = (
|
||||
Signal(self)
|
||||
)
|
||||
self._on_dns_cache_miss: Signal[_SignalCallback[TraceDnsCacheMissParams]] = (
|
||||
self._on_dns_cache_hit: _TracingSignal[TraceDnsCacheHitParams] = Signal(self)
|
||||
self._on_dns_cache_miss: _TracingSignal[TraceDnsCacheMissParams] = Signal(self)
|
||||
self._on_request_headers_sent: _TracingSignal[TraceRequestHeadersSentParams] = (
|
||||
Signal(self)
|
||||
)
|
||||
self._on_request_headers_sent: Signal[
|
||||
_SignalCallback[TraceRequestHeadersSentParams]
|
||||
] = Signal(self)
|
||||
|
||||
self._trace_config_ctx_factory = trace_config_ctx_factory
|
||||
|
||||
@@ -125,91 +110,91 @@ class TraceConfig:
|
||||
self._on_request_headers_sent.freeze()
|
||||
|
||||
@property
|
||||
def on_request_start(self) -> "Signal[_SignalCallback[TraceRequestStartParams]]":
|
||||
def on_request_start(self) -> "_TracingSignal[TraceRequestStartParams]":
|
||||
return self._on_request_start
|
||||
|
||||
@property
|
||||
def on_request_chunk_sent(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceRequestChunkSentParams]]":
|
||||
) -> "_TracingSignal[TraceRequestChunkSentParams]":
|
||||
return self._on_request_chunk_sent
|
||||
|
||||
@property
|
||||
def on_response_chunk_received(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceResponseChunkReceivedParams]]":
|
||||
) -> "_TracingSignal[TraceResponseChunkReceivedParams]":
|
||||
return self._on_response_chunk_received
|
||||
|
||||
@property
|
||||
def on_request_end(self) -> "Signal[_SignalCallback[TraceRequestEndParams]]":
|
||||
def on_request_end(self) -> "_TracingSignal[TraceRequestEndParams]":
|
||||
return self._on_request_end
|
||||
|
||||
@property
|
||||
def on_request_exception(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceRequestExceptionParams]]":
|
||||
) -> "_TracingSignal[TraceRequestExceptionParams]":
|
||||
return self._on_request_exception
|
||||
|
||||
@property
|
||||
def on_request_redirect(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceRequestRedirectParams]]":
|
||||
) -> "_TracingSignal[TraceRequestRedirectParams]":
|
||||
return self._on_request_redirect
|
||||
|
||||
@property
|
||||
def on_connection_queued_start(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceConnectionQueuedStartParams]]":
|
||||
) -> "_TracingSignal[TraceConnectionQueuedStartParams]":
|
||||
return self._on_connection_queued_start
|
||||
|
||||
@property
|
||||
def on_connection_queued_end(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceConnectionQueuedEndParams]]":
|
||||
) -> "_TracingSignal[TraceConnectionQueuedEndParams]":
|
||||
return self._on_connection_queued_end
|
||||
|
||||
@property
|
||||
def on_connection_create_start(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceConnectionCreateStartParams]]":
|
||||
) -> "_TracingSignal[TraceConnectionCreateStartParams]":
|
||||
return self._on_connection_create_start
|
||||
|
||||
@property
|
||||
def on_connection_create_end(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceConnectionCreateEndParams]]":
|
||||
) -> "_TracingSignal[TraceConnectionCreateEndParams]":
|
||||
return self._on_connection_create_end
|
||||
|
||||
@property
|
||||
def on_connection_reuseconn(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceConnectionReuseconnParams]]":
|
||||
) -> "_TracingSignal[TraceConnectionReuseconnParams]":
|
||||
return self._on_connection_reuseconn
|
||||
|
||||
@property
|
||||
def on_dns_resolvehost_start(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceDnsResolveHostStartParams]]":
|
||||
) -> "_TracingSignal[TraceDnsResolveHostStartParams]":
|
||||
return self._on_dns_resolvehost_start
|
||||
|
||||
@property
|
||||
def on_dns_resolvehost_end(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceDnsResolveHostEndParams]]":
|
||||
) -> "_TracingSignal[TraceDnsResolveHostEndParams]":
|
||||
return self._on_dns_resolvehost_end
|
||||
|
||||
@property
|
||||
def on_dns_cache_hit(self) -> "Signal[_SignalCallback[TraceDnsCacheHitParams]]":
|
||||
def on_dns_cache_hit(self) -> "_TracingSignal[TraceDnsCacheHitParams]":
|
||||
return self._on_dns_cache_hit
|
||||
|
||||
@property
|
||||
def on_dns_cache_miss(self) -> "Signal[_SignalCallback[TraceDnsCacheMissParams]]":
|
||||
def on_dns_cache_miss(self) -> "_TracingSignal[TraceDnsCacheMissParams]":
|
||||
return self._on_dns_cache_miss
|
||||
|
||||
@property
|
||||
def on_request_headers_sent(
|
||||
self,
|
||||
) -> "Signal[_SignalCallback[TraceRequestHeadersSentParams]]":
|
||||
) -> "_TracingSignal[TraceRequestHeadersSentParams]":
|
||||
return self._on_request_headers_sent
|
||||
|
||||
|
||||
|
||||
+4
-17
@@ -309,18 +309,12 @@ async def _run_app(
|
||||
port: Optional[int] = None,
|
||||
path: Union[PathLike, TypingIterable[PathLike], None] = None,
|
||||
sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None,
|
||||
shutdown_timeout: float = 60.0,
|
||||
keepalive_timeout: float = 75.0,
|
||||
ssl_context: Optional[SSLContext] = None,
|
||||
print: Optional[Callable[..., None]] = print,
|
||||
backlog: int = 128,
|
||||
access_log_class: Type[AbstractAccessLogger] = AccessLogger,
|
||||
access_log_format: str = AccessLogger.LOG_FORMAT,
|
||||
access_log: Optional[logging.Logger] = access_logger,
|
||||
handle_signals: bool = True,
|
||||
reuse_address: Optional[bool] = None,
|
||||
reuse_port: Optional[bool] = None,
|
||||
handler_cancellation: bool = False,
|
||||
**kwargs: Any, # TODO(PY311): Use Unpack
|
||||
) -> None:
|
||||
# An internal function to actually do all dirty job for application running
|
||||
if asyncio.iscoroutine(app):
|
||||
@@ -328,16 +322,7 @@ async def _run_app(
|
||||
|
||||
app = cast(Application, app)
|
||||
|
||||
runner = AppRunner(
|
||||
app,
|
||||
handle_signals=handle_signals,
|
||||
access_log_class=access_log_class,
|
||||
access_log_format=access_log_format,
|
||||
access_log=access_log,
|
||||
keepalive_timeout=keepalive_timeout,
|
||||
shutdown_timeout=shutdown_timeout,
|
||||
handler_cancellation=handler_cancellation,
|
||||
)
|
||||
runner = AppRunner(app, **kwargs)
|
||||
|
||||
await runner.setup()
|
||||
|
||||
@@ -484,6 +469,7 @@ def run_app(
|
||||
reuse_port: Optional[bool] = None,
|
||||
handler_cancellation: bool = False,
|
||||
loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Run an app locally"""
|
||||
if loop is None:
|
||||
@@ -515,6 +501,7 @@ def run_app(
|
||||
reuse_address=reuse_address,
|
||||
reuse_port=reuse_port,
|
||||
handler_cancellation=handler_cancellation,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -62,8 +62,8 @@ __all__ = ("Application", "CleanupError")
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
_AppSignal = Signal[Callable[["Application"], Awaitable[None]]]
|
||||
_RespPrepareSignal = Signal[Callable[[Request, StreamResponse], Awaitable[None]]]
|
||||
_AppSignal = Signal["Application"]
|
||||
_RespPrepareSignal = Signal[Request, StreamResponse]
|
||||
_Middlewares = FrozenList[Middleware]
|
||||
_MiddlewaresHandlers = Optional[Sequence[Tuple[Middleware, bool]]]
|
||||
_Subapps = List["Application"]
|
||||
|
||||
+4
-4
@@ -164,8 +164,8 @@ class FileResponse(StreamResponse):
|
||||
) -> Optional[AbstractStreamWriter]:
|
||||
self.set_status(HTTPNotModified.status_code)
|
||||
self._length_check = False
|
||||
self.etag = etag_value # type: ignore[assignment]
|
||||
self.last_modified = last_modified # type: ignore[assignment]
|
||||
self.etag = etag_value
|
||||
self.last_modified = last_modified
|
||||
# Delete any Content-Length headers provided by user. HTTP 304
|
||||
# should always have empty response body
|
||||
return await super().prepare(request)
|
||||
@@ -395,8 +395,8 @@ class FileResponse(StreamResponse):
|
||||
# compress.
|
||||
self._compression = False
|
||||
|
||||
self.etag = f"{st.st_mtime_ns:x}-{st.st_size:x}" # type: ignore[assignment]
|
||||
self.last_modified = file_mtime # type: ignore[assignment]
|
||||
self.etag = f"{st.st_mtime_ns:x}-{st.st_size:x}"
|
||||
self.last_modified = file_mtime
|
||||
self.content_length = count
|
||||
|
||||
self._headers[hdrs.ACCEPT_RANGES] = "bytes"
|
||||
|
||||
+1
-1
@@ -468,7 +468,7 @@ class RequestHandler(BaseProtocol):
|
||||
def log_access(
|
||||
self, request: BaseRequest, response: StreamResponse, time: Optional[float]
|
||||
) -> None:
|
||||
if self.access_logger is not None and self.access_logger.enabled:
|
||||
if self._logging_enabled and self.access_logger is not None:
|
||||
if TYPE_CHECKING:
|
||||
assert time is not None
|
||||
self.access_logger.log(request, response, self._loop.time() - time)
|
||||
|
||||
+6
-8
@@ -607,7 +607,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
|
||||
if rng is not None:
|
||||
try:
|
||||
pattern = r"^bytes=(\d*)-(\d*)$"
|
||||
start, end = re.findall(pattern, rng)[0]
|
||||
start, end = re.findall(pattern, rng, re.ASCII)[0]
|
||||
except IndexError: # pattern was not found in header
|
||||
raise ValueError("range not in acceptable format")
|
||||
|
||||
@@ -721,13 +721,13 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
|
||||
multipart = await self.multipart()
|
||||
max_size = self._client_max_size
|
||||
|
||||
field = await multipart.next()
|
||||
while field is not None:
|
||||
size = 0
|
||||
size = 0
|
||||
while (field := await multipart.next()) is not None:
|
||||
field_ct = field.headers.get(hdrs.CONTENT_TYPE)
|
||||
|
||||
if isinstance(field, BodyPartReader):
|
||||
assert field.name is not None
|
||||
if field.name is None:
|
||||
raise ValueError("Multipart field missing name.")
|
||||
|
||||
# Note that according to RFC 7578, the Content-Type header
|
||||
# is optional, even for files, so we can't assume it's
|
||||
@@ -740,7 +740,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
|
||||
)
|
||||
chunk = await field.read_chunk(size=2**16)
|
||||
while chunk:
|
||||
chunk = field.decode(chunk)
|
||||
chunk = await field.decode(chunk)
|
||||
await self._loop.run_in_executor(None, tmp.write, chunk)
|
||||
size += len(chunk)
|
||||
if 0 < max_size < size:
|
||||
@@ -779,8 +779,6 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
|
||||
raise ValueError(
|
||||
"To decode nested multipart you need to use custom reader",
|
||||
)
|
||||
|
||||
field = await multipart.next()
|
||||
else:
|
||||
data = await self.read()
|
||||
if data:
|
||||
|
||||
+2
-2
@@ -791,8 +791,8 @@ class Response(StreamResponse):
|
||||
del self._headers[hdrs.CONTENT_LENGTH]
|
||||
elif not self._chunked:
|
||||
if isinstance(self._body, Payload):
|
||||
if self._body.size is not None:
|
||||
self._headers[hdrs.CONTENT_LENGTH] = str(self._body.size)
|
||||
if (size := self._body.size) is not None:
|
||||
self._headers[hdrs.CONTENT_LENGTH] = str(size)
|
||||
else:
|
||||
body_len = len(self._body) if self._body else "0"
|
||||
# https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-7
|
||||
|
||||
+27
-25
@@ -7,6 +7,7 @@ import html
|
||||
import inspect
|
||||
import keyword
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
@@ -94,6 +95,7 @@ ROUTE_RE: Final[Pattern[str]] = re.compile(
|
||||
)
|
||||
PATH_SEP: Final[str] = re.escape("/")
|
||||
|
||||
IS_WINDOWS: Final[bool] = platform.system() == "Windows"
|
||||
|
||||
_ExpectHandler = Callable[[Request], Awaitable[Optional[StreamResponse]]]
|
||||
_Resolve = Tuple[Optional["UrlMappingMatchInfo"], Set[str]]
|
||||
@@ -194,6 +196,8 @@ class AbstractRoute(abc.ABC):
|
||||
):
|
||||
pass
|
||||
elif inspect.isgeneratorfunction(handler):
|
||||
if TYPE_CHECKING:
|
||||
assert False
|
||||
warnings.warn(
|
||||
"Bare generators are deprecated, use @coroutine wrapper",
|
||||
DeprecationWarning,
|
||||
@@ -649,7 +653,12 @@ class StaticResource(PrefixResource):
|
||||
async def resolve(self, request: Request) -> _Resolve:
|
||||
path = request.rel_url.path_safe
|
||||
method = request.method
|
||||
if not path.startswith(self._prefix2) and path != self._prefix:
|
||||
# We normalise here to avoid matches that traverse below the static root.
|
||||
# e.g. /static/../../../../home/user/webapp/static/
|
||||
norm_path = os.path.normpath(path)
|
||||
if IS_WINDOWS:
|
||||
norm_path = norm_path.replace("\\", "/")
|
||||
if not norm_path.startswith(self._prefix2) and norm_path != self._prefix:
|
||||
return None, set()
|
||||
|
||||
allowed_methods = self._allowed_methods
|
||||
@@ -666,14 +675,7 @@ class StaticResource(PrefixResource):
|
||||
return iter(self._routes.values())
|
||||
|
||||
async def _handle(self, request: Request) -> StreamResponse:
|
||||
rel_url = request.match_info["filename"]
|
||||
filename = Path(rel_url)
|
||||
if filename.anchor:
|
||||
# rel_url is an absolute name like
|
||||
# /static/\\machine_name\c$ or /static/D:\path
|
||||
# where the static dir is totally different
|
||||
raise HTTPForbidden()
|
||||
|
||||
filename = request.match_info["filename"]
|
||||
unresolved_path = self._directory.joinpath(filename)
|
||||
loop = asyncio.get_running_loop()
|
||||
return await loop.run_in_executor(
|
||||
@@ -978,7 +980,7 @@ class View(AbstractView):
|
||||
assert isinstance(ret, StreamResponse)
|
||||
return ret
|
||||
|
||||
def __await__(self) -> Generator[Any, None, StreamResponse]:
|
||||
def __await__(self) -> Generator[None, None, StreamResponse]:
|
||||
return self._iter().__await__()
|
||||
|
||||
def _raise_allowed_methods(self) -> NoReturn:
|
||||
@@ -1032,6 +1034,21 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
|
||||
resource_index = self._resource_index
|
||||
allowed_methods: Set[str] = set()
|
||||
|
||||
# MatchedSubAppResource is primarily used to match on domain names
|
||||
# (though custom rules could match on other things). This means that
|
||||
# the traversal algorithm below can't be applied, and that we likely
|
||||
# need to check these first so a sub app that defines the same path
|
||||
# as a parent app will get priority if there's a domain match.
|
||||
#
|
||||
# For most cases we do not expect there to be many of these since
|
||||
# currently they are only added by `.add_domain()`.
|
||||
for resource in self._matched_sub_app_resources:
|
||||
match_dict, allowed = await resource.resolve(request)
|
||||
if match_dict is not None:
|
||||
return match_dict
|
||||
else:
|
||||
allowed_methods |= allowed
|
||||
|
||||
# Walk the url parts looking for candidates. We walk the url backwards
|
||||
# to ensure the most explicit match is found first. If there are multiple
|
||||
# candidates for a given url part because there are multiple resources
|
||||
@@ -1049,21 +1066,6 @@ class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]):
|
||||
break
|
||||
url_part = url_part.rpartition("/")[0] or "/"
|
||||
|
||||
#
|
||||
# We didn't find any candidates, so we'll try the matched sub-app
|
||||
# resources which we have to walk in a linear fashion because they
|
||||
# have regex/wildcard match rules and we cannot index them.
|
||||
#
|
||||
# For most cases we do not expect there to be many of these since
|
||||
# currently they are only added by `add_domain`
|
||||
#
|
||||
for resource in self._matched_sub_app_resources:
|
||||
match_dict, allowed = await resource.resolve(request)
|
||||
if match_dict is not None:
|
||||
return match_dict
|
||||
else:
|
||||
allowed_methods |= allowed
|
||||
|
||||
if allowed_methods:
|
||||
return MatchInfoError(HTTPMethodNotAllowed(request.method, allowed_methods))
|
||||
|
||||
|
||||
+85
-6
@@ -1,10 +1,94 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"pkgconfig",
|
||||
"setuptools >= 46.4.0",
|
||||
# setuptools >= 67.0 required for Python 3.12+ support
|
||||
# Next step should be >= 77.0 for PEP 639 support
|
||||
# Don't bump too early to give distributors time to update
|
||||
# their setuptools version.
|
||||
"setuptools >= 67.0",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "aiohttp"
|
||||
# TODO: Update to just 'license = "..."' once setuptools is bumped to >=77
|
||||
license = {text = "Apache-2.0 AND MIT"}
|
||||
description = "Async http client/server framework (asyncio)"
|
||||
readme = "README.rst"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Framework :: AsyncIO",
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"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",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
]
|
||||
requires-python = ">= 3.9"
|
||||
dependencies = [
|
||||
"aiohappyeyeballs >= 2.5.0",
|
||||
"aiosignal >= 1.4.0",
|
||||
"async-timeout >= 4.0, < 6.0 ; python_version < '3.11'",
|
||||
"attrs >= 17.3.0",
|
||||
"frozenlist >= 1.1.1",
|
||||
"multidict >=4.5, < 7.0",
|
||||
"propcache >= 0.2.0",
|
||||
"yarl >= 1.17.0, < 2.0",
|
||||
]
|
||||
dynamic = [
|
||||
"version",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
speedups = [
|
||||
"aiodns >= 3.3.0",
|
||||
"Brotli >= 1.2; platform_python_implementation == 'CPython'",
|
||||
"brotlicffi >= 1.2; platform_python_implementation != 'CPython'",
|
||||
"backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14'",
|
||||
]
|
||||
|
||||
[[project.maintainers]]
|
||||
name = "aiohttp team"
|
||||
email = "team@aiohttp.org"
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/aio-libs/aiohttp"
|
||||
"Chat: Matrix" = "https://matrix.to/#/#aio-libs:matrix.org"
|
||||
"Chat: Matrix Space" = "https://matrix.to/#/#aio-libs-space:matrix.org"
|
||||
"CI: GitHub Actions" = "https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI"
|
||||
"Coverage: codecov" = "https://codecov.io/github/aio-libs/aiohttp"
|
||||
"Docs: Changelog" = "https://docs.aiohttp.org/en/stable/changes.html"
|
||||
"Docs: RTD" = "https://docs.aiohttp.org"
|
||||
"GitHub: issues" = "https://github.com/aio-libs/aiohttp/issues"
|
||||
"GitHub: repo" = "https://github.com/aio-libs/aiohttp"
|
||||
|
||||
[tool.setuptools]
|
||||
license-files = [
|
||||
# TODO: Use 'project.license-files' instead once setuptools is bumped to >=77
|
||||
"LICENSE.txt",
|
||||
"vendor/llhttp/LICENSE",
|
||||
]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "aiohttp.__version__"}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = [
|
||||
"aiohttp",
|
||||
"aiohttp.*",
|
||||
]
|
||||
|
||||
[tool.setuptools.exclude-package-data]
|
||||
"*" = ["*.c", "*.h"]
|
||||
|
||||
[tool.towncrier]
|
||||
package = "aiohttp"
|
||||
filename = "CHANGES.rst"
|
||||
@@ -88,8 +172,3 @@ ignore-words-list = 'te,ue'
|
||||
# TODO(3.13): Remove aiohttp.helpers once https://github.com/python/cpython/pull/106771
|
||||
# is available in all supported cpython versions
|
||||
exclude-modules = "(^aiohttp\\.helpers)"
|
||||
|
||||
[tool.black]
|
||||
# TODO: Remove when project metadata is moved here.
|
||||
# Black can read the value from [project.requires-python].
|
||||
target-version = ["py39", "py310", "py311", "py312"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
fdc695f12e2e48e0f73319168d74ce5fa039801e0e83c3d430e78156162d3882 /home/runner/work/aiohttp/aiohttp/requirements/cython.txt
|
||||
be58a7c23eaf4532a259b5ac86ace4c1bf9e55e67da7e4afec9dc55e8ed84597 /home/runner/work/aiohttp/aiohttp/requirements/cython.txt
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-r runtime-deps.in
|
||||
|
||||
gunicorn
|
||||
@@ -0,0 +1,50 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/base-ft.txt --strip-extras requirements/base-ft.in
|
||||
#
|
||||
aiodns==3.5.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiosignal==1.4.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
async-timeout==5.0.1 ; python_version < "3.11"
|
||||
# via -r requirements/runtime-deps.in
|
||||
attrs==25.3.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
brotli==1.2.0 ; platform_python_implementation == "CPython"
|
||||
# via -r requirements/runtime-deps.in
|
||||
cffi==2.0.0
|
||||
# via pycares
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# aiosignal
|
||||
gunicorn==23.0.0
|
||||
# via -r requirements/base-ft.in
|
||||
idna==3.10
|
||||
# via yarl
|
||||
multidict==6.6.4
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
packaging==25.0
|
||||
# via gunicorn
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
pycares==4.11.0
|
||||
# via aiodns
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# aiosignal
|
||||
# multidict
|
||||
yarl==1.21.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
backports.zstd==0.5.0 ; platform_python_implementation == "CPython" and python_version < "3.14"
|
||||
# via -r requirements/runtime-deps.in
|
||||
+16
-13
@@ -4,21 +4,21 @@
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/base.txt --strip-extras requirements/base.in
|
||||
#
|
||||
aiodns==3.4.0
|
||||
aiodns==3.5.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiosignal==1.3.2
|
||||
aiosignal==1.4.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
async-timeout==5.0.1 ; python_version < "3.11"
|
||||
# via -r requirements/runtime-deps.in
|
||||
attrs==25.3.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
brotli==1.1.0 ; platform_python_implementation == "CPython"
|
||||
brotli==1.2.0 ; platform_python_implementation == "CPython"
|
||||
# via -r requirements/runtime-deps.in
|
||||
cffi==1.17.1
|
||||
cffi==2.0.0
|
||||
# via pycares
|
||||
frozenlist==1.6.0
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# aiosignal
|
||||
@@ -26,24 +26,27 @@ gunicorn==23.0.0
|
||||
# via -r requirements/base.in
|
||||
idna==3.4
|
||||
# via yarl
|
||||
multidict==6.4.4
|
||||
multidict==6.6.4
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
packaging==25.0
|
||||
# via gunicorn
|
||||
propcache==0.3.1
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
pycares==4.8.0
|
||||
pycares==4.11.0
|
||||
# via aiodns
|
||||
pycparser==2.22
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
typing-extensions==4.13.2
|
||||
# via multidict
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# aiosignal
|
||||
# multidict
|
||||
uvloop==0.21.0 ; platform_system != "Windows" and implementation_name == "cpython"
|
||||
winloop==0.1.8; platform_system == "Windows" and implementation_name == "cpython"
|
||||
# via -r requirements/base.in
|
||||
yarl==1.20.0
|
||||
yarl==1.21.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
backports.zstd==0.5.0 ; platform_python_implementation == "CPython" and python_version < "3.14"
|
||||
# via -r requirements/runtime-deps.in
|
||||
|
||||
+88
-82
@@ -1,10 +1,10 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with python 3.10
|
||||
# To update, run:
|
||||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/constraints.txt --resolver=backtracking --strip-extras requirements/constraints.in
|
||||
# pip-compile --allow-unsafe --output-file=requirements/constraints.txt --strip-extras requirements/constraints.in
|
||||
#
|
||||
aiodns==3.4.0
|
||||
aiodns==3.5.0
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/runtime-deps.in
|
||||
@@ -12,7 +12,7 @@ aiohappyeyeballs==2.6.1
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiohttp-theme==0.1.7
|
||||
# via -r requirements/doc.in
|
||||
aiosignal==1.3.2
|
||||
aiosignal==1.4.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
alabaster==1.0.0
|
||||
# via sphinx
|
||||
@@ -26,26 +26,26 @@ attrs==25.3.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
babel==2.17.0
|
||||
# via sphinx
|
||||
blockbuster==1.5.24
|
||||
blockbuster==1.5.26
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
brotli==1.1.0 ; platform_python_implementation == "CPython"
|
||||
# -r requirements/test-common.in
|
||||
brotli==1.2.0 ; platform_python_implementation == "CPython"
|
||||
# via -r requirements/runtime-deps.in
|
||||
build==1.2.2.post1
|
||||
build==1.3.0
|
||||
# via pip-tools
|
||||
certifi==2025.4.26
|
||||
certifi==2025.10.5
|
||||
# via requests
|
||||
cffi==1.17.1
|
||||
cffi==2.0.0
|
||||
# via
|
||||
# cryptography
|
||||
# pycares
|
||||
# pytest-codspeed
|
||||
cfgv==3.4.0
|
||||
# via pre-commit
|
||||
charset-normalizer==3.4.2
|
||||
charset-normalizer==3.4.3
|
||||
# via requests
|
||||
cherry-picker==2.5.0
|
||||
cherry-picker==2.6.0
|
||||
# via -r requirements/dev.in
|
||||
click==8.1.8
|
||||
# via
|
||||
@@ -54,17 +54,17 @@ click==8.1.8
|
||||
# slotscheck
|
||||
# towncrier
|
||||
# wait-for-it
|
||||
coverage==7.8.1
|
||||
coverage==7.10.7
|
||||
# via
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
# pytest-cov
|
||||
cryptography==45.0.2
|
||||
cryptography==46.0.2
|
||||
# via
|
||||
# pyjwt
|
||||
# trustme
|
||||
cython==3.1.1
|
||||
cython==3.1.4
|
||||
# via -r requirements/cython.in
|
||||
distlib==0.3.9
|
||||
distlib==0.4.0
|
||||
# via virtualenv
|
||||
docutils==0.21.2
|
||||
# via sphinx
|
||||
@@ -72,23 +72,23 @@ exceptiongroup==1.3.0
|
||||
# via pytest
|
||||
execnet==2.1.1
|
||||
# via pytest-xdist
|
||||
filelock==3.18.0
|
||||
filelock==3.19.1
|
||||
# via virtualenv
|
||||
forbiddenfruit==0.1.4
|
||||
# via blockbuster
|
||||
freezegun==1.5.1
|
||||
freezegun==1.5.5
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
frozenlist==1.6.0
|
||||
# -r requirements/test-common.in
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# aiosignal
|
||||
gidgethub==5.3.0
|
||||
gidgethub==5.4.0
|
||||
# via cherry-picker
|
||||
gunicorn==23.0.0
|
||||
# via -r requirements/base.in
|
||||
identify==2.6.10
|
||||
identify==2.6.15
|
||||
# via pre-commit
|
||||
idna==3.3
|
||||
# via
|
||||
@@ -97,33 +97,31 @@ idna==3.3
|
||||
# yarl
|
||||
imagesize==1.4.1
|
||||
# via sphinx
|
||||
incremental==24.7.2
|
||||
# via towncrier
|
||||
iniconfig==2.1.0
|
||||
# via pytest
|
||||
isal==1.7.2
|
||||
isal==1.7.2 ; python_version < "3.14"
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
jinja2==3.1.6
|
||||
# via
|
||||
# sphinx
|
||||
# towncrier
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via jinja2
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
multidict==6.4.4
|
||||
multidict==6.6.4
|
||||
# via
|
||||
# -r requirements/multidict.in
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
mypy==1.15.0 ; implementation_name == "cpython"
|
||||
mypy==1.18.2 ; implementation_name == "cpython"
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
mypy-extensions==1.1.0
|
||||
# via mypy
|
||||
nodeenv==1.9.1
|
||||
@@ -134,34 +132,39 @@ packaging==25.0
|
||||
# gunicorn
|
||||
# pytest
|
||||
# sphinx
|
||||
pip-tools==7.4.1
|
||||
pathspec==0.12.1
|
||||
# via mypy
|
||||
pip-tools==7.5.1
|
||||
# via -r requirements/dev.in
|
||||
pkgconfig==1.5.5
|
||||
# via -r requirements/test.in
|
||||
platformdirs==4.3.8
|
||||
# via -r requirements/test-common.in
|
||||
platformdirs==4.4.0
|
||||
# via virtualenv
|
||||
pluggy==1.6.0
|
||||
# via pytest
|
||||
pre-commit==4.2.0
|
||||
# via
|
||||
# pytest
|
||||
# pytest-cov
|
||||
pre-commit==4.3.0
|
||||
# via -r requirements/lint.in
|
||||
propcache==0.3.1
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
proxy-py==2.4.10
|
||||
# via -r requirements/test.in
|
||||
pycares==4.8.0
|
||||
# via -r requirements/test-common.in
|
||||
pycares==4.11.0
|
||||
# via aiodns
|
||||
pycparser==2.22
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
pydantic==2.11.5
|
||||
pydantic==2.11.9
|
||||
# via python-on-whales
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pyenchant==3.2.2
|
||||
pyenchant==3.3.0
|
||||
# via sphinxcontrib-spelling
|
||||
pygments==2.19.1
|
||||
pygments==2.19.2
|
||||
# via
|
||||
# pytest
|
||||
# rich
|
||||
# sphinx
|
||||
pyjwt==2.9.0
|
||||
@@ -172,47 +175,47 @@ pyproject-hooks==1.2.0
|
||||
# via
|
||||
# build
|
||||
# pip-tools
|
||||
pytest==8.3.5
|
||||
pytest==8.4.2
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
# pytest-codspeed
|
||||
# pytest-cov
|
||||
# pytest-mock
|
||||
# pytest-xdist
|
||||
pytest-codspeed==3.2.0
|
||||
pytest-codspeed==4.0.0
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
pytest-cov==6.1.1
|
||||
# via -r requirements/test.in
|
||||
pytest-mock==3.14.0
|
||||
# -r requirements/test-common.in
|
||||
pytest-cov==7.0.0
|
||||
# via -r requirements/test-common.in
|
||||
pytest-mock==3.15.1
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
pytest-xdist==3.6.1
|
||||
# via -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
pytest-xdist==3.8.0
|
||||
# via -r requirements/test-common.in
|
||||
python-dateutil==2.9.0.post0
|
||||
# via freezegun
|
||||
python-on-whales==0.76.1
|
||||
python-on-whales==0.78.0
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
pyyaml==6.0.2
|
||||
# -r requirements/test-common.in
|
||||
pyyaml==6.0.3
|
||||
# via pre-commit
|
||||
re-assert==1.1.0
|
||||
# via -r requirements/test.in
|
||||
regex==2024.11.6
|
||||
# via -r requirements/test-common.in
|
||||
regex==2025.9.18
|
||||
# via re-assert
|
||||
requests==2.32.3
|
||||
requests==2.32.5
|
||||
# via
|
||||
# cherry-picker
|
||||
# sphinx
|
||||
# sphinxcontrib-spelling
|
||||
rich==14.0.0
|
||||
rich==14.1.0
|
||||
# via pytest-codspeed
|
||||
setuptools-git==1.2
|
||||
# via -r requirements/test.in
|
||||
# via -r requirements/test-common.in
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
slotscheck==0.19.1
|
||||
@@ -242,67 +245,70 @@ sphinxcontrib-towncrier==0.5.0a0
|
||||
# via -r requirements/doc.in
|
||||
stamina==25.1.0
|
||||
# via cherry-picker
|
||||
tenacity==9.0.0
|
||||
tenacity==9.1.2
|
||||
# via stamina
|
||||
tomli==2.2.1
|
||||
# via
|
||||
# build
|
||||
# cherry-picker
|
||||
# coverage
|
||||
# incremental
|
||||
# mypy
|
||||
# pip-tools
|
||||
# pytest
|
||||
# slotscheck
|
||||
# sphinx
|
||||
# towncrier
|
||||
towncrier==23.11.0
|
||||
towncrier==25.8.0
|
||||
# via
|
||||
# -r requirements/doc.in
|
||||
# sphinxcontrib-towncrier
|
||||
trustme==1.2.1 ; platform_machine != "i686"
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
typing-extensions==4.13.2
|
||||
# -r requirements/test-common.in
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# aiosignal
|
||||
# cryptography
|
||||
# exceptiongroup
|
||||
# multidict
|
||||
# mypy
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# python-on-whales
|
||||
# rich
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
# virtualenv
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
uritemplate==4.1.1
|
||||
uritemplate==4.2.0
|
||||
# via gidgethub
|
||||
urllib3==2.4.0
|
||||
urllib3==2.5.0
|
||||
# via requests
|
||||
uvloop==0.21.0 ; platform_system != "Windows"
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# -r requirements/lint.in
|
||||
valkey==6.1.0
|
||||
valkey==6.1.1
|
||||
# via -r requirements/lint.in
|
||||
virtualenv==20.31.2
|
||||
virtualenv==20.34.0
|
||||
# via pre-commit
|
||||
wait-for-it==2.3.0
|
||||
# via -r requirements/test.in
|
||||
# via -r requirements/test-common.in
|
||||
wheel==0.45.1
|
||||
# via pip-tools
|
||||
yarl==1.20.0
|
||||
yarl==1.21.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
zlib-ng==0.5.1
|
||||
zlib-ng==1.0.0
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
backports.zstd==0.5.0 ; implementation_name == "cpython"
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/runtime-deps.in
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
pip==25.1.1
|
||||
pip==25.2
|
||||
# via pip-tools
|
||||
setuptools==80.9.0
|
||||
# via pip-tools
|
||||
setuptools==80.8.0
|
||||
# via
|
||||
# incremental
|
||||
# pip-tools
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/cython.txt --resolver=backtracking --strip-extras requirements/cython.in
|
||||
#
|
||||
cython==3.1.1
|
||||
cython==3.1.4
|
||||
# via -r requirements/cython.in
|
||||
multidict==6.4.4
|
||||
multidict==6.6.4
|
||||
# via -r requirements/multidict.in
|
||||
typing-extensions==4.13.2
|
||||
typing-extensions==4.15.0
|
||||
# via multidict
|
||||
|
||||
+86
-80
@@ -1,10 +1,10 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with python 3.10
|
||||
# To update, run:
|
||||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/dev.txt --resolver=backtracking --strip-extras requirements/dev.in
|
||||
# pip-compile --allow-unsafe --output-file=requirements/dev.txt --strip-extras requirements/dev.in
|
||||
#
|
||||
aiodns==3.4.0
|
||||
aiodns==3.5.0
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/runtime-deps.in
|
||||
@@ -12,7 +12,7 @@ aiohappyeyeballs==2.6.1
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiohttp-theme==0.1.7
|
||||
# via -r requirements/doc.in
|
||||
aiosignal==1.3.2
|
||||
aiosignal==1.4.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
alabaster==1.0.0
|
||||
# via sphinx
|
||||
@@ -26,26 +26,26 @@ attrs==25.3.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
babel==2.17.0
|
||||
# via sphinx
|
||||
blockbuster==1.5.24
|
||||
blockbuster==1.5.26
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
brotli==1.1.0 ; platform_python_implementation == "CPython"
|
||||
# -r requirements/test-common.in
|
||||
brotli==1.2.0 ; platform_python_implementation == "CPython"
|
||||
# via -r requirements/runtime-deps.in
|
||||
build==1.2.2.post1
|
||||
build==1.3.0
|
||||
# via pip-tools
|
||||
certifi==2025.4.26
|
||||
certifi==2025.10.5
|
||||
# via requests
|
||||
cffi==1.17.1
|
||||
cffi==2.0.0
|
||||
# via
|
||||
# cryptography
|
||||
# pycares
|
||||
# pytest-codspeed
|
||||
cfgv==3.4.0
|
||||
# via pre-commit
|
||||
charset-normalizer==3.4.2
|
||||
charset-normalizer==3.4.3
|
||||
# via requests
|
||||
cherry-picker==2.5.0
|
||||
cherry-picker==2.6.0
|
||||
# via -r requirements/dev.in
|
||||
click==8.1.8
|
||||
# via
|
||||
@@ -54,15 +54,15 @@ click==8.1.8
|
||||
# slotscheck
|
||||
# towncrier
|
||||
# wait-for-it
|
||||
coverage==7.8.1
|
||||
coverage==7.10.7
|
||||
# via
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
# pytest-cov
|
||||
cryptography==45.0.2
|
||||
cryptography==46.0.2
|
||||
# via
|
||||
# pyjwt
|
||||
# trustme
|
||||
distlib==0.3.9
|
||||
distlib==0.4.0
|
||||
# via virtualenv
|
||||
docutils==0.21.2
|
||||
# via sphinx
|
||||
@@ -70,23 +70,23 @@ exceptiongroup==1.3.0
|
||||
# via pytest
|
||||
execnet==2.1.1
|
||||
# via pytest-xdist
|
||||
filelock==3.18.0
|
||||
filelock==3.19.1
|
||||
# via virtualenv
|
||||
forbiddenfruit==0.1.4
|
||||
# via blockbuster
|
||||
freezegun==1.5.1
|
||||
freezegun==1.5.5
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
frozenlist==1.6.0
|
||||
# -r requirements/test-common.in
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# aiosignal
|
||||
gidgethub==5.3.0
|
||||
gidgethub==5.4.0
|
||||
# via cherry-picker
|
||||
gunicorn==23.0.0
|
||||
# via -r requirements/base.in
|
||||
identify==2.6.10
|
||||
identify==2.6.15
|
||||
# via pre-commit
|
||||
idna==3.4
|
||||
# via
|
||||
@@ -95,32 +95,30 @@ idna==3.4
|
||||
# yarl
|
||||
imagesize==1.4.1
|
||||
# via sphinx
|
||||
incremental==24.7.2
|
||||
# via towncrier
|
||||
iniconfig==2.1.0
|
||||
# via pytest
|
||||
isal==1.7.2
|
||||
isal==1.7.2 ; python_version < "3.14"
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
jinja2==3.1.6
|
||||
# via
|
||||
# sphinx
|
||||
# towncrier
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via jinja2
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
multidict==6.4.4
|
||||
multidict==6.6.4
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
mypy==1.15.0 ; implementation_name == "cpython"
|
||||
mypy==1.18.2 ; implementation_name == "cpython"
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
mypy-extensions==1.1.0
|
||||
# via mypy
|
||||
nodeenv==1.9.1
|
||||
@@ -131,32 +129,37 @@ packaging==25.0
|
||||
# gunicorn
|
||||
# pytest
|
||||
# sphinx
|
||||
pip-tools==7.4.1
|
||||
pathspec==0.12.1
|
||||
# via mypy
|
||||
pip-tools==7.5.1
|
||||
# via -r requirements/dev.in
|
||||
pkgconfig==1.5.5
|
||||
# via -r requirements/test.in
|
||||
platformdirs==4.3.8
|
||||
# via -r requirements/test-common.in
|
||||
platformdirs==4.4.0
|
||||
# via virtualenv
|
||||
pluggy==1.6.0
|
||||
# via pytest
|
||||
pre-commit==4.2.0
|
||||
# via
|
||||
# pytest
|
||||
# pytest-cov
|
||||
pre-commit==4.3.0
|
||||
# via -r requirements/lint.in
|
||||
propcache==0.3.1
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
proxy-py==2.4.10
|
||||
# via -r requirements/test.in
|
||||
pycares==4.8.0
|
||||
# via -r requirements/test-common.in
|
||||
pycares==4.11.0
|
||||
# via aiodns
|
||||
pycparser==2.22
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
pydantic==2.11.5
|
||||
pydantic==2.11.9
|
||||
# via python-on-whales
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pygments==2.19.1
|
||||
pygments==2.19.2
|
||||
# via
|
||||
# pytest
|
||||
# rich
|
||||
# sphinx
|
||||
pyjwt==2.8.0
|
||||
@@ -167,46 +170,46 @@ pyproject-hooks==1.2.0
|
||||
# via
|
||||
# build
|
||||
# pip-tools
|
||||
pytest==8.3.5
|
||||
pytest==8.4.2
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
# pytest-codspeed
|
||||
# pytest-cov
|
||||
# pytest-mock
|
||||
# pytest-xdist
|
||||
pytest-codspeed==3.2.0
|
||||
pytest-codspeed==4.0.0
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
pytest-cov==6.1.1
|
||||
# via -r requirements/test.in
|
||||
pytest-mock==3.14.0
|
||||
# -r requirements/test-common.in
|
||||
pytest-cov==7.0.0
|
||||
# via -r requirements/test-common.in
|
||||
pytest-mock==3.15.1
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
pytest-xdist==3.6.1
|
||||
# via -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
pytest-xdist==3.8.0
|
||||
# via -r requirements/test-common.in
|
||||
python-dateutil==2.9.0.post0
|
||||
# via freezegun
|
||||
python-on-whales==0.76.1
|
||||
python-on-whales==0.78.0
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
pyyaml==6.0.2
|
||||
# -r requirements/test-common.in
|
||||
pyyaml==6.0.3
|
||||
# via pre-commit
|
||||
re-assert==1.1.0
|
||||
# via -r requirements/test.in
|
||||
regex==2024.11.6
|
||||
# via -r requirements/test-common.in
|
||||
regex==2025.9.18
|
||||
# via re-assert
|
||||
requests==2.32.3
|
||||
requests==2.32.5
|
||||
# via
|
||||
# cherry-picker
|
||||
# sphinx
|
||||
rich==14.0.0
|
||||
rich==14.1.0
|
||||
# via pytest-codspeed
|
||||
setuptools-git==1.2
|
||||
# via -r requirements/test.in
|
||||
# via -r requirements/test-common.in
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
slotscheck==0.19.1
|
||||
@@ -233,67 +236,70 @@ sphinxcontrib-towncrier==0.5.0a0
|
||||
# via -r requirements/doc.in
|
||||
stamina==25.1.0
|
||||
# via cherry-picker
|
||||
tenacity==9.0.0
|
||||
tenacity==9.1.2
|
||||
# via stamina
|
||||
tomli==2.2.1
|
||||
# via
|
||||
# build
|
||||
# cherry-picker
|
||||
# coverage
|
||||
# incremental
|
||||
# mypy
|
||||
# pip-tools
|
||||
# pytest
|
||||
# slotscheck
|
||||
# sphinx
|
||||
# towncrier
|
||||
towncrier==23.11.0
|
||||
towncrier==25.8.0
|
||||
# via
|
||||
# -r requirements/doc.in
|
||||
# sphinxcontrib-towncrier
|
||||
trustme==1.2.1 ; platform_machine != "i686"
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
typing-extensions==4.13.2
|
||||
# -r requirements/test-common.in
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# aiosignal
|
||||
# cryptography
|
||||
# exceptiongroup
|
||||
# multidict
|
||||
# mypy
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# python-on-whales
|
||||
# rich
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
# virtualenv
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
uritemplate==4.1.1
|
||||
uritemplate==4.2.0
|
||||
# via gidgethub
|
||||
urllib3==2.4.0
|
||||
urllib3==2.5.0
|
||||
# via requests
|
||||
uvloop==0.21.0 ; platform_system != "Windows" and implementation_name == "cpython"
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# -r requirements/lint.in
|
||||
valkey==6.1.0
|
||||
valkey==6.1.1
|
||||
# via -r requirements/lint.in
|
||||
virtualenv==20.31.2
|
||||
virtualenv==20.34.0
|
||||
# via pre-commit
|
||||
wait-for-it==2.3.0
|
||||
# via -r requirements/test.in
|
||||
# via -r requirements/test-common.in
|
||||
wheel==0.45.1
|
||||
# via pip-tools
|
||||
yarl==1.20.0
|
||||
yarl==1.21.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
zlib-ng==0.5.1
|
||||
zlib-ng==1.0.0
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/test.in
|
||||
# -r requirements/test-common.in
|
||||
backports.zstd==0.5.0 ; platform_python_implementation == "CPython" and python_version < "3.14"
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# -r requirements/runtime-deps.in
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
pip==25.1.1
|
||||
pip==25.2
|
||||
# via pip-tools
|
||||
setuptools==80.9.0
|
||||
# via pip-tools
|
||||
setuptools==80.8.0
|
||||
# via
|
||||
# incremental
|
||||
# pip-tools
|
||||
|
||||
+8
-15
@@ -10,9 +10,9 @@ alabaster==1.0.0
|
||||
# via sphinx
|
||||
babel==2.17.0
|
||||
# via sphinx
|
||||
certifi==2025.4.26
|
||||
certifi==2025.10.5
|
||||
# via requests
|
||||
charset-normalizer==3.4.2
|
||||
charset-normalizer==3.4.3
|
||||
# via requests
|
||||
click==8.1.8
|
||||
# via towncrier
|
||||
@@ -22,21 +22,19 @@ idna==3.4
|
||||
# via requests
|
||||
imagesize==1.4.1
|
||||
# via sphinx
|
||||
incremental==24.7.2
|
||||
# via towncrier
|
||||
jinja2==3.1.6
|
||||
# via
|
||||
# sphinx
|
||||
# towncrier
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via jinja2
|
||||
packaging==25.0
|
||||
# via sphinx
|
||||
pyenchant==3.2.2
|
||||
pyenchant==3.3.0
|
||||
# via sphinxcontrib-spelling
|
||||
pygments==2.19.1
|
||||
pygments==2.19.2
|
||||
# via sphinx
|
||||
requests==2.32.3
|
||||
requests==2.32.5
|
||||
# via
|
||||
# sphinx
|
||||
# sphinxcontrib-spelling
|
||||
@@ -65,16 +63,11 @@ sphinxcontrib-towncrier==0.5.0a0
|
||||
# via -r requirements/doc.in
|
||||
tomli==2.2.1
|
||||
# via
|
||||
# incremental
|
||||
# sphinx
|
||||
# towncrier
|
||||
towncrier==23.11.0
|
||||
towncrier==25.8.0
|
||||
# via
|
||||
# -r requirements/doc.in
|
||||
# sphinxcontrib-towncrier
|
||||
urllib3==2.4.0
|
||||
urllib3==2.5.0
|
||||
# via requests
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
setuptools==80.8.0
|
||||
# via incremental
|
||||
|
||||
+8
-15
@@ -10,31 +10,29 @@ alabaster==1.0.0
|
||||
# via sphinx
|
||||
babel==2.17.0
|
||||
# via sphinx
|
||||
certifi==2025.4.26
|
||||
certifi==2025.10.5
|
||||
# via requests
|
||||
charset-normalizer==3.4.2
|
||||
charset-normalizer==3.4.3
|
||||
# via requests
|
||||
click==8.1.8
|
||||
# via towncrier
|
||||
docutils==0.21.2
|
||||
# via sphinx
|
||||
idna==3.4
|
||||
idna==3.10
|
||||
# via requests
|
||||
imagesize==1.4.1
|
||||
# via sphinx
|
||||
incremental==24.7.2
|
||||
# via towncrier
|
||||
jinja2==3.1.6
|
||||
# via
|
||||
# sphinx
|
||||
# towncrier
|
||||
markupsafe==3.0.2
|
||||
markupsafe==3.0.3
|
||||
# via jinja2
|
||||
packaging==25.0
|
||||
# via sphinx
|
||||
pygments==2.19.1
|
||||
pygments==2.19.2
|
||||
# via sphinx
|
||||
requests==2.32.3
|
||||
requests==2.32.5
|
||||
# via sphinx
|
||||
snowballstemmer==3.0.1
|
||||
# via sphinx
|
||||
@@ -58,16 +56,11 @@ sphinxcontrib-towncrier==0.5.0a0
|
||||
# via -r requirements/doc.in
|
||||
tomli==2.2.1
|
||||
# via
|
||||
# incremental
|
||||
# sphinx
|
||||
# towncrier
|
||||
towncrier==23.11.0
|
||||
towncrier==25.8.0
|
||||
# via
|
||||
# -r requirements/doc.in
|
||||
# sphinxcontrib-towncrier
|
||||
urllib3==2.4.0
|
||||
urllib3==2.5.0
|
||||
# via requests
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
setuptools==80.8.0
|
||||
# via incremental
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
aiodns
|
||||
backports.zstd; implementation_name == "cpython"
|
||||
blockbuster
|
||||
freezegun
|
||||
isal
|
||||
|
||||
+36
-29
@@ -1,18 +1,18 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with python 3.10
|
||||
# This file is autogenerated by pip-compile with Python 3.12
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/lint.txt --resolver=backtracking --strip-extras requirements/lint.in
|
||||
#
|
||||
aiodns==3.4.0
|
||||
aiodns==3.5.0
|
||||
# via -r requirements/lint.in
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
async-timeout==5.0.1
|
||||
# via valkey
|
||||
blockbuster==1.5.24
|
||||
blockbuster==1.5.26
|
||||
# via -r requirements/lint.in
|
||||
cffi==1.17.1
|
||||
cffi==2.0.0
|
||||
# via
|
||||
# cryptography
|
||||
# pycares
|
||||
@@ -21,19 +21,19 @@ cfgv==3.4.0
|
||||
# via pre-commit
|
||||
click==8.1.8
|
||||
# via slotscheck
|
||||
cryptography==45.0.2
|
||||
cryptography==46.0.2
|
||||
# via trustme
|
||||
distlib==0.3.9
|
||||
distlib==0.4.0
|
||||
# via virtualenv
|
||||
exceptiongroup==1.3.0
|
||||
# via pytest
|
||||
filelock==3.18.0
|
||||
filelock==3.19.1
|
||||
# via virtualenv
|
||||
forbiddenfruit==0.1.4
|
||||
# via blockbuster
|
||||
freezegun==1.5.1
|
||||
freezegun==1.5.5
|
||||
# via -r requirements/lint.in
|
||||
identify==2.6.10
|
||||
identify==2.6.15
|
||||
# via pre-commit
|
||||
idna==3.7
|
||||
# via trustme
|
||||
@@ -45,7 +45,7 @@ markdown-it-py==3.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
mypy==1.15.0 ; implementation_name == "cpython"
|
||||
mypy==1.18.2 ; implementation_name == "cpython"
|
||||
# via -r requirements/lint.in
|
||||
mypy-extensions==1.1.0
|
||||
# via mypy
|
||||
@@ -53,38 +53,42 @@ nodeenv==1.9.1
|
||||
# via pre-commit
|
||||
packaging==25.0
|
||||
# via pytest
|
||||
platformdirs==4.3.8
|
||||
pathspec==0.12.1
|
||||
# via mypy
|
||||
platformdirs==4.4.0
|
||||
# via virtualenv
|
||||
pluggy==1.6.0
|
||||
# via pytest
|
||||
pre-commit==4.2.0
|
||||
pre-commit==4.3.0
|
||||
# via -r requirements/lint.in
|
||||
pycares==4.8.0
|
||||
pycares==4.11.0
|
||||
# via aiodns
|
||||
pycparser==2.22
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
pydantic==2.11.5
|
||||
pydantic==2.11.9
|
||||
# via python-on-whales
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pygments==2.19.1
|
||||
# via rich
|
||||
pytest==8.3.5
|
||||
pygments==2.19.2
|
||||
# via
|
||||
# pytest
|
||||
# rich
|
||||
pytest==8.4.2
|
||||
# via
|
||||
# -r requirements/lint.in
|
||||
# pytest-codspeed
|
||||
# pytest-mock
|
||||
pytest-codspeed==3.2.0
|
||||
pytest-codspeed==4.0.0
|
||||
# via -r requirements/lint.in
|
||||
pytest-mock==3.14.0
|
||||
pytest-mock==3.15.1
|
||||
# via -r requirements/lint.in
|
||||
python-dateutil==2.9.0.post0
|
||||
# via freezegun
|
||||
python-on-whales==0.76.1
|
||||
python-on-whales==0.78.0
|
||||
# via -r requirements/lint.in
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
# via pre-commit
|
||||
rich==14.0.0
|
||||
rich==14.1.0
|
||||
# via pytest-codspeed
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
@@ -97,22 +101,25 @@ tomli==2.2.1
|
||||
# slotscheck
|
||||
trustme==1.2.1
|
||||
# via -r requirements/lint.in
|
||||
typing-extensions==4.13.2
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# cryptography
|
||||
# exceptiongroup
|
||||
# mypy
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# python-on-whales
|
||||
# rich
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
# virtualenv
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
uvloop==0.21.0 ; platform_system != "Windows"
|
||||
# via -r requirements/lint.in
|
||||
valkey==6.1.0
|
||||
valkey==6.1.1
|
||||
# via -r requirements/lint.in
|
||||
virtualenv==20.31.2
|
||||
virtualenv==20.34.0
|
||||
# via pre-commit
|
||||
zlib-ng==0.5.1
|
||||
zlib-ng==1.0.0
|
||||
# via -r requirements/lint.in
|
||||
backports.zstd==0.5.0 ; implementation_name == "cpython"
|
||||
# via -r requirements/lint.in
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/multidict.txt --resolver=backtracking --strip-extras requirements/multidict.in
|
||||
#
|
||||
multidict==6.4.4
|
||||
multidict==6.6.4
|
||||
# via -r requirements/multidict.in
|
||||
typing-extensions==4.13.2
|
||||
typing-extensions==4.15.0
|
||||
# via multidict
|
||||
|
||||
+6
-5
@@ -1,12 +1,13 @@
|
||||
# Extracted from `setup.cfg` via `make sync-direct-runtime-deps`
|
||||
# Extracted from `pyproject.toml` via `make sync-direct-runtime-deps`
|
||||
|
||||
aiodns >= 3.3.0
|
||||
aiohappyeyeballs >= 2.5.0
|
||||
aiosignal >= 1.1.2
|
||||
async-timeout >= 4.0, < 6.0 ; python_version < "3.11"
|
||||
aiosignal >= 1.4.0
|
||||
async-timeout >= 4.0, < 6.0 ; python_version < '3.11'
|
||||
attrs >= 17.3.0
|
||||
Brotli; platform_python_implementation == 'CPython'
|
||||
brotlicffi; platform_python_implementation != 'CPython'
|
||||
backports.zstd; platform_python_implementation == 'CPython' and python_version < '3.14'
|
||||
Brotli >= 1.2; platform_python_implementation == 'CPython'
|
||||
brotlicffi >= 1.2; platform_python_implementation != 'CPython'
|
||||
frozenlist >= 1.1.1
|
||||
multidict >=4.5, < 7.0
|
||||
propcache >= 0.2.0
|
||||
|
||||
+18
-14
@@ -1,42 +1,46 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# This file is autogenerated by pip-compile with Python 3.12
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/runtime-deps.txt --strip-extras requirements/runtime-deps.in
|
||||
#
|
||||
aiodns==3.4.0
|
||||
aiodns==3.5.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiosignal==1.3.2
|
||||
aiosignal==1.4.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
async-timeout==5.0.1 ; python_version < "3.11"
|
||||
# via -r requirements/runtime-deps.in
|
||||
attrs==25.3.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
brotli==1.1.0 ; platform_python_implementation == "CPython"
|
||||
brotli==1.2.0 ; platform_python_implementation == "CPython"
|
||||
# via -r requirements/runtime-deps.in
|
||||
cffi==1.17.1
|
||||
cffi==2.0.0
|
||||
# via pycares
|
||||
frozenlist==1.6.0
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# aiosignal
|
||||
idna==3.4
|
||||
idna==3.10
|
||||
# via yarl
|
||||
multidict==6.4.4
|
||||
multidict==6.6.4
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
propcache==0.3.1
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
pycares==4.8.0
|
||||
pycares==4.11.0
|
||||
# via aiodns
|
||||
pycparser==2.22
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
typing-extensions==4.13.2
|
||||
# via multidict
|
||||
yarl==1.20.0
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# aiosignal
|
||||
# multidict
|
||||
yarl==1.21.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
backports.zstd==0.5.0 ; platform_python_implementation == "CPython" and python_version < "3.14"
|
||||
# via -r requirements/runtime-deps.in
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Sync direct runtime dependencies from setup.cfg to runtime-deps.in."""
|
||||
"""Sync direct runtime dependencies from pyproject.toml to runtime-deps.in."""
|
||||
|
||||
from configparser import ConfigParser
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
cfg = ConfigParser()
|
||||
cfg.read(Path("setup.cfg"))
|
||||
reqs = cfg["options"]["install_requires"] + cfg.items("options.extras_require")[0][1]
|
||||
reqs = sorted(reqs.split("\n"), key=str.casefold)
|
||||
reqs.remove("")
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
else:
|
||||
raise RuntimeError("Use Python 3.11+ to run 'make sync-direct-runtime-deps'")
|
||||
|
||||
data = tomllib.loads(Path("pyproject.toml").read_text())
|
||||
reqs = (
|
||||
data["project"]["dependencies"]
|
||||
+ data["project"]["optional-dependencies"]["speedups"]
|
||||
)
|
||||
reqs = sorted(reqs, key=str.casefold)
|
||||
|
||||
with open(Path("requirements", "runtime-deps.in"), "w") as outfile:
|
||||
header = "# Extracted from `setup.cfg` via `make sync-direct-runtime-deps`\n\n"
|
||||
header = "# Extracted from `pyproject.toml` via `make sync-direct-runtime-deps`\n\n"
|
||||
outfile.write(header)
|
||||
outfile.write("\n".join(reqs) + "\n")
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
blockbuster
|
||||
coverage
|
||||
freezegun
|
||||
isal; python_version < "3.14" # no wheel for 3.14
|
||||
mypy; implementation_name == "cpython"
|
||||
pkgconfig
|
||||
proxy.py >= 2.4.4rc5
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-mock
|
||||
pytest-xdist
|
||||
pytest_codspeed
|
||||
python-on-whales
|
||||
re-assert
|
||||
setuptools-git
|
||||
trustme; platform_machine != "i686" # no 32-bit wheels
|
||||
wait-for-it
|
||||
zlib_ng
|
||||
@@ -0,0 +1,117 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/test-common.txt --strip-extras requirements/test-common.in
|
||||
#
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
blockbuster==1.5.26
|
||||
# via -r requirements/test-common.in
|
||||
cffi==2.0.0
|
||||
# via
|
||||
# cryptography
|
||||
# pytest-codspeed
|
||||
click==8.2.1
|
||||
# via wait-for-it
|
||||
coverage==7.10.7
|
||||
# via
|
||||
# -r requirements/test-common.in
|
||||
# pytest-cov
|
||||
cryptography==46.0.2
|
||||
# via trustme
|
||||
exceptiongroup==1.3.0
|
||||
# via pytest
|
||||
execnet==2.1.1
|
||||
# via pytest-xdist
|
||||
forbiddenfruit==0.1.4
|
||||
# via blockbuster
|
||||
freezegun==1.5.5
|
||||
# via -r requirements/test-common.in
|
||||
idna==3.10
|
||||
# via trustme
|
||||
iniconfig==2.1.0
|
||||
# via pytest
|
||||
isal==1.8.0 ; python_version < "3.14"
|
||||
# via -r requirements/test-common.in
|
||||
markdown-it-py==4.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
mypy==1.18.2 ; implementation_name == "cpython"
|
||||
# via -r requirements/test-common.in
|
||||
mypy-extensions==1.1.0
|
||||
# via mypy
|
||||
packaging==25.0
|
||||
# via pytest
|
||||
pathspec==0.12.1
|
||||
# via mypy
|
||||
pkgconfig==1.5.5
|
||||
# via -r requirements/test-common.in
|
||||
pluggy==1.6.0
|
||||
# via
|
||||
# pytest
|
||||
# pytest-cov
|
||||
proxy-py==2.4.10
|
||||
# via -r requirements/test-common.in
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
pydantic==2.12.0a1
|
||||
# via python-on-whales
|
||||
pydantic-core==2.37.2
|
||||
# via pydantic
|
||||
pygments==2.19.2
|
||||
# via
|
||||
# pytest
|
||||
# rich
|
||||
pytest==8.4.2
|
||||
# via
|
||||
# -r requirements/test-common.in
|
||||
# pytest-codspeed
|
||||
# pytest-cov
|
||||
# pytest-mock
|
||||
# pytest-xdist
|
||||
pytest-codspeed==4.0.0
|
||||
# via -r requirements/test-common.in
|
||||
pytest-cov==7.0.0
|
||||
# via -r requirements/test-common.in
|
||||
pytest-mock==3.15.1
|
||||
# via -r requirements/test-common.in
|
||||
pytest-xdist==3.8.0
|
||||
# via -r requirements/test-common.in
|
||||
python-dateutil==2.9.0.post0
|
||||
# via freezegun
|
||||
python-on-whales==0.78.0
|
||||
# via -r requirements/test-common.in
|
||||
re-assert==1.1.0
|
||||
# via -r requirements/test-common.in
|
||||
regex==2025.9.18
|
||||
# via re-assert
|
||||
rich==14.1.0
|
||||
# via pytest-codspeed
|
||||
setuptools-git==1.2
|
||||
# via -r requirements/test-common.in
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
tomli==2.2.1
|
||||
# via
|
||||
# coverage
|
||||
# mypy
|
||||
# pytest
|
||||
trustme==1.2.1 ; platform_machine != "i686"
|
||||
# via -r requirements/test-common.in
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# cryptography
|
||||
# exceptiongroup
|
||||
# mypy
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# python-on-whales
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
wait-for-it==2.3.0
|
||||
# via -r requirements/test-common.in
|
||||
zlib-ng==1.0.0
|
||||
# via -r requirements/test-common.in
|
||||
@@ -0,0 +1,2 @@
|
||||
-r base-ft.in
|
||||
-r test-common.in
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/test-ft.txt --strip-extras requirements/test-ft.in
|
||||
#
|
||||
aiodns==3.5.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiohappyeyeballs==2.6.1
|
||||
# via -r requirements/runtime-deps.in
|
||||
aiosignal==1.4.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
async-timeout==5.0.1 ; python_version < "3.11"
|
||||
# via -r requirements/runtime-deps.in
|
||||
attrs==25.3.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
blockbuster==1.5.26
|
||||
# via -r requirements/test-common.in
|
||||
brotli==1.2.0 ; platform_python_implementation == "CPython"
|
||||
# via -r requirements/runtime-deps.in
|
||||
cffi==2.0.0
|
||||
# via
|
||||
# cryptography
|
||||
# pycares
|
||||
# pytest-codspeed
|
||||
click==8.2.1
|
||||
# via wait-for-it
|
||||
coverage==7.10.7
|
||||
# via
|
||||
# -r requirements/test-common.in
|
||||
# pytest-cov
|
||||
cryptography==46.0.2
|
||||
# via trustme
|
||||
exceptiongroup==1.3.0
|
||||
# via pytest
|
||||
execnet==2.1.1
|
||||
# via pytest-xdist
|
||||
forbiddenfruit==0.1.4
|
||||
# via blockbuster
|
||||
freezegun==1.5.5
|
||||
# via -r requirements/test-common.in
|
||||
frozenlist==1.8.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# aiosignal
|
||||
gunicorn==23.0.0
|
||||
# via -r requirements/base-ft.in
|
||||
idna==3.10
|
||||
# via
|
||||
# trustme
|
||||
# yarl
|
||||
iniconfig==2.1.0
|
||||
# via pytest
|
||||
isal==1.8.0 ; python_version < "3.14"
|
||||
# via -r requirements/test-common.in
|
||||
markdown-it-py==4.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
multidict==6.6.4
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
mypy==1.18.2 ; implementation_name == "cpython"
|
||||
# via -r requirements/test-common.in
|
||||
mypy-extensions==1.1.0
|
||||
# via mypy
|
||||
packaging==25.0
|
||||
# via
|
||||
# gunicorn
|
||||
# pytest
|
||||
pathspec==0.12.1
|
||||
# via mypy
|
||||
pkgconfig==1.5.5
|
||||
# via -r requirements/test-common.in
|
||||
pluggy==1.6.0
|
||||
# via
|
||||
# pytest
|
||||
# pytest-cov
|
||||
propcache==0.4.0
|
||||
# via
|
||||
# -r requirements/runtime-deps.in
|
||||
# yarl
|
||||
proxy-py==2.4.10
|
||||
# via -r requirements/test-common.in
|
||||
pycares==4.11.0
|
||||
# via aiodns
|
||||
pycparser==2.23
|
||||
# via cffi
|
||||
pydantic==2.12.0a1
|
||||
# via python-on-whales
|
||||
pydantic-core==2.37.2
|
||||
# via pydantic
|
||||
pygments==2.19.2
|
||||
# via
|
||||
# pytest
|
||||
# rich
|
||||
pytest==8.4.2
|
||||
# via
|
||||
# -r requirements/test-common.in
|
||||
# pytest-codspeed
|
||||
# pytest-cov
|
||||
# pytest-mock
|
||||
# pytest-xdist
|
||||
pytest-codspeed==4.0.0
|
||||
# via -r requirements/test-common.in
|
||||
pytest-cov==7.0.0
|
||||
# via -r requirements/test-common.in
|
||||
pytest-mock==3.15.1
|
||||
# via -r requirements/test-common.in
|
||||
pytest-xdist==3.8.0
|
||||
# via -r requirements/test-common.in
|
||||
python-dateutil==2.9.0.post0
|
||||
# via freezegun
|
||||
python-on-whales==0.78.0
|
||||
# via -r requirements/test-common.in
|
||||
re-assert==1.1.0
|
||||
# via -r requirements/test-common.in
|
||||
regex==2025.9.18
|
||||
# via re-assert
|
||||
rich==14.1.0
|
||||
# via pytest-codspeed
|
||||
setuptools-git==1.2
|
||||
# via -r requirements/test-common.in
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
tomli==2.2.1
|
||||
# via
|
||||
# coverage
|
||||
# mypy
|
||||
# pytest
|
||||
trustme==1.2.1 ; platform_machine != "i686"
|
||||
# via -r requirements/test-common.in
|
||||
typing-extensions==4.15.0
|
||||
# via
|
||||
# aiosignal
|
||||
# cryptography
|
||||
# exceptiongroup
|
||||
# multidict
|
||||
# mypy
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# python-on-whales
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.2
|
||||
# via pydantic
|
||||
wait-for-it==2.3.0
|
||||
# via -r requirements/test-common.in
|
||||
yarl==1.21.0
|
||||
# via -r requirements/runtime-deps.in
|
||||
zlib-ng==1.0.0
|
||||
# via -r requirements/test-common.in
|
||||
backports.zstd==0.5.0 ; platform_python_implementation == "CPython" and python_version < "3.14"
|
||||
# via -r requirements/runtime-deps.in
|
||||
+1
-19
@@ -1,20 +1,2 @@
|
||||
-r base.in
|
||||
|
||||
blockbuster
|
||||
coverage
|
||||
freezegun
|
||||
isal
|
||||
mypy; implementation_name == "cpython"
|
||||
pkgconfig
|
||||
proxy.py >= 2.4.4rc5
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-mock
|
||||
pytest-xdist
|
||||
pytest_codspeed
|
||||
python-on-whales
|
||||
re-assert
|
||||
setuptools-git
|
||||
trustme; platform_machine != "i686" # no 32-bit wheels
|
||||
wait-for-it
|
||||
zlib_ng
|
||||
-r test-common.in
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user