mirror of https://github.com/scrapy/scrapy.git
Some type hints
This commit is contained in:
parent
7fec9f991f
commit
ef09e0d10f
|
|
@ -22,7 +22,7 @@ __all__ = [
|
||||||
|
|
||||||
|
|
||||||
# Scrapy and Twisted versions
|
# Scrapy and Twisted versions
|
||||||
__version__ = pkgutil.get_data(__package__, 'VERSION').decode('ascii').strip()
|
__version__ = (pkgutil.get_data(__package__, "VERSION") or b"").decode("ascii").strip()
|
||||||
version_info = tuple(int(v) if v.isdigit() else v for v in __version__.split('.'))
|
version_info = tuple(int(v) if v.isdigit() else v for v in __version__.split('.'))
|
||||||
twisted_version = (_txv.major, _txv.minor, _txv.micro)
|
twisted_version = (_txv.major, _txv.minor, _txv.micro)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ Base class for Scrapy commands
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from optparse import OptionGroup
|
from optparse import OptionGroup
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
from twisted.python import failure
|
from twisted.python import failure
|
||||||
|
|
||||||
from scrapy.utils.conf import arglist_to_dict, feed_process_params_from_cli
|
from scrapy.utils.conf import arglist_to_dict, feed_process_params_from_cli
|
||||||
|
|
@ -15,7 +17,7 @@ class ScrapyCommand:
|
||||||
crawler_process = None
|
crawler_process = None
|
||||||
|
|
||||||
# default settings to be used for this command instead of global defaults
|
# default settings to be used for this command instead of global defaults
|
||||||
default_settings = {}
|
default_settings: Dict[str, Any] = {}
|
||||||
|
|
||||||
exitcode = 0
|
exitcode = 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from itemadapter import is_item, ItemAdapter
|
from itemadapter import is_item, ItemAdapter
|
||||||
from w3lib.url import is_url
|
from w3lib.url import is_url
|
||||||
|
|
@ -10,6 +11,7 @@ from scrapy.utils import display
|
||||||
from scrapy.utils.spider import iterate_spider_output, spidercls_for_request
|
from scrapy.utils.spider import iterate_spider_output, spidercls_for_request
|
||||||
from scrapy.exceptions import UsageError
|
from scrapy.exceptions import UsageError
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,8 +19,8 @@ class Command(BaseRunSpiderCommand):
|
||||||
requires_project = True
|
requires_project = True
|
||||||
|
|
||||||
spider = None
|
spider = None
|
||||||
items = {}
|
items: Dict[int, list] = {}
|
||||||
requests = {}
|
requests: Dict[int, list] = {}
|
||||||
|
|
||||||
first_response = None
|
first_response = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,77 @@
|
||||||
import sys
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from inspect import getmembers
|
from inspect import getmembers
|
||||||
|
from typing import Dict
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
from scrapy.utils.spider import iterate_spider_output
|
|
||||||
from scrapy.utils.python import get_spec
|
from scrapy.utils.python import get_spec
|
||||||
|
from scrapy.utils.spider import iterate_spider_output
|
||||||
|
|
||||||
|
|
||||||
|
class Contract:
|
||||||
|
""" Abstract class for contracts """
|
||||||
|
request_cls = None
|
||||||
|
|
||||||
|
def __init__(self, method, *args):
|
||||||
|
self.testcase_pre = _create_testcase(method, f'@{self.name} pre-hook')
|
||||||
|
self.testcase_post = _create_testcase(method, f'@{self.name} post-hook')
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def add_pre_hook(self, request, results):
|
||||||
|
if hasattr(self, 'pre_process'):
|
||||||
|
cb = request.callback
|
||||||
|
|
||||||
|
@wraps(cb)
|
||||||
|
def wrapper(response, **cb_kwargs):
|
||||||
|
try:
|
||||||
|
results.startTest(self.testcase_pre)
|
||||||
|
self.pre_process(response)
|
||||||
|
results.stopTest(self.testcase_pre)
|
||||||
|
except AssertionError:
|
||||||
|
results.addFailure(self.testcase_pre, sys.exc_info())
|
||||||
|
except Exception:
|
||||||
|
results.addError(self.testcase_pre, sys.exc_info())
|
||||||
|
else:
|
||||||
|
results.addSuccess(self.testcase_pre)
|
||||||
|
finally:
|
||||||
|
return list(iterate_spider_output(cb(response, **cb_kwargs)))
|
||||||
|
|
||||||
|
request.callback = wrapper
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
def add_post_hook(self, request, results):
|
||||||
|
if hasattr(self, 'post_process'):
|
||||||
|
cb = request.callback
|
||||||
|
|
||||||
|
@wraps(cb)
|
||||||
|
def wrapper(response, **cb_kwargs):
|
||||||
|
output = list(iterate_spider_output(cb(response, **cb_kwargs)))
|
||||||
|
try:
|
||||||
|
results.startTest(self.testcase_post)
|
||||||
|
self.post_process(output)
|
||||||
|
results.stopTest(self.testcase_post)
|
||||||
|
except AssertionError:
|
||||||
|
results.addFailure(self.testcase_post, sys.exc_info())
|
||||||
|
except Exception:
|
||||||
|
results.addError(self.testcase_post, sys.exc_info())
|
||||||
|
else:
|
||||||
|
results.addSuccess(self.testcase_post)
|
||||||
|
finally:
|
||||||
|
return output
|
||||||
|
|
||||||
|
request.callback = wrapper
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
def adjust_request_args(self, args):
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
class ContractsManager:
|
class ContractsManager:
|
||||||
contracts = {}
|
contracts: Dict[str, Contract] = {}
|
||||||
|
|
||||||
def __init__(self, contracts):
|
def __init__(self, contracts):
|
||||||
for contract in contracts:
|
for contract in contracts:
|
||||||
|
|
@ -107,66 +168,6 @@ class ContractsManager:
|
||||||
request.errback = eb_wrapper
|
request.errback = eb_wrapper
|
||||||
|
|
||||||
|
|
||||||
class Contract:
|
|
||||||
""" Abstract class for contracts """
|
|
||||||
request_cls = None
|
|
||||||
|
|
||||||
def __init__(self, method, *args):
|
|
||||||
self.testcase_pre = _create_testcase(method, f'@{self.name} pre-hook')
|
|
||||||
self.testcase_post = _create_testcase(method, f'@{self.name} post-hook')
|
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def add_pre_hook(self, request, results):
|
|
||||||
if hasattr(self, 'pre_process'):
|
|
||||||
cb = request.callback
|
|
||||||
|
|
||||||
@wraps(cb)
|
|
||||||
def wrapper(response, **cb_kwargs):
|
|
||||||
try:
|
|
||||||
results.startTest(self.testcase_pre)
|
|
||||||
self.pre_process(response)
|
|
||||||
results.stopTest(self.testcase_pre)
|
|
||||||
except AssertionError:
|
|
||||||
results.addFailure(self.testcase_pre, sys.exc_info())
|
|
||||||
except Exception:
|
|
||||||
results.addError(self.testcase_pre, sys.exc_info())
|
|
||||||
else:
|
|
||||||
results.addSuccess(self.testcase_pre)
|
|
||||||
finally:
|
|
||||||
return list(iterate_spider_output(cb(response, **cb_kwargs)))
|
|
||||||
|
|
||||||
request.callback = wrapper
|
|
||||||
|
|
||||||
return request
|
|
||||||
|
|
||||||
def add_post_hook(self, request, results):
|
|
||||||
if hasattr(self, 'post_process'):
|
|
||||||
cb = request.callback
|
|
||||||
|
|
||||||
@wraps(cb)
|
|
||||||
def wrapper(response, **cb_kwargs):
|
|
||||||
output = list(iterate_spider_output(cb(response, **cb_kwargs)))
|
|
||||||
try:
|
|
||||||
results.startTest(self.testcase_post)
|
|
||||||
self.post_process(output)
|
|
||||||
results.stopTest(self.testcase_post)
|
|
||||||
except AssertionError:
|
|
||||||
results.addFailure(self.testcase_post, sys.exc_info())
|
|
||||||
except Exception:
|
|
||||||
results.addError(self.testcase_post, sys.exc_info())
|
|
||||||
else:
|
|
||||||
results.addSuccess(self.testcase_post)
|
|
||||||
finally:
|
|
||||||
return output
|
|
||||||
|
|
||||||
request.callback = wrapper
|
|
||||||
|
|
||||||
return request
|
|
||||||
|
|
||||||
def adjust_request_args(self, args):
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def _create_testcase(method, desc):
|
def _create_testcase(method, desc):
|
||||||
spider = method.__self__.name
|
spider = method.__self__.name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
from http.cookiejar import CookieJar as _CookieJar, DefaultCookiePolicy, IPV4_RE
|
from http.cookiejar import CookieJar as _CookieJar, DefaultCookiePolicy
|
||||||
|
|
||||||
from scrapy.utils.httpobj import urlparse_cached
|
from scrapy.utils.httpobj import urlparse_cached
|
||||||
from scrapy.utils.python import to_unicode
|
from scrapy.utils.python import to_unicode
|
||||||
|
|
||||||
|
|
||||||
|
# Defined in the http.cookiejar module, but undocumented:
|
||||||
|
# https://github.com/python/cpython/blob/v3.9.0/Lib/http/cookiejar.py#L527
|
||||||
|
IPV4_RE = re.compile(r"\.\d+$", re.ASCII)
|
||||||
|
|
||||||
|
|
||||||
class CookieJar:
|
class CookieJar:
|
||||||
def __init__(self, policy=None, check_expired_frequency=10000):
|
def __init__(self, policy=None, check_expired_frequency=10000):
|
||||||
self.policy = policy or DefaultCookiePolicy()
|
self.policy = policy or DefaultCookiePolicy()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from abc import ABCMeta
|
||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
from typing import Dict
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from scrapy.utils.deprecate import ScrapyDeprecationWarning
|
from scrapy.utils.deprecate import ScrapyDeprecationWarning
|
||||||
|
|
@ -75,7 +76,7 @@ class ItemMeta(_BaseItemMeta):
|
||||||
|
|
||||||
class DictItem(MutableMapping, BaseItem):
|
class DictItem(MutableMapping, BaseItem):
|
||||||
|
|
||||||
fields = {}
|
fields: Dict[str, Field] = {}
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if issubclass(cls, DictItem) and not issubclass(cls, Item):
|
if issubclass(cls, DictItem) and not issubclass(cls, Item):
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from email.mime.base import MIMEBase
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.nonmultipart import MIMENonMultipart
|
from email.mime.nonmultipart import MIMENonMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.utils import COMMASPACE, formatdate
|
from email.utils import formatdate
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from twisted.internet import defer, ssl
|
from twisted.internet import defer, ssl
|
||||||
|
|
@ -21,6 +21,11 @@ from scrapy.utils.python import to_bytes
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Defined in the email.utils module, but undocumented:
|
||||||
|
# https://github.com/python/cpython/blob/v3.9.0/Lib/email/utils.py#L42
|
||||||
|
COMMASPACE = ", "
|
||||||
|
|
||||||
|
|
||||||
def _to_bytes_or_none(text):
|
def _to_bytes_or_none(text):
|
||||||
if text is None:
|
if text is None:
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ RefererMiddleware: populates Request referer field, based on the Response which
|
||||||
originated it.
|
originated it.
|
||||||
"""
|
"""
|
||||||
import warnings
|
import warnings
|
||||||
|
from typing import Tuple
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from w3lib.url import safe_url_string
|
from w3lib.url import safe_url_string
|
||||||
|
|
||||||
from scrapy.http import Request, Response
|
|
||||||
from scrapy.exceptions import NotConfigured
|
|
||||||
from scrapy import signals
|
from scrapy import signals
|
||||||
from scrapy.utils.python import to_unicode
|
from scrapy.exceptions import NotConfigured
|
||||||
|
from scrapy.http import Request, Response
|
||||||
from scrapy.utils.misc import load_object
|
from scrapy.utils.misc import load_object
|
||||||
|
from scrapy.utils.python import to_unicode
|
||||||
from scrapy.utils.url import strip_url
|
from scrapy.utils.url import strip_url
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,7 +31,8 @@ POLICY_SCRAPY_DEFAULT = "scrapy-default"
|
||||||
|
|
||||||
class ReferrerPolicy:
|
class ReferrerPolicy:
|
||||||
|
|
||||||
NOREFERRER_SCHEMES = LOCAL_SCHEMES
|
NOREFERRER_SCHEMES: Tuple[str, ...] = LOCAL_SCHEMES
|
||||||
|
name: str
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
@ -88,7 +90,7 @@ class NoReferrerPolicy(ReferrerPolicy):
|
||||||
is to be sent along with requests made from a particular request client to any origin.
|
is to be sent along with requests made from a particular request client to any origin.
|
||||||
The header will be omitted entirely.
|
The header will be omitted entirely.
|
||||||
"""
|
"""
|
||||||
name = POLICY_NO_REFERRER
|
name: str = POLICY_NO_REFERRER
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
return None
|
return None
|
||||||
|
|
@ -108,7 +110,7 @@ class NoReferrerWhenDowngradePolicy(ReferrerPolicy):
|
||||||
|
|
||||||
This is a user agent's default behavior, if no policy is otherwise specified.
|
This is a user agent's default behavior, if no policy is otherwise specified.
|
||||||
"""
|
"""
|
||||||
name = POLICY_NO_REFERRER_WHEN_DOWNGRADE
|
name: str = POLICY_NO_REFERRER_WHEN_DOWNGRADE
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
if not self.tls_protected(response_url) or self.tls_protected(request_url):
|
if not self.tls_protected(response_url) or self.tls_protected(request_url):
|
||||||
|
|
@ -125,7 +127,7 @@ class SameOriginPolicy(ReferrerPolicy):
|
||||||
Cross-origin requests, on the other hand, will contain no referrer information.
|
Cross-origin requests, on the other hand, will contain no referrer information.
|
||||||
A Referer HTTP header will not be sent.
|
A Referer HTTP header will not be sent.
|
||||||
"""
|
"""
|
||||||
name = POLICY_SAME_ORIGIN
|
name: str = POLICY_SAME_ORIGIN
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
if self.origin(response_url) == self.origin(request_url):
|
if self.origin(response_url) == self.origin(request_url):
|
||||||
|
|
@ -141,7 +143,7 @@ class OriginPolicy(ReferrerPolicy):
|
||||||
when making both same-origin requests and cross-origin requests
|
when making both same-origin requests and cross-origin requests
|
||||||
from a particular request client.
|
from a particular request client.
|
||||||
"""
|
"""
|
||||||
name = POLICY_ORIGIN
|
name: str = POLICY_ORIGIN
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
return self.origin_referrer(response_url)
|
return self.origin_referrer(response_url)
|
||||||
|
|
@ -160,7 +162,7 @@ class StrictOriginPolicy(ReferrerPolicy):
|
||||||
on the other hand, will contain no referrer information.
|
on the other hand, will contain no referrer information.
|
||||||
A Referer HTTP header will not be sent.
|
A Referer HTTP header will not be sent.
|
||||||
"""
|
"""
|
||||||
name = POLICY_STRICT_ORIGIN
|
name: str = POLICY_STRICT_ORIGIN
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
if (
|
if (
|
||||||
|
|
@ -181,7 +183,7 @@ class OriginWhenCrossOriginPolicy(ReferrerPolicy):
|
||||||
is sent as referrer information when making cross-origin requests
|
is sent as referrer information when making cross-origin requests
|
||||||
from a particular request client.
|
from a particular request client.
|
||||||
"""
|
"""
|
||||||
name = POLICY_ORIGIN_WHEN_CROSS_ORIGIN
|
name: str = POLICY_ORIGIN_WHEN_CROSS_ORIGIN
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
origin = self.origin(response_url)
|
origin = self.origin(response_url)
|
||||||
|
|
@ -208,7 +210,7 @@ class StrictOriginWhenCrossOriginPolicy(ReferrerPolicy):
|
||||||
on the other hand, will contain no referrer information.
|
on the other hand, will contain no referrer information.
|
||||||
A Referer HTTP header will not be sent.
|
A Referer HTTP header will not be sent.
|
||||||
"""
|
"""
|
||||||
name = POLICY_STRICT_ORIGIN_WHEN_CROSS_ORIGIN
|
name: str = POLICY_STRICT_ORIGIN_WHEN_CROSS_ORIGIN
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
origin = self.origin(response_url)
|
origin = self.origin(response_url)
|
||||||
|
|
@ -234,7 +236,7 @@ class UnsafeUrlPolicy(ReferrerPolicy):
|
||||||
to insecure origins.
|
to insecure origins.
|
||||||
Carefully consider the impact of setting such a policy for potentially sensitive documents.
|
Carefully consider the impact of setting such a policy for potentially sensitive documents.
|
||||||
"""
|
"""
|
||||||
name = POLICY_UNSAFE_URL
|
name: str = POLICY_UNSAFE_URL
|
||||||
|
|
||||||
def referrer(self, response_url, request_url):
|
def referrer(self, response_url, request_url):
|
||||||
return self.stripped_referrer(response_url)
|
return self.stripped_referrer(response_url)
|
||||||
|
|
@ -246,8 +248,8 @@ class DefaultReferrerPolicy(NoReferrerWhenDowngradePolicy):
|
||||||
with the addition that "Referer" is not sent if the parent request was
|
with the addition that "Referer" is not sent if the parent request was
|
||||||
using ``file://`` or ``s3://`` scheme.
|
using ``file://`` or ``s3://`` scheme.
|
||||||
"""
|
"""
|
||||||
NOREFERRER_SCHEMES = LOCAL_SCHEMES + ('file', 's3')
|
NOREFERRER_SCHEMES: Tuple[str, ...] = LOCAL_SCHEMES + ('file', 's3')
|
||||||
name = POLICY_SCRAPY_DEFAULT
|
name: str = POLICY_SCRAPY_DEFAULT
|
||||||
|
|
||||||
|
|
||||||
_policy_classes = {p.name: p for p in (
|
_policy_classes = {p.name: p for p in (
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
"""Helper functions for scrapy.http objects (Request, Response)"""
|
"""Helper functions for scrapy.http objects (Request, Response)"""
|
||||||
|
|
||||||
import weakref
|
from typing import Union
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse, ParseResult
|
||||||
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
from scrapy.http import Request, Response
|
||||||
|
|
||||||
|
|
||||||
_urlparse_cache = weakref.WeakKeyDictionary()
|
_urlparse_cache: "WeakKeyDictionary[Union[Request, Response], ParseResult]" = WeakKeyDictionary()
|
||||||
|
|
||||||
|
|
||||||
def urlparse_cached(request_or_response):
|
def urlparse_cached(request_or_response: Union[Request, Response]) -> ParseResult:
|
||||||
"""Return urlparse.urlparse caching the result, where the argument can be a
|
"""Return urlparse.urlparse caching the result, where the argument can be a
|
||||||
Request or Response object
|
Request or Response object
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,27 @@ scrapy.http.Request objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import weakref
|
from typing import Dict, Iterable, Optional, Tuple, Union
|
||||||
from urllib.parse import urlunparse
|
from urllib.parse import urlunparse
|
||||||
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
from w3lib.http import basic_auth_header
|
from w3lib.http import basic_auth_header
|
||||||
from w3lib.url import canonicalize_url
|
from w3lib.url import canonicalize_url
|
||||||
|
|
||||||
|
from scrapy.http import Request
|
||||||
from scrapy.utils.httpobj import urlparse_cached
|
from scrapy.utils.httpobj import urlparse_cached
|
||||||
from scrapy.utils.python import to_bytes, to_unicode
|
from scrapy.utils.python import to_bytes, to_unicode
|
||||||
|
|
||||||
|
|
||||||
_fingerprint_cache = weakref.WeakKeyDictionary()
|
_fingerprint_cache: "WeakKeyDictionary[Request, Dict[Tuple[Optional[Tuple[bytes, ...]], bool], str]]"
|
||||||
|
_fingerprint_cache = WeakKeyDictionary()
|
||||||
|
|
||||||
|
|
||||||
def request_fingerprint(request, include_headers=None, keep_fragments=False):
|
def request_fingerprint(
|
||||||
|
request: Request,
|
||||||
|
include_headers: Optional[Iterable[Union[bytes, str]]] = None,
|
||||||
|
keep_fragments: bool = False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Return the request fingerprint.
|
Return the request fingerprint.
|
||||||
|
|
||||||
|
|
@ -49,17 +56,18 @@ def request_fingerprint(request, include_headers=None, keep_fragments=False):
|
||||||
(for instance when handling requests with a headless browser).
|
(for instance when handling requests with a headless browser).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
headers: Optional[Tuple[bytes, ...]] = None
|
||||||
if include_headers:
|
if include_headers:
|
||||||
include_headers = tuple(to_bytes(h.lower()) for h in sorted(include_headers))
|
headers = tuple(to_bytes(h.lower()) for h in sorted(include_headers))
|
||||||
cache = _fingerprint_cache.setdefault(request, {})
|
cache = _fingerprint_cache.setdefault(request, {})
|
||||||
cache_key = (include_headers, keep_fragments)
|
cache_key = (headers, keep_fragments)
|
||||||
if cache_key not in cache:
|
if cache_key not in cache:
|
||||||
fp = hashlib.sha1()
|
fp = hashlib.sha1()
|
||||||
fp.update(to_bytes(request.method))
|
fp.update(to_bytes(request.method))
|
||||||
fp.update(to_bytes(canonicalize_url(request.url, keep_fragments=keep_fragments)))
|
fp.update(to_bytes(canonicalize_url(request.url, keep_fragments=keep_fragments)))
|
||||||
fp.update(request.body or b'')
|
fp.update(request.body or b'')
|
||||||
if include_headers:
|
if headers:
|
||||||
for hdr in include_headers:
|
for hdr in headers:
|
||||||
if hdr in request.headers:
|
if hdr in request.headers:
|
||||||
fp.update(hdr)
|
fp.update(hdr)
|
||||||
for v in request.headers.getlist(hdr):
|
for v in request.headers.getlist(hdr):
|
||||||
|
|
@ -68,14 +76,14 @@ def request_fingerprint(request, include_headers=None, keep_fragments=False):
|
||||||
return cache[cache_key]
|
return cache[cache_key]
|
||||||
|
|
||||||
|
|
||||||
def request_authenticate(request, username, password):
|
def request_authenticate(request: Request, username: str, password: str) -> None:
|
||||||
"""Autenticate the given request (in place) using the HTTP basic access
|
"""Autenticate the given request (in place) using the HTTP basic access
|
||||||
authentication mechanism (RFC 2617) and the given username and password
|
authentication mechanism (RFC 2617) and the given username and password
|
||||||
"""
|
"""
|
||||||
request.headers['Authorization'] = basic_auth_header(username, password)
|
request.headers['Authorization'] = basic_auth_header(username, password)
|
||||||
|
|
||||||
|
|
||||||
def request_httprepr(request):
|
def request_httprepr(request: Request) -> bytes:
|
||||||
"""Return the raw HTTP representation (as bytes) of the given request.
|
"""Return the raw HTTP representation (as bytes) of the given request.
|
||||||
This is provided only for reference since it's not the actual stream of
|
This is provided only for reference since it's not the actual stream of
|
||||||
bytes that will be send when performing the request (that's controlled
|
bytes that will be send when performing the request (that's controlled
|
||||||
|
|
@ -92,7 +100,7 @@ def request_httprepr(request):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
def referer_str(request):
|
def referer_str(request: Request) -> Optional[str]:
|
||||||
""" Return Referer HTTP header suitable for logging. """
|
""" Return Referer HTTP header suitable for logging. """
|
||||||
referrer = request.headers.get('Referer')
|
referrer = request.headers.get('Referer')
|
||||||
if referrer is None:
|
if referrer is None:
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,23 @@ This module provides some useful functions for working with
|
||||||
scrapy.http.Response objects
|
scrapy.http.Response objects
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import weakref
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from typing import Any, Callable, Iterable, Optional, Tuple, Union
|
||||||
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
import scrapy
|
||||||
|
from scrapy.http.response import Response
|
||||||
|
|
||||||
from twisted.web import http
|
from twisted.web import http
|
||||||
from scrapy.utils.python import to_bytes, to_unicode
|
from scrapy.utils.python import to_bytes, to_unicode
|
||||||
from w3lib import html
|
from w3lib import html
|
||||||
|
|
||||||
|
|
||||||
_baseurl_cache = weakref.WeakKeyDictionary()
|
_baseurl_cache: "WeakKeyDictionary[Response, str]" = WeakKeyDictionary()
|
||||||
|
|
||||||
|
|
||||||
def get_base_url(response):
|
def get_base_url(response: "scrapy.http.response.text.TextResponse") -> str:
|
||||||
"""Return the base url of the given response, joined with the response url"""
|
"""Return the base url of the given response, joined with the response url"""
|
||||||
if response not in _baseurl_cache:
|
if response not in _baseurl_cache:
|
||||||
text = response.text[0:4096]
|
text = response.text[0:4096]
|
||||||
|
|
@ -23,10 +27,13 @@ def get_base_url(response):
|
||||||
return _baseurl_cache[response]
|
return _baseurl_cache[response]
|
||||||
|
|
||||||
|
|
||||||
_metaref_cache = weakref.WeakKeyDictionary()
|
_metaref_cache: "WeakKeyDictionary[Response, Union[Tuple[None, None], Tuple[float, str]]]" = WeakKeyDictionary()
|
||||||
|
|
||||||
|
|
||||||
def get_meta_refresh(response, ignore_tags=('script', 'noscript')):
|
def get_meta_refresh(
|
||||||
|
response: "scrapy.http.response.text.TextResponse",
|
||||||
|
ignore_tags: Optional[Iterable[str]] = ('script', 'noscript'),
|
||||||
|
) -> Union[Tuple[None, None], Tuple[float, str]]:
|
||||||
"""Parse the http-equiv refrsh parameter from the given response"""
|
"""Parse the http-equiv refrsh parameter from the given response"""
|
||||||
if response not in _metaref_cache:
|
if response not in _metaref_cache:
|
||||||
text = response.text[0:4096]
|
text = response.text[0:4096]
|
||||||
|
|
@ -35,14 +42,15 @@ def get_meta_refresh(response, ignore_tags=('script', 'noscript')):
|
||||||
return _metaref_cache[response]
|
return _metaref_cache[response]
|
||||||
|
|
||||||
|
|
||||||
def response_status_message(status):
|
def response_status_message(status: Union[bytes, float, int, str]) -> str:
|
||||||
"""Return status code plus status text descriptive message
|
"""Return status code plus status text descriptive message
|
||||||
"""
|
"""
|
||||||
message = http.RESPONSES.get(int(status), "Unknown Status")
|
status_int = int(status)
|
||||||
return f'{status} {to_unicode(message)}'
|
message = http.RESPONSES.get(status_int, "Unknown Status")
|
||||||
|
return f'{status_int} {to_unicode(message)}'
|
||||||
|
|
||||||
|
|
||||||
def response_httprepr(response):
|
def response_httprepr(response: Response) -> bytes:
|
||||||
"""Return raw HTTP representation (as bytes) of the given response. This
|
"""Return raw HTTP representation (as bytes) of the given response. This
|
||||||
is provided only for reference, since it's not the exact stream of bytes
|
is provided only for reference, since it's not the exact stream of bytes
|
||||||
that was received (that's not exposed by Twisted).
|
that was received (that's not exposed by Twisted).
|
||||||
|
|
@ -60,7 +68,10 @@ def response_httprepr(response):
|
||||||
return b"".join(values)
|
return b"".join(values)
|
||||||
|
|
||||||
|
|
||||||
def open_in_browser(response, _openfunc=webbrowser.open):
|
def open_in_browser(
|
||||||
|
response: Union["scrapy.http.response.html.HtmlResponse", "scrapy.http.response.text.TextResponse"],
|
||||||
|
_openfunc: Callable[[str], Any] = webbrowser.open,
|
||||||
|
) -> Any:
|
||||||
"""Open the given response in a local web browser, populating the <base>
|
"""Open the given response in a local web browser, populating the <base>
|
||||||
tag for external links to work
|
tag for external links to work
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,15 @@ and no performance penalty at all when disabled (as object_ref becomes just an
|
||||||
alias to object in that case).
|
alias to object in that case).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import weakref
|
|
||||||
from time import time
|
|
||||||
from operator import itemgetter
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from operator import itemgetter
|
||||||
|
from time import time
|
||||||
|
from typing import DefaultDict
|
||||||
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
|
||||||
NoneType = type(None)
|
NoneType = type(None)
|
||||||
live_refs = defaultdict(weakref.WeakKeyDictionary)
|
live_refs: DefaultDict[type, WeakKeyDictionary] = defaultdict(WeakKeyDictionary)
|
||||||
|
|
||||||
|
|
||||||
class object_ref:
|
class object_ref:
|
||||||
|
|
|
||||||
36
setup.cfg
36
setup.cfg
|
|
@ -10,54 +10,18 @@ follow_imports = skip
|
||||||
|
|
||||||
# FIXME: remove the following sections once the issues are solved
|
# FIXME: remove the following sections once the issues are solved
|
||||||
|
|
||||||
[mypy-scrapy]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.commands]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.commands.parse]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.downloadermiddlewares.httpproxy]
|
[mypy-scrapy.downloadermiddlewares.httpproxy]
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
|
||||||
[mypy-scrapy.contracts]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.interfaces]
|
[mypy-scrapy.interfaces]
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
|
||||||
[mypy-scrapy.item]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.http.cookies]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.mail]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.pipelines.images]
|
[mypy-scrapy.pipelines.images]
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
|
||||||
[mypy-scrapy.settings.default_settings]
|
[mypy-scrapy.settings.default_settings]
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
|
||||||
[mypy-scrapy.spidermiddlewares.referer]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.utils.httpobj]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.utils.request]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.utils.response]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-scrapy.utils.trackref]
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-tests.mocks.dummydbm]
|
[mypy-tests.mocks.dummydbm]
|
||||||
ignore_errors = True
|
ignore_errors = True
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue