mirror of https://github.com/scrapy/scrapy.git
Remove Python 3.9 support (#7121)
This commit is contained in:
parent
e48a1bdde3
commit
d414d393d4
|
|
@ -20,10 +20,10 @@ jobs:
|
||||||
- python-version: "3.13"
|
- python-version: "3.13"
|
||||||
env:
|
env:
|
||||||
TOXENV: pylint
|
TOXENV: pylint
|
||||||
- python-version: "3.9"
|
- python-version: "3.10"
|
||||||
env:
|
env:
|
||||||
TOXENV: typing
|
TOXENV: typing
|
||||||
- python-version: "3.9"
|
- python-version: "3.10"
|
||||||
env:
|
env:
|
||||||
TOXENV: typing-tests
|
TOXENV: typing-tests
|
||||||
- python-version: "3.13" # Keep in sync with .readthedocs.yml
|
- python-version: "3.13" # Keep in sync with .readthedocs.yml
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,6 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- python-version: "3.9"
|
|
||||||
env:
|
|
||||||
TOXENV: py
|
|
||||||
- python-version: "3.10"
|
- python-version: "3.10"
|
||||||
env:
|
env:
|
||||||
TOXENV: py
|
TOXENV: py
|
||||||
|
|
@ -40,19 +37,19 @@ jobs:
|
||||||
TOXENV: pypy3
|
TOXENV: pypy3
|
||||||
|
|
||||||
# pinned deps
|
# pinned deps
|
||||||
- python-version: "3.9.21"
|
- python-version: "3.10.19"
|
||||||
env:
|
env:
|
||||||
TOXENV: pinned
|
TOXENV: pinned
|
||||||
- python-version: "3.9.21"
|
- python-version: "3.10.19"
|
||||||
env:
|
env:
|
||||||
TOXENV: default-reactor-pinned
|
TOXENV: default-reactor-pinned
|
||||||
- python-version: pypy3.11
|
- python-version: pypy3.11
|
||||||
env:
|
env:
|
||||||
TOXENV: pypy3-pinned
|
TOXENV: pypy3-pinned
|
||||||
- python-version: "3.9.21"
|
- python-version: "3.10.19"
|
||||||
env:
|
env:
|
||||||
TOXENV: extra-deps-pinned
|
TOXENV: extra-deps-pinned
|
||||||
- python-version: "3.9.21"
|
- python-version: "3.10.19"
|
||||||
env:
|
env:
|
||||||
TOXENV: botocore-pinned
|
TOXENV: botocore-pinned
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,6 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- python-version: "3.9"
|
|
||||||
env:
|
|
||||||
TOXENV: py
|
|
||||||
- python-version: "3.10"
|
- python-version: "3.10"
|
||||||
env:
|
env:
|
||||||
TOXENV: py
|
TOXENV: py
|
||||||
|
|
@ -37,10 +34,10 @@ jobs:
|
||||||
TOXENV: default-reactor
|
TOXENV: default-reactor
|
||||||
|
|
||||||
# pinned deps
|
# pinned deps
|
||||||
- python-version: "3.9.13"
|
- python-version: "3.10.11"
|
||||||
env:
|
env:
|
||||||
TOXENV: pinned
|
TOXENV: pinned
|
||||||
- python-version: "3.9.13"
|
- python-version: "3.10.11"
|
||||||
env:
|
env:
|
||||||
TOXENV: extra-deps-pinned
|
TOXENV: extra-deps-pinned
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
:alt: Ask DeepWiki
|
:alt: Ask DeepWiki
|
||||||
|
|
||||||
Scrapy_ is a web scraping framework to extract structured data from websites.
|
Scrapy_ is a web scraping framework to extract structured data from websites.
|
||||||
It is cross-platform, and requires Python 3.9+. It is maintained by Zyte_
|
It is cross-platform, and requires Python 3.10+. It is maintained by Zyte_
|
||||||
(formerly Scrapinghub) and `many other contributors`_.
|
(formerly Scrapinghub) and `many other contributors`_.
|
||||||
|
|
||||||
.. _many other contributors: https://github.com/scrapy/scrapy/graphs/contributors
|
.. _many other contributors: https://github.com/scrapy/scrapy/graphs/contributors
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Installation guide
|
||||||
Supported Python versions
|
Supported Python versions
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
Scrapy requires Python 3.9+, either the CPython implementation (default) or
|
Scrapy requires Python 3.10+, either the CPython implementation (default) or
|
||||||
the PyPy implementation (see :ref:`python:implementations`).
|
the PyPy implementation (see :ref:`python:implementations`).
|
||||||
|
|
||||||
.. _intro-install-scrapy:
|
.. _intro-install-scrapy:
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ The advantage of using the :class:`ImagesPipeline` for image files is that you
|
||||||
can configure some extra functions like generating thumbnails and filtering
|
can configure some extra functions like generating thumbnails and filtering
|
||||||
the images based on their size.
|
the images based on their size.
|
||||||
|
|
||||||
The Images Pipeline requires Pillow_ 8.0.0 or greater. It is used for
|
The Images Pipeline requires Pillow_ 8.3.2 or greater. It is used for
|
||||||
thumbnailing and normalizing images to JPEG/RGB format.
|
thumbnailing and normalizing images to JPEG/RGB format.
|
||||||
|
|
||||||
.. _Pillow: https://github.com/python-pillow/Pillow
|
.. _Pillow: https://github.com/python-pillow/Pillow
|
||||||
|
|
@ -238,7 +238,7 @@ Amazon S3 storage
|
||||||
.. setting:: FILES_STORE_S3_ACL
|
.. setting:: FILES_STORE_S3_ACL
|
||||||
.. setting:: IMAGES_STORE_S3_ACL
|
.. setting:: IMAGES_STORE_S3_ACL
|
||||||
|
|
||||||
If botocore_ >= 1.4.87 is installed, :setting:`FILES_STORE` and
|
If botocore_ >= 1.13.45 is installed, :setting:`FILES_STORE` and
|
||||||
:setting:`IMAGES_STORE` can represent an Amazon S3 bucket. Scrapy will
|
:setting:`IMAGES_STORE` can represent an Amazon S3 bucket. Scrapy will
|
||||||
automatically upload the files to the bucket.
|
automatically upload the files to the bucket.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ dependencies = [
|
||||||
"defusedxml>=0.7.1",
|
"defusedxml>=0.7.1",
|
||||||
"itemadapter>=0.1.0",
|
"itemadapter>=0.1.0",
|
||||||
"itemloaders>=1.0.1",
|
"itemloaders>=1.0.1",
|
||||||
"lxml>=4.6.0",
|
"lxml>=4.6.4",
|
||||||
"packaging",
|
"packaging",
|
||||||
"parsel>=1.5.0",
|
"parsel>=1.5.0",
|
||||||
"protego>=0.1.15",
|
"protego>=0.1.15",
|
||||||
|
|
@ -35,7 +35,6 @@ classifiers = [
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
|
@ -49,7 +48,7 @@ classifiers = [
|
||||||
license = "BSD-3-Clause"
|
license = "BSD-3-Clause"
|
||||||
license-files = ["LICENSE", "AUTHORS"]
|
license-files = ["LICENSE", "AUTHORS"]
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.10"
|
||||||
authors = [{ name = "Scrapy developers", email = "pablo@pablohoffman.com" }]
|
authors = [{ name = "Scrapy developers", email = "pablo@pablohoffman.com" }]
|
||||||
maintainers = [{ name = "Pablo Hoffman", email = "pablo@pablohoffman.com" }]
|
maintainers = [{ name = "Pablo Hoffman", email = "pablo@pablohoffman.com" }]
|
||||||
|
|
||||||
|
|
@ -375,6 +374,8 @@ ignore = [
|
||||||
"SIM115",
|
"SIM115",
|
||||||
# Yoda condition detected
|
# Yoda condition detected
|
||||||
"SIM300",
|
"SIM300",
|
||||||
|
# removed in recent ruff
|
||||||
|
"UP038",
|
||||||
|
|
||||||
# Ones that we may want to address (fix, ignore per-line or move to "don't want to fix")
|
# Ones that we may want to address (fix, ignore per-line or move to "don't want to fix")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from importlib.metadata import entry_points
|
from importlib.metadata import entry_points
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, ParamSpec
|
||||||
|
|
||||||
import scrapy
|
import scrapy
|
||||||
from scrapy.commands import BaseRunSpiderCommand, ScrapyCommand, ScrapyHelpFormatter
|
from scrapy.commands import BaseRunSpiderCommand, ScrapyCommand, ScrapyHelpFormatter
|
||||||
|
|
@ -20,12 +20,9 @@ from scrapy.utils.reactor import _asyncio_reactor_path
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
# typing.ParamSpec requires Python 3.10
|
|
||||||
from typing_extensions import ParamSpec
|
|
||||||
|
|
||||||
from scrapy.settings import BaseSettings, Settings
|
from scrapy.settings import BaseSettings, Settings
|
||||||
|
|
||||||
_P = ParamSpec("_P")
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
|
||||||
class ScrapyArgumentParser(argparse.ArgumentParser):
|
class ScrapyArgumentParser(argparse.ArgumentParser):
|
||||||
|
|
@ -67,11 +64,7 @@ def _get_commands_from_entry_points(
|
||||||
inproject: bool, group: str = "scrapy.commands"
|
inproject: bool, group: str = "scrapy.commands"
|
||||||
) -> dict[str, ScrapyCommand]:
|
) -> dict[str, ScrapyCommand]:
|
||||||
cmds: dict[str, ScrapyCommand] = {}
|
cmds: dict[str, ScrapyCommand] = {}
|
||||||
if sys.version_info >= (3, 10):
|
for entry_point in entry_points(group=group):
|
||||||
eps = entry_points(group=group)
|
|
||||||
else:
|
|
||||||
eps = entry_points().get(group, ())
|
|
||||||
for entry_point in eps:
|
|
||||||
obj = entry_point.load()
|
obj = entry_point.load()
|
||||||
if inspect.isclass(obj):
|
if inspect.isclass(obj):
|
||||||
cmds[entry_point.name] = obj()
|
cmds[entry_point.name] = obj()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from typing import Any, Callable
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from itemadapter import ItemAdapter, is_item
|
from itemadapter import ItemAdapter, is_item
|
||||||
|
|
||||||
|
|
@ -9,6 +9,9 @@ from scrapy.contracts import Contract
|
||||||
from scrapy.exceptions import ContractFail
|
from scrapy.exceptions import ContractFail
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
# contracts
|
# contracts
|
||||||
class UrlContract(Contract):
|
class UrlContract(Contract):
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import logging
|
||||||
import warnings
|
import warnings
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, Union
|
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred, inlineCallbacks
|
from twisted.internet.defer import Deferred, inlineCallbacks
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
@ -53,7 +53,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
QueueTuple = tuple[Union[Response, Failure], Request, Deferred[None]]
|
QueueTuple: TypeAlias = tuple[Response | Failure, Request, Deferred[None]]
|
||||||
|
|
||||||
|
|
||||||
class Slot:
|
class Slot:
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from collections.abc import AsyncIterator, Callable, Coroutine, Iterable
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from inspect import isasyncgenfunction, iscoroutine
|
from inspect import isasyncgenfunction, iscoroutine
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar, cast
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred, inlineCallbacks
|
from twisted.internet.defer import Deferred, inlineCallbacks
|
||||||
|
|
@ -41,9 +41,9 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
ScrapeFunc = Callable[
|
ScrapeFunc: TypeAlias = Callable[
|
||||||
[Union[Response, Failure], Request],
|
[Response | Failure, Request],
|
||||||
Coroutine[Any, Any, Union[Iterable[_T], AsyncIterator[_T]]],
|
Coroutine[Any, Any, Iterable[_T] | AsyncIterator[_T]],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -179,7 +179,7 @@ class SpiderMiddlewareManager(MiddlewareManager):
|
||||||
yield from iterable
|
yield from iterable
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
exception_result = cast(
|
exception_result = cast(
|
||||||
"Union[Failure, MutableChain[_T]]",
|
"Failure | MutableChain[_T]",
|
||||||
self._process_spider_exception(
|
self._process_spider_exception(
|
||||||
response, ex, exception_processor_index
|
response, ex, exception_processor_index
|
||||||
),
|
),
|
||||||
|
|
@ -195,7 +195,7 @@ class SpiderMiddlewareManager(MiddlewareManager):
|
||||||
yield r
|
yield r
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
exception_result = cast(
|
exception_result = cast(
|
||||||
"Union[Failure, MutableAsyncChain[_T]]",
|
"Failure | MutableAsyncChain[_T]",
|
||||||
self._process_spider_exception(
|
self._process_spider_exception(
|
||||||
response, ex, exception_processor_index
|
response, ex, exception_processor_index
|
||||||
),
|
),
|
||||||
|
|
@ -241,9 +241,7 @@ class SpiderMiddlewareManager(MiddlewareManager):
|
||||||
# simplified when downgrading is removed.
|
# simplified when downgrading is removed.
|
||||||
if dfd.called:
|
if dfd.called:
|
||||||
# the result is available immediately if _process_spider_output didn't do downgrading
|
# the result is available immediately if _process_spider_output didn't do downgrading
|
||||||
return cast(
|
return cast("MutableChain[_T] | MutableAsyncChain[_T]", dfd.result)
|
||||||
"Union[MutableChain[_T], MutableAsyncChain[_T]]", dfd.result
|
|
||||||
)
|
|
||||||
# we forbid waiting here because otherwise we would need to return a deferred from
|
# we forbid waiting here because otherwise we would need to return a deferred from
|
||||||
# _process_spider_exception too, which complicates the architecture
|
# _process_spider_exception too, which complicates the architecture
|
||||||
msg = f"Async iterable returned from {global_object_name(method)} cannot be downgraded"
|
msg = f"Async iterable returned from {global_object_name(method)} cannot be downgraded"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from collections.abc import Callable
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path, PureWindowsPath
|
from pathlib import Path, PureWindowsPath
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import IO, TYPE_CHECKING, Any, Optional, Protocol, TypeVar, cast
|
from typing import IO, TYPE_CHECKING, Any, Protocol, TypeAlias, TypeVar, cast
|
||||||
from urllib.parse import unquote, urlparse
|
from urllib.parse import unquote, urlparse
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred, DeferredList, maybeDeferred
|
from twisted.internet.defer import Deferred, DeferredList, maybeDeferred
|
||||||
|
|
@ -50,7 +50,9 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
UriParamsCallableT = Callable[[dict[str, Any], Spider], Optional[dict[str, Any]]]
|
UriParamsCallableT: TypeAlias = Callable[
|
||||||
|
[dict[str, Any], Spider], dict[str, Any] | None
|
||||||
|
]
|
||||||
|
|
||||||
_StorageT = TypeVar("_StorageT", bound="FeedStorageProtocol")
|
_StorageT = TypeVar("_StorageT", bound="FeedStorageProtocol")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from email.utils import mktime_tz, parsedate_tz
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import time
|
from time import time
|
||||||
from typing import IO, TYPE_CHECKING, Any, cast
|
from typing import IO, TYPE_CHECKING, Any, Concatenate, cast
|
||||||
from weakref import WeakKeyDictionary
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
from w3lib.http import headers_dict_to_raw, headers_raw_to_dict
|
from w3lib.http import headers_dict_to_raw, headers_raw_to_dict
|
||||||
|
|
@ -23,9 +23,6 @@ if TYPE_CHECKING:
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
|
||||||
# typing.Concatenate requires Python 3.10
|
|
||||||
from typing_extensions import Concatenate
|
|
||||||
|
|
||||||
from scrapy.http.request import Request
|
from scrapy.http.request import Request
|
||||||
from scrapy.settings import BaseSettings
|
from scrapy.settings import BaseSettings
|
||||||
from scrapy.spiders import Spider
|
from scrapy.spiders import Spider
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import TYPE_CHECKING, Any, AnyStr, Union, cast
|
from typing import TYPE_CHECKING, Any, AnyStr, TypeAlias, cast
|
||||||
|
|
||||||
from w3lib.http import headers_dict_to_raw
|
from w3lib.http import headers_dict_to_raw
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
|
||||||
_RawValueT = Union[bytes, str, int]
|
_RawValue: TypeAlias = bytes | str | int
|
||||||
|
|
||||||
|
|
||||||
# isn't fully compatible typing-wise with either dict or CaselessDict,
|
# isn't fully compatible typing-wise with either dict or CaselessDict,
|
||||||
|
|
@ -44,9 +44,9 @@ class Headers(CaselessDict):
|
||||||
"""Normalize key to bytes"""
|
"""Normalize key to bytes"""
|
||||||
return self._tobytes(key.title())
|
return self._tobytes(key.title())
|
||||||
|
|
||||||
def normvalue(self, value: _RawValueT | Iterable[_RawValueT]) -> list[bytes]:
|
def normvalue(self, value: _RawValue | Iterable[_RawValue]) -> list[bytes]:
|
||||||
"""Normalize values to bytes"""
|
"""Normalize values to bytes"""
|
||||||
_value: Iterable[_RawValueT]
|
_value: Iterable[_RawValue]
|
||||||
if value is None:
|
if value is None:
|
||||||
_value = []
|
_value = []
|
||||||
elif isinstance(value, (str, bytes)):
|
elif isinstance(value, (str, bytes)):
|
||||||
|
|
@ -58,7 +58,7 @@ class Headers(CaselessDict):
|
||||||
|
|
||||||
return [self._tobytes(x) for x in _value]
|
return [self._tobytes(x) for x in _value]
|
||||||
|
|
||||||
def _tobytes(self, x: _RawValueT) -> bytes:
|
def _tobytes(self, x: _RawValue) -> bytes:
|
||||||
if isinstance(x, bytes):
|
if isinstance(x, bytes):
|
||||||
return x
|
return x
|
||||||
if isinstance(x, str):
|
if isinstance(x, str):
|
||||||
|
|
@ -87,15 +87,15 @@ class Headers(CaselessDict):
|
||||||
return self.normvalue(def_val)
|
return self.normvalue(def_val)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def setlist(self, key: AnyStr, list_: Iterable[_RawValueT]) -> None:
|
def setlist(self, key: AnyStr, list_: Iterable[_RawValue]) -> None:
|
||||||
self[key] = list_
|
self[key] = list_
|
||||||
|
|
||||||
def setlistdefault(
|
def setlistdefault(
|
||||||
self, key: AnyStr, default_list: Iterable[_RawValueT] = ()
|
self, key: AnyStr, default_list: Iterable[_RawValue] = ()
|
||||||
) -> Any:
|
) -> Any:
|
||||||
return self.setdefault(key, default_list)
|
return self.setdefault(key, default_list)
|
||||||
|
|
||||||
def appendlist(self, key: AnyStr, value: Iterable[_RawValueT]) -> None:
|
def appendlist(self, key: AnyStr, value: Iterable[_RawValue]) -> None:
|
||||||
lst = self.getlist(key)
|
lst = self.getlist(key)
|
||||||
lst.extend(self.normvalue(value))
|
lst.extend(self.normvalue(value))
|
||||||
self[key] = lst
|
self[key] = lst
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,11 @@ from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
Any,
|
||||||
AnyStr,
|
AnyStr,
|
||||||
|
Concatenate,
|
||||||
NoReturn,
|
NoReturn,
|
||||||
|
TypeAlias,
|
||||||
TypedDict,
|
TypedDict,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -33,13 +34,13 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
# typing.Concatenate requires Python 3.10
|
|
||||||
# typing.NotRequired and typing.Self require Python 3.11
|
# typing.NotRequired and typing.Self require Python 3.11
|
||||||
from typing_extensions import Concatenate, NotRequired, Self
|
from typing_extensions import NotRequired, Self
|
||||||
|
|
||||||
|
# circular import
|
||||||
from scrapy.http import Response
|
from scrapy.http import Response
|
||||||
|
|
||||||
CallbackT = Callable[Concatenate[Response, ...], Any]
|
CallbackT: TypeAlias = Callable[Concatenate[Response, ...], Any]
|
||||||
|
|
||||||
|
|
||||||
class VerboseCookie(TypedDict):
|
class VerboseCookie(TypedDict):
|
||||||
|
|
@ -50,7 +51,7 @@ class VerboseCookie(TypedDict):
|
||||||
secure: NotRequired[bool]
|
secure: NotRequired[bool]
|
||||||
|
|
||||||
|
|
||||||
CookiesT = Union[dict[str, str], list[VerboseCookie]]
|
CookiesT: TypeAlias = dict[str, str] | list[VerboseCookie]
|
||||||
|
|
||||||
|
|
||||||
RequestTypeVar = TypeVar("RequestTypeVar", bound="Request")
|
RequestTypeVar = TypeVar("RequestTypeVar", bound="Request")
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ See documentation in docs/topics/request-response.rst
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
from typing import TYPE_CHECKING, Any, TypeAlias, cast
|
||||||
from urllib.parse import urlencode, urljoin, urlsplit, urlunsplit
|
from urllib.parse import urlencode, urljoin, urlsplit, urlunsplit
|
||||||
|
|
||||||
from parsel.csstranslator import HTMLTranslator
|
from parsel.csstranslator import HTMLTranslator
|
||||||
|
|
@ -31,9 +31,9 @@ if TYPE_CHECKING:
|
||||||
from scrapy.http.response.text import TextResponse
|
from scrapy.http.response.text import TextResponse
|
||||||
|
|
||||||
|
|
||||||
FormdataVType = Union[str, Iterable[str]]
|
FormdataVType: TypeAlias = str | Iterable[str]
|
||||||
FormdataKVType = tuple[str, FormdataVType]
|
FormdataKVType: TypeAlias = tuple[str, FormdataVType]
|
||||||
FormdataType = Optional[Union[dict[str, FormdataVType], list[FormdataKVType]]]
|
FormdataType: TypeAlias = dict[str, FormdataVType] | list[FormdataKVType] | None
|
||||||
|
|
||||||
|
|
||||||
class FormRequest(Request):
|
class FormRequest(Request):
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import operator
|
||||||
import re
|
import re
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING, Any, Union, cast
|
from typing import TYPE_CHECKING, Any, TypeAlias, cast
|
||||||
from urllib.parse import urljoin, urlparse
|
from urllib.parse import urljoin, urlparse
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
@ -157,8 +157,8 @@ class LxmlParserLinkExtractor:
|
||||||
return links
|
return links
|
||||||
|
|
||||||
|
|
||||||
_RegexT = Union[str, re.Pattern[str]]
|
_Regex: TypeAlias = str | re.Pattern[str]
|
||||||
_RegexOrSeveralT = Union[_RegexT, Iterable[_RegexT]]
|
_RegexOrSeveral: TypeAlias = _Regex | Iterable[_Regex]
|
||||||
|
|
||||||
|
|
||||||
class LxmlLinkExtractor:
|
class LxmlLinkExtractor:
|
||||||
|
|
@ -166,8 +166,8 @@ class LxmlLinkExtractor:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
allow: _RegexOrSeveralT = (),
|
allow: _RegexOrSeveral = (),
|
||||||
deny: _RegexOrSeveralT = (),
|
deny: _RegexOrSeveral = (),
|
||||||
allow_domains: str | Iterable[str] = (),
|
allow_domains: str | Iterable[str] = (),
|
||||||
deny_domains: str | Iterable[str] = (),
|
deny_domains: str | Iterable[str] = (),
|
||||||
restrict_xpaths: str | Iterable[str] = (),
|
restrict_xpaths: str | Iterable[str] = (),
|
||||||
|
|
@ -179,7 +179,7 @@ class LxmlLinkExtractor:
|
||||||
deny_extensions: str | Iterable[str] | None = None,
|
deny_extensions: str | Iterable[str] | None = None,
|
||||||
restrict_css: str | Iterable[str] = (),
|
restrict_css: str | Iterable[str] = (),
|
||||||
strip: bool = True,
|
strip: bool = True,
|
||||||
restrict_text: _RegexOrSeveralT | None = None,
|
restrict_text: _RegexOrSeveral | None = None,
|
||||||
):
|
):
|
||||||
tags, attrs = set(arg_to_iter(tags)), set(arg_to_iter(attrs))
|
tags, attrs = set(arg_to_iter(tags)), set(arg_to_iter(attrs))
|
||||||
self.link_extractor = LxmlParserLinkExtractor(
|
self.link_extractor = LxmlParserLinkExtractor(
|
||||||
|
|
@ -208,7 +208,7 @@ class LxmlLinkExtractor:
|
||||||
self.restrict_text: list[re.Pattern[str]] = self._compile_regexes(restrict_text)
|
self.restrict_text: list[re.Pattern[str]] = self._compile_regexes(restrict_text)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _compile_regexes(value: _RegexOrSeveralT | None) -> list[re.Pattern[str]]:
|
def _compile_regexes(value: _RegexOrSeveral | None) -> list[re.Pattern[str]]:
|
||||||
return [
|
return [
|
||||||
x if isinstance(x, re.Pattern) else re.compile(x)
|
x if isinstance(x, re.Pattern) else re.compile(x)
|
||||||
for x in arg_to_iter(value)
|
for x in arg_to_iter(value)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import pprint
|
||||||
import warnings
|
import warnings
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar, cast
|
||||||
|
|
||||||
from scrapy.exceptions import NotConfigured, ScrapyDeprecationWarning
|
from scrapy.exceptions import NotConfigured, ScrapyDeprecationWarning
|
||||||
from scrapy.utils.defer import ensure_awaitable
|
from scrapy.utils.defer import ensure_awaitable
|
||||||
|
|
@ -18,20 +18,18 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
|
|
||||||
# typing.Concatenate and typing.ParamSpec require Python 3.10
|
|
||||||
# typing.Self requires Python 3.11
|
# typing.Self requires Python 3.11
|
||||||
from typing_extensions import Concatenate, ParamSpec, Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from scrapy import Spider
|
from scrapy import Spider
|
||||||
from scrapy.crawler import Crawler
|
from scrapy.crawler import Crawler
|
||||||
from scrapy.settings import BaseSettings, Settings
|
from scrapy.settings import BaseSettings, Settings
|
||||||
|
|
||||||
_P = ParamSpec("_P")
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
|
||||||
class MiddlewareManager(ABC):
|
class MiddlewareManager(ABC):
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ See documentation in docs/item-pipeline.rst
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from typing import TYPE_CHECKING, Any, Callable, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred, DeferredList
|
from twisted.internet.defer import Deferred, DeferredList
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ from scrapy.utils.defer import (
|
||||||
from scrapy.utils.python import global_object_name
|
from scrapy.utils.python import global_object_name
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterable
|
from collections.abc import Callable, Iterable
|
||||||
|
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ class ImagesPipeline(FilesPipeline):
|
||||||
self._ImageOps = ImageOps
|
self._ImageOps = ImageOps
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise NotConfigured(
|
raise NotConfigured(
|
||||||
"ImagesPipeline requires installing Pillow 8.0.0 or later"
|
"ImagesPipeline requires installing Pillow 8.3.2 or later"
|
||||||
)
|
)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import logging
|
||||||
import warnings
|
import warnings
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import TYPE_CHECKING, Any, Literal, TypedDict, Union, cast
|
from typing import TYPE_CHECKING, Any, Literal, TypeAlias, TypedDict, cast
|
||||||
|
|
||||||
from twisted import version as twisted_version
|
from twisted import version as twisted_version
|
||||||
from twisted.internet.defer import (
|
from twisted.internet.defer import (
|
||||||
|
|
@ -47,7 +47,9 @@ class FileInfo(TypedDict):
|
||||||
status: str
|
status: str
|
||||||
|
|
||||||
|
|
||||||
FileInfoOrError = Union[tuple[Literal[True], FileInfo], tuple[Literal[False], Failure]]
|
FileInfoOrError: TypeAlias = (
|
||||||
|
tuple[Literal[True], FileInfo] | tuple[Literal[False], Failure]
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import warnings
|
||||||
from collections.abc import Iterable, Iterator, Mapping, MutableMapping
|
from collections.abc import Iterable, Iterator, Mapping, MutableMapping
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import TYPE_CHECKING, Any, Union, cast
|
from typing import TYPE_CHECKING, Any, TypeAlias, cast
|
||||||
|
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.settings import default_settings
|
from scrapy.settings import default_settings
|
||||||
|
|
@ -14,7 +14,7 @@ from scrapy.utils.misc import load_object
|
||||||
|
|
||||||
# The key types are restricted in BaseSettings._get_key() to ones supported by JSON,
|
# The key types are restricted in BaseSettings._get_key() to ones supported by JSON,
|
||||||
# see https://github.com/scrapy/scrapy/issues/5383.
|
# see https://github.com/scrapy/scrapy/issues/5383.
|
||||||
_SettingsKeyT = Union[bool, float, int, str, None]
|
_SettingsKey: TypeAlias = bool | float | int | str | None
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||||
# typing.Self requires Python 3.11
|
# typing.Self requires Python 3.11
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
_SettingsInputT = Union[SupportsItems[_SettingsKeyT, Any], str, None]
|
_SettingsInput: TypeAlias = SupportsItems[_SettingsKey, Any] | str | None
|
||||||
|
|
||||||
|
|
||||||
SETTINGS_PRIORITIES: dict[str, int] = {
|
SETTINGS_PRIORITIES: dict[str, int] = {
|
||||||
|
|
@ -76,7 +76,7 @@ class SettingsAttribute:
|
||||||
return f"<SettingsAttribute value={self.value!r} priority={self.priority}>"
|
return f"<SettingsAttribute value={self.value!r} priority={self.priority}>"
|
||||||
|
|
||||||
|
|
||||||
class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
class BaseSettings(MutableMapping[_SettingsKey, Any]):
|
||||||
"""
|
"""
|
||||||
Instances of this class behave like dictionaries, but store priorities
|
Instances of this class behave like dictionaries, but store priorities
|
||||||
along with their ``(key, value)`` pairs, and can be frozen (i.e. marked
|
along with their ``(key, value)`` pairs, and can be frozen (i.e. marked
|
||||||
|
|
@ -100,13 +100,13 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
|
|
||||||
__default = object()
|
__default = object()
|
||||||
|
|
||||||
def __init__(self, values: _SettingsInputT = None, priority: int | str = "project"):
|
def __init__(self, values: _SettingsInput = None, priority: int | str = "project"):
|
||||||
self.frozen: bool = False
|
self.frozen: bool = False
|
||||||
self.attributes: dict[_SettingsKeyT, SettingsAttribute] = {}
|
self.attributes: dict[_SettingsKey, SettingsAttribute] = {}
|
||||||
if values:
|
if values:
|
||||||
self.update(values, priority)
|
self.update(values, priority)
|
||||||
|
|
||||||
def __getitem__(self, opt_name: _SettingsKeyT) -> Any:
|
def __getitem__(self, opt_name: _SettingsKey) -> Any:
|
||||||
if opt_name not in self:
|
if opt_name not in self:
|
||||||
return None
|
return None
|
||||||
return self.attributes[opt_name].value
|
return self.attributes[opt_name].value
|
||||||
|
|
@ -114,7 +114,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
def __contains__(self, name: Any) -> bool:
|
def __contains__(self, name: Any) -> bool:
|
||||||
return name in self.attributes
|
return name in self.attributes
|
||||||
|
|
||||||
def add_to_list(self, name: _SettingsKeyT, item: Any) -> None:
|
def add_to_list(self, name: _SettingsKey, item: Any) -> None:
|
||||||
"""Append *item* to the :class:`list` setting with the specified *name*
|
"""Append *item* to the :class:`list` setting with the specified *name*
|
||||||
if *item* is not already in that list.
|
if *item* is not already in that list.
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
if item not in value:
|
if item not in value:
|
||||||
self.set(name, [*value, item], self.getpriority(name) or 0)
|
self.set(name, [*value, item], self.getpriority(name) or 0)
|
||||||
|
|
||||||
def remove_from_list(self, name: _SettingsKeyT, item: Any) -> None:
|
def remove_from_list(self, name: _SettingsKey, item: Any) -> None:
|
||||||
"""Remove *item* from the :class:`list` setting with the specified
|
"""Remove *item* from the :class:`list` setting with the specified
|
||||||
*name*.
|
*name*.
|
||||||
|
|
||||||
|
|
@ -139,7 +139,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
raise ValueError(f"{item!r} not found in the {name} setting ({value!r}).")
|
raise ValueError(f"{item!r} not found in the {name} setting ({value!r}).")
|
||||||
self.set(name, [v for v in value if v != item], self.getpriority(name) or 0)
|
self.set(name, [v for v in value if v != item], self.getpriority(name) or 0)
|
||||||
|
|
||||||
def get(self, name: _SettingsKeyT, default: Any = None) -> Any:
|
def get(self, name: _SettingsKey, default: Any = None) -> Any:
|
||||||
"""
|
"""
|
||||||
Get a setting value without affecting its original type.
|
Get a setting value without affecting its original type.
|
||||||
|
|
||||||
|
|
@ -160,7 +160,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
|
|
||||||
return self[name] if self[name] is not None else default
|
return self[name] if self[name] is not None else default
|
||||||
|
|
||||||
def getbool(self, name: _SettingsKeyT, default: bool = False) -> bool:
|
def getbool(self, name: _SettingsKey, default: bool = False) -> bool:
|
||||||
"""
|
"""
|
||||||
Get a setting value as a boolean.
|
Get a setting value as a boolean.
|
||||||
|
|
||||||
|
|
@ -190,7 +190,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
"'True'/'False' and 'true'/'false'"
|
"'True'/'False' and 'true'/'false'"
|
||||||
)
|
)
|
||||||
|
|
||||||
def getint(self, name: _SettingsKeyT, default: int = 0) -> int:
|
def getint(self, name: _SettingsKey, default: int = 0) -> int:
|
||||||
"""
|
"""
|
||||||
Get a setting value as an int.
|
Get a setting value as an int.
|
||||||
|
|
||||||
|
|
@ -202,7 +202,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
"""
|
"""
|
||||||
return int(self.get(name, default))
|
return int(self.get(name, default))
|
||||||
|
|
||||||
def getfloat(self, name: _SettingsKeyT, default: float = 0.0) -> float:
|
def getfloat(self, name: _SettingsKey, default: float = 0.0) -> float:
|
||||||
"""
|
"""
|
||||||
Get a setting value as a float.
|
Get a setting value as a float.
|
||||||
|
|
||||||
|
|
@ -215,7 +215,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
return float(self.get(name, default))
|
return float(self.get(name, default))
|
||||||
|
|
||||||
def getlist(
|
def getlist(
|
||||||
self, name: _SettingsKeyT, default: list[Any] | None = None
|
self, name: _SettingsKey, default: list[Any] | None = None
|
||||||
) -> list[Any]:
|
) -> list[Any]:
|
||||||
"""
|
"""
|
||||||
Get a setting value as a list. If the setting original type is a list,
|
Get a setting value as a list. If the setting original type is a list,
|
||||||
|
|
@ -239,7 +239,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
return list(value)
|
return list(value)
|
||||||
|
|
||||||
def getdict(
|
def getdict(
|
||||||
self, name: _SettingsKeyT, default: dict[Any, Any] | None = None
|
self, name: _SettingsKey, default: dict[Any, Any] | None = None
|
||||||
) -> dict[Any, Any]:
|
) -> dict[Any, Any]:
|
||||||
"""
|
"""
|
||||||
Get a setting value as a dictionary. If the setting original type is a
|
Get a setting value as a dictionary. If the setting original type is a
|
||||||
|
|
@ -263,7 +263,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
|
|
||||||
def getdictorlist(
|
def getdictorlist(
|
||||||
self,
|
self,
|
||||||
name: _SettingsKeyT,
|
name: _SettingsKey,
|
||||||
default: dict[Any, Any] | list[Any] | tuple[Any] | None = None,
|
default: dict[Any, Any] | list[Any] | tuple[Any] | None = None,
|
||||||
) -> dict[Any, Any] | list[Any]:
|
) -> dict[Any, Any] | list[Any]:
|
||||||
"""Get a setting value as either a :class:`dict` or a :class:`list`.
|
"""Get a setting value as either a :class:`dict` or a :class:`list`.
|
||||||
|
|
@ -310,7 +310,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
)
|
)
|
||||||
return copy.deepcopy(value)
|
return copy.deepcopy(value)
|
||||||
|
|
||||||
def getwithbase(self, name: _SettingsKeyT) -> BaseSettings:
|
def getwithbase(self, name: _SettingsKey) -> BaseSettings:
|
||||||
"""Get a composition of a dictionary-like setting and its `_BASE`
|
"""Get a composition of a dictionary-like setting and its `_BASE`
|
||||||
counterpart.
|
counterpart.
|
||||||
|
|
||||||
|
|
@ -324,7 +324,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
compbs.update(self[name])
|
compbs.update(self[name])
|
||||||
return compbs
|
return compbs
|
||||||
|
|
||||||
def getpriority(self, name: _SettingsKeyT) -> int | None:
|
def getpriority(self, name: _SettingsKey) -> int | None:
|
||||||
"""
|
"""
|
||||||
Return the current numerical priority value of a setting, or ``None`` if
|
Return the current numerical priority value of a setting, or ``None`` if
|
||||||
the given ``name`` does not exist.
|
the given ``name`` does not exist.
|
||||||
|
|
@ -349,7 +349,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
|
|
||||||
def replace_in_component_priority_dict(
|
def replace_in_component_priority_dict(
|
||||||
self,
|
self,
|
||||||
name: _SettingsKeyT,
|
name: _SettingsKey,
|
||||||
old_cls: type,
|
old_cls: type,
|
||||||
new_cls: type,
|
new_cls: type,
|
||||||
priority: int | None = None,
|
priority: int | None = None,
|
||||||
|
|
@ -388,11 +388,11 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
)
|
)
|
||||||
self.set(name, component_priority_dict, priority=self.getpriority(name) or 0)
|
self.set(name, component_priority_dict, priority=self.getpriority(name) or 0)
|
||||||
|
|
||||||
def __setitem__(self, name: _SettingsKeyT, value: Any) -> None:
|
def __setitem__(self, name: _SettingsKey, value: Any) -> None:
|
||||||
self.set(name, value)
|
self.set(name, value)
|
||||||
|
|
||||||
def set(
|
def set(
|
||||||
self, name: _SettingsKeyT, value: Any, priority: int | str = "project"
|
self, name: _SettingsKey, value: Any, priority: int | str = "project"
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Store a key/value attribute with a given priority.
|
Store a key/value attribute with a given priority.
|
||||||
|
|
@ -422,7 +422,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
self.attributes[name].set(value, priority)
|
self.attributes[name].set(value, priority)
|
||||||
|
|
||||||
def set_in_component_priority_dict(
|
def set_in_component_priority_dict(
|
||||||
self, name: _SettingsKeyT, cls: type, priority: int | None
|
self, name: _SettingsKey, cls: type, priority: int | None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set the *cls* component in the *name* :ref:`component priority
|
"""Set the *cls* component in the *name* :ref:`component priority
|
||||||
dictionary <component-priority-dictionaries>` setting with *priority*.
|
dictionary <component-priority-dictionaries>` setting with *priority*.
|
||||||
|
|
@ -447,7 +447,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
|
|
||||||
def setdefault(
|
def setdefault(
|
||||||
self,
|
self,
|
||||||
name: _SettingsKeyT,
|
name: _SettingsKey,
|
||||||
default: Any = None,
|
default: Any = None,
|
||||||
priority: int | str = "project",
|
priority: int | str = "project",
|
||||||
) -> Any:
|
) -> Any:
|
||||||
|
|
@ -458,7 +458,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
return self.attributes[name].value
|
return self.attributes[name].value
|
||||||
|
|
||||||
def setdefault_in_component_priority_dict(
|
def setdefault_in_component_priority_dict(
|
||||||
self, name: _SettingsKeyT, cls: type, priority: int | None
|
self, name: _SettingsKey, cls: type, priority: int | None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set the *cls* component in the *name* :ref:`component priority
|
"""Set the *cls* component in the *name* :ref:`component priority
|
||||||
dictionary <component-priority-dictionaries>` setting with *priority*
|
dictionary <component-priority-dictionaries>` setting with *priority*
|
||||||
|
|
@ -475,7 +475,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
component_priority_dict[cls] = priority
|
component_priority_dict[cls] = priority
|
||||||
self.set(name, component_priority_dict, self.getpriority(name) or 0)
|
self.set(name, component_priority_dict, self.getpriority(name) or 0)
|
||||||
|
|
||||||
def setdict(self, values: _SettingsInputT, priority: int | str = "project") -> None:
|
def setdict(self, values: _SettingsInput, priority: int | str = "project") -> None:
|
||||||
self.update(values, priority)
|
self.update(values, priority)
|
||||||
|
|
||||||
def setmodule(
|
def setmodule(
|
||||||
|
|
@ -503,7 +503,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
self.set(key, getattr(module, key), priority)
|
self.set(key, getattr(module, key), priority)
|
||||||
|
|
||||||
# BaseSettings.update() doesn't support all inputs that MutableMapping.update() supports
|
# BaseSettings.update() doesn't support all inputs that MutableMapping.update() supports
|
||||||
def update(self, values: _SettingsInputT, priority: int | str = "project") -> None: # type: ignore[override]
|
def update(self, values: _SettingsInput, priority: int | str = "project") -> None: # type: ignore[override]
|
||||||
"""
|
"""
|
||||||
Store key/value pairs with a given priority.
|
Store key/value pairs with a given priority.
|
||||||
|
|
||||||
|
|
@ -527,7 +527,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
"""
|
"""
|
||||||
self._assert_mutability()
|
self._assert_mutability()
|
||||||
if isinstance(values, str):
|
if isinstance(values, str):
|
||||||
values = cast("dict[_SettingsKeyT, Any]", json.loads(values))
|
values = cast("dict[_SettingsKey, Any]", json.loads(values))
|
||||||
if values is not None:
|
if values is not None:
|
||||||
if isinstance(values, BaseSettings):
|
if isinstance(values, BaseSettings):
|
||||||
for name, value in values.items():
|
for name, value in values.items():
|
||||||
|
|
@ -536,7 +536,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
for name, value in values.items():
|
for name, value in values.items():
|
||||||
self.set(name, value, priority)
|
self.set(name, value, priority)
|
||||||
|
|
||||||
def delete(self, name: _SettingsKeyT, priority: int | str = "project") -> None:
|
def delete(self, name: _SettingsKey, priority: int | str = "project") -> None:
|
||||||
if name not in self:
|
if name not in self:
|
||||||
raise KeyError(name)
|
raise KeyError(name)
|
||||||
self._assert_mutability()
|
self._assert_mutability()
|
||||||
|
|
@ -544,7 +544,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
if priority >= cast("int", self.getpriority(name)):
|
if priority >= cast("int", self.getpriority(name)):
|
||||||
del self.attributes[name]
|
del self.attributes[name]
|
||||||
|
|
||||||
def __delitem__(self, name: _SettingsKeyT) -> None:
|
def __delitem__(self, name: _SettingsKey) -> None:
|
||||||
self._assert_mutability()
|
self._assert_mutability()
|
||||||
del self.attributes[name]
|
del self.attributes[name]
|
||||||
|
|
||||||
|
|
@ -584,26 +584,26 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
copy.freeze()
|
copy.freeze()
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[_SettingsKeyT]:
|
def __iter__(self) -> Iterator[_SettingsKey]:
|
||||||
return iter(self.attributes)
|
return iter(self.attributes)
|
||||||
|
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self.attributes)
|
return len(self.attributes)
|
||||||
|
|
||||||
def _to_dict(self) -> dict[_SettingsKeyT, Any]:
|
def _to_dict(self) -> dict[_SettingsKey, Any]:
|
||||||
return {
|
return {
|
||||||
self._get_key(k): (v._to_dict() if isinstance(v, BaseSettings) else v)
|
self._get_key(k): (v._to_dict() if isinstance(v, BaseSettings) else v)
|
||||||
for k, v in self.items()
|
for k, v in self.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_key(self, key_value: Any) -> _SettingsKeyT:
|
def _get_key(self, key_value: Any) -> _SettingsKey:
|
||||||
return (
|
return (
|
||||||
key_value
|
key_value
|
||||||
if isinstance(key_value, (bool, float, int, str, type(None)))
|
if isinstance(key_value, (bool, float, int, str, type(None)))
|
||||||
else str(key_value)
|
else str(key_value)
|
||||||
)
|
)
|
||||||
|
|
||||||
def copy_to_dict(self) -> dict[_SettingsKeyT, Any]:
|
def copy_to_dict(self) -> dict[_SettingsKey, Any]:
|
||||||
"""
|
"""
|
||||||
Make a copy of current settings and convert to a dict.
|
Make a copy of current settings and convert to a dict.
|
||||||
|
|
||||||
|
|
@ -626,7 +626,7 @@ class BaseSettings(MutableMapping[_SettingsKeyT, Any]):
|
||||||
else:
|
else:
|
||||||
p.text(pformat(self.copy_to_dict()))
|
p.text(pformat(self.copy_to_dict()))
|
||||||
|
|
||||||
def pop(self, name: _SettingsKeyT, default: Any = __default) -> Any:
|
def pop(self, name: _SettingsKey, default: Any = __default) -> Any:
|
||||||
try:
|
try:
|
||||||
value = self.attributes[name].value
|
value = self.attributes[name].value
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -648,7 +648,7 @@ class Settings(BaseSettings):
|
||||||
described on :ref:`topics-settings-ref` already populated.
|
described on :ref:`topics-settings-ref` already populated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, values: _SettingsInputT = None, priority: int | str = "project"):
|
def __init__(self, values: _SettingsInput = None, priority: int | str = "project"):
|
||||||
# Do not pass kwarg values here. We don't want to promote user-defined
|
# Do not pass kwarg values here. We don't want to promote user-defined
|
||||||
# dicts, and we want to update, not replace, default dicts with the
|
# dicts, and we want to update, not replace, default dicts with the
|
||||||
# values given by the user
|
# values given by the user
|
||||||
|
|
@ -670,7 +670,7 @@ def iter_default_settings() -> Iterable[tuple[str, Any]]:
|
||||||
|
|
||||||
|
|
||||||
def overridden_settings(
|
def overridden_settings(
|
||||||
settings: Mapping[_SettingsKeyT, Any],
|
settings: Mapping[_SettingsKey, Any],
|
||||||
) -> Iterable[tuple[str, Any]]:
|
) -> Iterable[tuple[str, Any]]:
|
||||||
"""Return an iterable of the settings that have been overridden"""
|
"""Return an iterable of the settings that have been overridden"""
|
||||||
for name, defvalue in iter_default_settings():
|
for name, defvalue in iter_default_settings():
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
from scrapy.crawler import Crawler
|
from scrapy.crawler import Crawler
|
||||||
from scrapy.http.request import CallbackT
|
from scrapy.http.request import CallbackT
|
||||||
from scrapy.settings import BaseSettings, _SettingsKeyT
|
from scrapy.settings import BaseSettings, _SettingsKey
|
||||||
from scrapy.utils.log import SpiderLoggerAdapter
|
from scrapy.utils.log import SpiderLoggerAdapter
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ class Spider(object_ref):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
custom_settings: dict[_SettingsKeyT, Any] | None = None
|
custom_settings: dict[_SettingsKey, Any] | None = None
|
||||||
|
|
||||||
#: Start URLs. See :meth:`start`.
|
#: Start URLs. See :meth:`start`.
|
||||||
start_urls: list[str]
|
start_urls: list[str]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from __future__ import annotations
|
||||||
import copy
|
import copy
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import AsyncIterator, Awaitable, Callable
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
||||||
from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast
|
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar, cast
|
||||||
|
|
||||||
from scrapy.http import HtmlResponse, Request, Response
|
from scrapy.http import HtmlResponse, Request, Response
|
||||||
from scrapy.link import Link
|
from scrapy.link import Link
|
||||||
|
|
@ -34,8 +34,8 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
ProcessLinksT = Callable[[list[Link]], list[Link]]
|
ProcessLinksT: TypeAlias = Callable[[list[Link]], list[Link]]
|
||||||
ProcessRequestT = Callable[[Request, Response], Optional[Request]]
|
ProcessRequestT: TypeAlias = Callable[[Request, Response], Request | None]
|
||||||
|
|
||||||
|
|
||||||
def _identity(x: _T) -> _T:
|
def _identity(x: _T) -> _T:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from collections.abc import AsyncIterator, Callable, Coroutine, Iterable
|
from collections.abc import AsyncIterator, Callable, Coroutine, Iterable
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar
|
from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
|
|
@ -17,15 +17,14 @@ from scrapy.utils.reactor import is_asyncio_reactor_installed, is_reactor_instal
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from twisted.internet.base import DelayedCall
|
from twisted.internet.base import DelayedCall
|
||||||
|
|
||||||
# typing.Concatenate and typing.ParamSpec require Python 3.10
|
|
||||||
# typing.Self, typing.TypeVarTuple and typing.Unpack require Python 3.11
|
# typing.Self, typing.TypeVarTuple and typing.Unpack require Python 3.11
|
||||||
from typing_extensions import Concatenate, ParamSpec, Self, TypeVarTuple, Unpack
|
from typing_extensions import Self, TypeVarTuple, Unpack
|
||||||
|
|
||||||
_P = ParamSpec("_P")
|
|
||||||
_Ts = TypeVarTuple("_Ts")
|
_Ts = TypeVarTuple("_Ts")
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import sys
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Callable, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from scrapy.exceptions import UsageError
|
from scrapy.exceptions import UsageError
|
||||||
from scrapy.settings import BaseSettings
|
from scrapy.settings import BaseSettings
|
||||||
|
|
@ -14,7 +14,7 @@ from scrapy.utils.deprecate import update_classpath
|
||||||
from scrapy.utils.python import without_none_values
|
from scrapy.utils.python import without_none_values
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Collection, Iterable, Mapping, MutableMapping
|
from collections.abc import Callable, Collection, Iterable, Mapping, MutableMapping
|
||||||
|
|
||||||
|
|
||||||
def build_component_list(
|
def build_component_list(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import inspect
|
import inspect
|
||||||
import warnings
|
import warnings
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, overload
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred, maybeDeferred
|
from twisted.internet.defer import Deferred, maybeDeferred
|
||||||
from twisted.internet.threads import deferToThread
|
from twisted.internet.threads import deferToThread
|
||||||
|
|
@ -13,13 +13,9 @@ from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import AsyncGenerator, Callable, Coroutine
|
from collections.abc import AsyncGenerator, Callable, Coroutine
|
||||||
|
|
||||||
# typing.ParamSpec requires Python 3.10
|
|
||||||
from typing_extensions import ParamSpec
|
|
||||||
|
|
||||||
_P = ParamSpec("_P")
|
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
|
||||||
def deprecated(
|
def deprecated(
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,16 @@ import warnings
|
||||||
from asyncio import Future
|
from asyncio import Future
|
||||||
from collections.abc import Awaitable, Coroutine, Iterable, Iterator
|
from collections.abc import Awaitable, Coroutine, Iterable, Iterator
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast, overload
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Concatenate,
|
||||||
|
Generic,
|
||||||
|
ParamSpec,
|
||||||
|
TypeVar,
|
||||||
|
cast,
|
||||||
|
overload,
|
||||||
|
)
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred, DeferredList, fail, succeed
|
from twisted.internet.defer import Deferred, DeferredList, fail, succeed
|
||||||
from twisted.internet.task import Cooperator
|
from twisted.internet.task import Cooperator
|
||||||
|
|
@ -24,14 +33,10 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
# typing.Concatenate and typing.ParamSpec require Python 3.10
|
|
||||||
from typing_extensions import Concatenate, ParamSpec
|
|
||||||
|
|
||||||
_P = ParamSpec("_P")
|
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
_T2 = TypeVar("_T2")
|
_T2 = TypeVar("_T2")
|
||||||
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
|
||||||
_DEFER_DELAY = 0.1
|
_DEFER_DELAY = 0.1
|
||||||
|
|
@ -327,7 +332,7 @@ def process_chain_both(
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
d: Deferred = Deferred()
|
d: Deferred = Deferred()
|
||||||
for cb, eb in zip(callbacks, errbacks):
|
for cb, eb in zip(callbacks, errbacks, strict=False):
|
||||||
d.addCallback(cb, *a, **kw)
|
d.addCallback(cb, *a, **kw)
|
||||||
d.addErrback(eb, *a, **kw)
|
d.addErrback(eb, *a, **kw)
|
||||||
if isinstance(input, failure.Failure):
|
if isinstance(input, failure.Failure):
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ def csviter(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
yield dict(zip(headers, row))
|
yield dict(zip(headers, row, strict=False))
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import pprint
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
from logging.config import dictConfig
|
from logging.config import dictConfig
|
||||||
from typing import TYPE_CHECKING, Any, Optional, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from twisted.internet import asyncioreactor
|
from twisted.internet import asyncioreactor
|
||||||
from twisted.python import log as twisted_log
|
from twisted.python import log as twisted_log
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
import scrapy
|
import scrapy
|
||||||
from scrapy.settings import Settings, _SettingsKeyT
|
from scrapy.settings import Settings, _SettingsKey
|
||||||
from scrapy.utils.versions import get_versions
|
from scrapy.utils.versions import get_versions
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -35,7 +35,7 @@ def failure_to_exc_info(
|
||||||
return (
|
return (
|
||||||
failure.type,
|
failure.type,
|
||||||
failure.value,
|
failure.value,
|
||||||
cast("Optional[TracebackType]", failure.getTracebackObject()),
|
cast("TracebackType | None", failure.getTracebackObject()),
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -83,7 +83,7 @@ DEFAULT_LOGGING = {
|
||||||
|
|
||||||
|
|
||||||
def configure_logging(
|
def configure_logging(
|
||||||
settings: Settings | dict[_SettingsKeyT, Any] | None = None,
|
settings: Settings | dict[_SettingsKey, Any] | None = None,
|
||||||
install_root_handler: bool = True,
|
install_root_handler: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ from __future__ import annotations
|
||||||
import signal
|
import signal
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, TypeAlias
|
||||||
|
|
||||||
# copy of _HANDLER from typeshed/stdlib/signal.pyi
|
# copy of _HANDLER from typeshed/stdlib/signal.pyi
|
||||||
SignalHandlerT = Union[
|
SignalHandlerT: TypeAlias = (
|
||||||
Callable[[int, Optional[FrameType]], Any], int, signal.Handlers, None
|
Callable[[int, FrameType | None], Any] | int | signal.Handlers | None
|
||||||
]
|
)
|
||||||
|
|
||||||
signal_names: dict[int, str] = {}
|
signal_names: dict[int, str] = {}
|
||||||
for signame in dir(signal):
|
for signame in dir(signal):
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import weakref
|
||||||
from collections.abc import AsyncIterator, Iterable, Mapping
|
from collections.abc import AsyncIterator, Iterable, Mapping
|
||||||
from functools import partial, wraps
|
from functools import partial, wraps
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, overload
|
from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar, overload
|
||||||
|
|
||||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
from scrapy.utils.asyncgen import as_async_generator
|
from scrapy.utils.asyncgen import as_async_generator
|
||||||
|
|
@ -22,15 +22,14 @@ if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterator
|
from collections.abc import Callable, Iterator
|
||||||
from re import Pattern
|
from re import Pattern
|
||||||
|
|
||||||
# typing.Concatenate and typing.ParamSpec require Python 3.10
|
|
||||||
# typing.Self requires Python 3.11
|
# typing.Self requires Python 3.11
|
||||||
from typing_extensions import Concatenate, ParamSpec, Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
_P = ParamSpec("_P")
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
_KT = TypeVar("_KT")
|
_KT = TypeVar("_KT")
|
||||||
_VT = TypeVar("_VT")
|
_VT = TypeVar("_VT")
|
||||||
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
|
||||||
def flatten(x: Iterable[Any]) -> list[Any]:
|
def flatten(x: Iterable[Any]) -> list[Any]:
|
||||||
|
|
@ -286,7 +285,7 @@ def get_spec(func: Callable[..., Any]) -> tuple[list[str], dict[str, Any]]:
|
||||||
|
|
||||||
firstdefault = len(spec.args) - len(defaults)
|
firstdefault = len(spec.args) - len(defaults)
|
||||||
args = spec.args[:firstdefault]
|
args = spec.args[:firstdefault]
|
||||||
kwargs = dict(zip(spec.args[firstdefault:], defaults))
|
kwargs = dict(zip(spec.args[firstdefault:], defaults, strict=False))
|
||||||
return args, kwargs
|
return args, kwargs
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
from typing import TYPE_CHECKING, Any, Generic, ParamSpec, TypeVar
|
||||||
from warnings import catch_warnings, filterwarnings
|
from warnings import catch_warnings, filterwarnings
|
||||||
|
|
||||||
from twisted.internet import asyncioreactor, error
|
from twisted.internet import asyncioreactor, error
|
||||||
|
|
@ -19,14 +19,11 @@ if TYPE_CHECKING:
|
||||||
from twisted.internet.protocol import ServerFactory
|
from twisted.internet.protocol import ServerFactory
|
||||||
from twisted.internet.tcp import Port
|
from twisted.internet.tcp import Port
|
||||||
|
|
||||||
# typing.ParamSpec requires Python 3.10
|
|
||||||
from typing_extensions import ParamSpec
|
|
||||||
|
|
||||||
from scrapy.utils.asyncio import CallLaterResult
|
from scrapy.utils.asyncio import CallLaterResult
|
||||||
|
|
||||||
_P = ParamSpec("_P")
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
|
||||||
def listen_tcp(portrange: list[int], host: str, factory: ServerFactory) -> Port: # type: ignore[return] # pylint: disable=inconsistent-return-statements # noqa: RET503
|
def listen_tcp(portrange: list[int], host: str, factory: ServerFactory) -> Port: # type: ignore[return] # pylint: disable=inconsistent-return-statements # noqa: RET503
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from __future__ import annotations
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from typing import TYPE_CHECKING, Union
|
from typing import TYPE_CHECKING, TypeAlias
|
||||||
from urllib.parse import ParseResult, urldefrag, urlparse, urlunparse
|
from urllib.parse import ParseResult, urldefrag, urlparse, urlunparse
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
from scrapy import Spider
|
from scrapy import Spider
|
||||||
|
|
||||||
UrlT = Union[str, bytes, ParseResult]
|
UrlT: TypeAlias = str | bytes | ParseResult
|
||||||
|
|
||||||
|
|
||||||
def url_is_from_any_domain(url: UrlT, domains: Iterable[str]) -> bool:
|
def url_is_from_any_domain(url: UrlT, domains: Iterable[str]) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -369,7 +369,7 @@ with multiples lines
|
||||||
est = [x for sublist in est for x in sublist] # flatten
|
est = [x for sublist in est for x in sublist] # flatten
|
||||||
est = [x.lstrip().rstrip() for x in est]
|
est = [x.lstrip().rstrip() for x in est]
|
||||||
it = iter(est)
|
it = iter(est)
|
||||||
s = dict(zip(it, it))
|
s = dict(zip(it, it, strict=False))
|
||||||
|
|
||||||
assert s["engine.spider.name"] == crawler.spider.name
|
assert s["engine.spider.name"] == crawler.spider.name
|
||||||
assert s["len(engine.scraper.slot.active)"] == "1"
|
assert s["len(engine.scraper.slot.active)"] == "1"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Any, Callable
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from scrapy.extensions.periodic_log import PeriodicLog
|
from scrapy.extensions.periodic_log import PeriodicLog
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
|
||||||
from .spiders import MetaSpider
|
from .spiders import MetaSpider
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
stats_dump_1 = {
|
stats_dump_1 = {
|
||||||
"log_count/INFO": 10,
|
"log_count/INFO": 10,
|
||||||
"log_count/WARNING": 1,
|
"log_count/WARNING": 1,
|
||||||
|
|
|
||||||
|
|
@ -2628,7 +2628,7 @@ class TestBatchDeliveries(TestFeedExportBase):
|
||||||
}
|
}
|
||||||
data = await self.exported_data(items, settings)
|
data = await self.exported_data(items, settings)
|
||||||
for fmt, expected in formats.items():
|
for fmt, expected in formats.items():
|
||||||
for expected_batch, got_batch in zip(expected, data[fmt]):
|
for expected_batch, got_batch in zip(expected, data[fmt], strict=False):
|
||||||
assert got_batch == expected_batch
|
assert got_batch == expected_batch
|
||||||
|
|
||||||
@deferred_f_from_coro_f
|
@deferred_f_from_coro_f
|
||||||
|
|
@ -2652,7 +2652,7 @@ class TestBatchDeliveries(TestFeedExportBase):
|
||||||
}
|
}
|
||||||
data = await self.exported_data(items, settings)
|
data = await self.exported_data(items, settings)
|
||||||
for fmt, expected in formats.items():
|
for fmt, expected in formats.items():
|
||||||
for expected_batch, got_batch in zip(expected, data[fmt]):
|
for expected_batch, got_batch in zip(expected, data[fmt], strict=False):
|
||||||
assert got_batch == expected_batch
|
assert got_batch == expected_batch
|
||||||
|
|
||||||
@deferred_f_from_coro_f
|
@deferred_f_from_coro_f
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import re
|
||||||
import string
|
import string
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Callable, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ from tests.mockserver.http_resources import LeafResource, Status
|
||||||
from tests.mockserver.utils import ssl_context_factory
|
from tests.mockserver.utils import ssl_context_factory
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import AsyncGenerator, Coroutine, Generator
|
from collections.abc import AsyncGenerator, Callable, Coroutine, Generator
|
||||||
|
|
||||||
from scrapy.core.http2.protocol import H2ClientProtocol
|
from scrapy.core.http2.protocol import H2ClientProtocol
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,7 @@ class TestResponseBase:
|
||||||
if response is None:
|
if response is None:
|
||||||
response = self._links_response()
|
response = self._links_response()
|
||||||
followed = response.follow_all(follow_obj)
|
followed = response.follow_all(follow_obj)
|
||||||
for req, target in zip(followed, target_urls):
|
for req, target in zip(followed, target_urls, strict=False):
|
||||||
assert req.url == target
|
assert req.url == target
|
||||||
yield req
|
yield req
|
||||||
|
|
||||||
|
|
@ -647,7 +647,7 @@ class TestTextResponse(TestResponseBase):
|
||||||
|
|
||||||
# select <a> elements
|
# select <a> elements
|
||||||
for sellist in [resp.css("a"), resp.xpath("//a")]:
|
for sellist in [resp.css("a"), resp.xpath("//a")]:
|
||||||
for sel, url in zip(sellist, urls):
|
for sel, url in zip(sellist, urls, strict=False):
|
||||||
self._assert_followed_url(sel, url, response=resp)
|
self._assert_followed_url(sel, url, response=resp)
|
||||||
|
|
||||||
# select <link> elements
|
# select <link> elements
|
||||||
|
|
@ -659,7 +659,7 @@ class TestTextResponse(TestResponseBase):
|
||||||
|
|
||||||
# href attributes should work
|
# href attributes should work
|
||||||
for sellist in [resp.css("a::attr(href)"), resp.xpath("//a/@href")]:
|
for sellist in [resp.css("a::attr(href)"), resp.xpath("//a/@href")]:
|
||||||
for sel, url in zip(sellist, urls):
|
for sel, url in zip(sellist, urls, strict=False):
|
||||||
self._assert_followed_url(sel, url, response=resp)
|
self._assert_followed_url(sel, url, response=resp)
|
||||||
|
|
||||||
# non-a elements are not supported
|
# non-a elements are not supported
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class MyRequest2(Request):
|
||||||
|
|
||||||
@pytest.mark.mypy_testing
|
@pytest.mark.mypy_testing
|
||||||
def mypy_test_headers():
|
def mypy_test_headers():
|
||||||
Request("data:,", headers=1) # E: Argument "headers" to "Request" has incompatible type "int"; expected "Union[Mapping[str, Any], Iterable[tuple[str, Any]], None]"
|
Request("data:,", headers=1) # E: Argument "headers" to "Request" has incompatible type "int"; expected "Mapping[str, Any] | Iterable[tuple[str, Any]] | None"
|
||||||
Request("data:,", headers=None)
|
Request("data:,", headers=None)
|
||||||
Request("data:,", headers={})
|
Request("data:,", headers={})
|
||||||
Request("data:,", headers=[])
|
Request("data:,", headers=[])
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from scrapy.http import HtmlResponse, Response, TextResponse
|
||||||
|
|
||||||
@pytest.mark.mypy_testing
|
@pytest.mark.mypy_testing
|
||||||
def mypy_test_headers():
|
def mypy_test_headers():
|
||||||
Response("data:,", headers=1) # E: Argument "headers" to "Response" has incompatible type "int"; expected "Union[Mapping[str, Any], Iterable[tuple[str, Any]], None]"
|
Response("data:,", headers=1) # E: Argument "headers" to "Response" has incompatible type "int"; expected "Mapping[str, Any] | Iterable[tuple[str, Any]] | None"
|
||||||
Response("data:,", headers=None)
|
Response("data:,", headers=None)
|
||||||
Response("data:,", headers={})
|
Response("data:,", headers={})
|
||||||
Response("data:,", headers=[])
|
Response("data:,", headers=[])
|
||||||
|
|
|
||||||
20
tox.ini
20
tox.ini
|
|
@ -43,7 +43,7 @@ commands =
|
||||||
pytest {posargs:--cov-config=pyproject.toml --cov=scrapy --cov-report= --cov-report=term-missing --cov-report=xml --junitxml=testenv.junit.xml -o junit_family=legacy --durations=10 scrapy tests --doctest-modules}
|
pytest {posargs:--cov-config=pyproject.toml --cov=scrapy --cov-report= --cov-report=term-missing --cov-report=xml --junitxml=testenv.junit.xml -o junit_family=legacy --durations=10 scrapy tests --doctest-modules}
|
||||||
|
|
||||||
[testenv:typing]
|
[testenv:typing]
|
||||||
basepython = python3.9
|
basepython = python3.10
|
||||||
deps =
|
deps =
|
||||||
mypy==1.16.1
|
mypy==1.16.1
|
||||||
typing-extensions==4.14.1
|
typing-extensions==4.14.1
|
||||||
|
|
@ -64,7 +64,7 @@ commands =
|
||||||
mypy {posargs:scrapy tests}
|
mypy {posargs:scrapy tests}
|
||||||
|
|
||||||
[testenv:typing-tests]
|
[testenv:typing-tests]
|
||||||
basepython = python3.9
|
basepython = python3.10
|
||||||
deps =
|
deps =
|
||||||
{[test-requirements]deps}
|
{[test-requirements]deps}
|
||||||
{[testenv:typing]deps}
|
{[testenv:typing]deps}
|
||||||
|
|
@ -97,7 +97,7 @@ commands =
|
||||||
twine check dist/*
|
twine check dist/*
|
||||||
|
|
||||||
[pinned]
|
[pinned]
|
||||||
basepython = python3.9
|
basepython = python3.10
|
||||||
deps =
|
deps =
|
||||||
# pytest 8.4.1 adds support for Twisted 25.5.0 but drops support for Twisted < 24.10.0
|
# pytest 8.4.1 adds support for Twisted 25.5.0 but drops support for Twisted < 24.10.0
|
||||||
pytest==8.4.0
|
pytest==8.4.0
|
||||||
|
|
@ -106,7 +106,7 @@ deps =
|
||||||
cryptography==37.0.0
|
cryptography==37.0.0
|
||||||
cssselect==0.9.1
|
cssselect==0.9.1
|
||||||
itemadapter==0.1.0
|
itemadapter==0.1.0
|
||||||
lxml==4.6.0
|
lxml==4.6.4
|
||||||
parsel==1.5.0
|
parsel==1.5.0
|
||||||
pyOpenSSL==22.0.0
|
pyOpenSSL==22.0.0
|
||||||
queuelib==1.4.2
|
queuelib==1.4.2
|
||||||
|
|
@ -151,17 +151,17 @@ deps =
|
||||||
basepython = {[pinned]basepython}
|
basepython = {[pinned]basepython}
|
||||||
deps =
|
deps =
|
||||||
{[pinned]deps}
|
{[pinned]deps}
|
||||||
Pillow==8.0.0
|
Pillow==8.3.2
|
||||||
boto3==1.20.0
|
boto3==1.20.0
|
||||||
bpython==0.7.1
|
bpython==0.7.1
|
||||||
brotli==0.5.2; implementation_name != "pypy"
|
brotli==0.5.2; implementation_name != "pypy"
|
||||||
brotlicffi==0.8.0; implementation_name == "pypy"
|
brotlicffi==0.8.0; implementation_name == "pypy"
|
||||||
brotlipy
|
brotlipy
|
||||||
google-cloud-storage==1.29.0
|
google-cloud-storage==1.29.0
|
||||||
ipython==2.0.0
|
ipython==7.1.0
|
||||||
robotexclusionrulesparser==1.6.2
|
robotexclusionrulesparser==1.6.2
|
||||||
uvloop==0.14.0; platform_system != "Windows" and implementation_name != "pypy"
|
uvloop==0.16.0; platform_system != "Windows" and implementation_name != "pypy"
|
||||||
zstandard==0.1; implementation_name != "pypy"
|
zstandard==0.16.0; implementation_name != "pypy"
|
||||||
setenv =
|
setenv =
|
||||||
{[pinned]setenv}
|
{[pinned]setenv}
|
||||||
commands = {[pinned]commands}
|
commands = {[pinned]commands}
|
||||||
|
|
@ -254,7 +254,7 @@ commands =
|
||||||
[testenv:botocore]
|
[testenv:botocore]
|
||||||
deps =
|
deps =
|
||||||
{[testenv]deps}
|
{[testenv]deps}
|
||||||
botocore>=1.4.87
|
botocore>=1.13.45
|
||||||
commands =
|
commands =
|
||||||
pytest {posargs:--cov-config=pyproject.toml --cov=scrapy --cov-report=xml --cov-report= tests --junitxml=botocore.junit.xml -o junit_family=legacy -m requires_botocore}
|
pytest {posargs:--cov-config=pyproject.toml --cov=scrapy --cov-report=xml --cov-report= tests --junitxml=botocore.junit.xml -o junit_family=legacy -m requires_botocore}
|
||||||
|
|
||||||
|
|
@ -262,7 +262,7 @@ commands =
|
||||||
basepython = {[pinned]basepython}
|
basepython = {[pinned]basepython}
|
||||||
deps =
|
deps =
|
||||||
{[pinned]deps}
|
{[pinned]deps}
|
||||||
botocore==1.4.87
|
botocore==1.13.45
|
||||||
setenv =
|
setenv =
|
||||||
{[pinned]setenv}
|
{[pinned]setenv}
|
||||||
commands =
|
commands =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue