Full typing for scrapy/resolver.py.

This commit is contained in:
Andrey Rakhmatullin 2024-04-29 23:10:03 +05:00
parent 21fa076181
commit ad35ffdb0d
1 changed files with 39 additions and 28 deletions

View File

@ -1,8 +1,12 @@
from typing import Any from __future__ import annotations
from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Type
from twisted.internet import defer from twisted.internet import defer
from twisted.internet.base import ThreadedResolver from twisted.internet.base import ReactorBase, ThreadedResolver
from twisted.internet.defer import Deferred
from twisted.internet.interfaces import ( from twisted.internet.interfaces import (
IAddress,
IHostnameResolver, IHostnameResolver,
IHostResolution, IHostResolution,
IResolutionReceiver, IResolutionReceiver,
@ -12,6 +16,12 @@ from zope.interface.declarations import implementer, provider
from scrapy.utils.datatypes import LocalCache from scrapy.utils.datatypes import LocalCache
if TYPE_CHECKING:
# typing.Self requires Python 3.11
from typing_extensions import Self
from scrapy.crawler import Crawler
# TODO: cache misses # TODO: cache misses
dnscache: LocalCache[str, Any] = LocalCache(10000) dnscache: LocalCache[str, Any] = LocalCache(10000)
@ -22,65 +32,66 @@ class CachingThreadedResolver(ThreadedResolver):
Default caching resolver. IPv4 only, supports setting a timeout value for DNS requests. Default caching resolver. IPv4 only, supports setting a timeout value for DNS requests.
""" """
def __init__(self, reactor, cache_size, timeout): def __init__(self, reactor: ReactorBase, cache_size: int, timeout: float):
super().__init__(reactor) super().__init__(reactor)
dnscache.limit = cache_size dnscache.limit = cache_size
self.timeout = timeout self.timeout = timeout
@classmethod @classmethod
def from_crawler(cls, crawler, reactor): def from_crawler(cls, crawler: Crawler, reactor: ReactorBase) -> Self:
if crawler.settings.getbool("DNSCACHE_ENABLED"): if crawler.settings.getbool("DNSCACHE_ENABLED"):
cache_size = crawler.settings.getint("DNSCACHE_SIZE") cache_size = crawler.settings.getint("DNSCACHE_SIZE")
else: else:
cache_size = 0 cache_size = 0
return cls(reactor, cache_size, crawler.settings.getfloat("DNS_TIMEOUT")) return cls(reactor, cache_size, crawler.settings.getfloat("DNS_TIMEOUT"))
def install_on_reactor(self): def install_on_reactor(self) -> None:
self.reactor.installResolver(self) self.reactor.installResolver(self)
def getHostByName(self, name: str, timeout=None): def getHostByName(self, name: str, timeout: Sequence[int] = ()) -> Deferred[str]:
if name in dnscache: if name in dnscache:
return defer.succeed(dnscache[name]) return defer.succeed(dnscache[name])
# in Twisted<=16.6, getHostByName() is always called with # in Twisted<=16.6, getHostByName() is always called with
# a default timeout of 60s (actually passed as (1, 3, 11, 45) tuple), # a default timeout of 60s (actually passed as (1, 3, 11, 45) tuple),
# so the input argument above is simply overridden # so the input argument above is simply overridden
# to enforce Scrapy's DNS_TIMEOUT setting's value # to enforce Scrapy's DNS_TIMEOUT setting's value
timeout = (self.timeout,) # The timeout arg is typed as Sequence[int] but supports floats.
timeout = (self.timeout,) # type: ignore[assignment]
d = super().getHostByName(name, timeout) d = super().getHostByName(name, timeout)
if dnscache.limit: if dnscache.limit:
d.addCallback(self._cache_result, name) d.addCallback(self._cache_result, name)
return d return d
def _cache_result(self, result, name): def _cache_result(self, result: Any, name: str) -> Any:
dnscache[name] = result dnscache[name] = result
return result return result
@implementer(IHostResolution) @implementer(IHostResolution)
class HostResolution: class HostResolution:
def __init__(self, name): def __init__(self, name: str):
self.name = name self.name: str = name
def cancel(self): def cancel(self) -> None:
raise NotImplementedError() raise NotImplementedError()
@provider(IResolutionReceiver) @provider(IResolutionReceiver)
class _CachingResolutionReceiver: class _CachingResolutionReceiver:
def __init__(self, resolutionReceiver, hostName): def __init__(self, resolutionReceiver: IResolutionReceiver, hostName: str):
self.resolutionReceiver = resolutionReceiver self.resolutionReceiver: IResolutionReceiver = resolutionReceiver
self.hostName = hostName self.hostName: str = hostName
self.addresses = [] self.addresses: List[IAddress] = []
def resolutionBegan(self, resolution): def resolutionBegan(self, resolution: IHostResolution) -> None:
self.resolutionReceiver.resolutionBegan(resolution) self.resolutionReceiver.resolutionBegan(resolution)
self.resolution = resolution self.resolution = resolution
def addressResolved(self, address): def addressResolved(self, address: IAddress) -> None:
self.resolutionReceiver.addressResolved(address) self.resolutionReceiver.addressResolved(address)
self.addresses.append(address) self.addresses.append(address)
def resolutionComplete(self): def resolutionComplete(self) -> None:
self.resolutionReceiver.resolutionComplete() self.resolutionReceiver.resolutionComplete()
if self.addresses: if self.addresses:
dnscache[self.hostName] = self.addresses dnscache[self.hostName] = self.addresses
@ -93,30 +104,30 @@ class CachingHostnameResolver:
does not support setting a timeout value for DNS requests. does not support setting a timeout value for DNS requests.
""" """
def __init__(self, reactor, cache_size): def __init__(self, reactor: ReactorBase, cache_size: int):
self.reactor = reactor self.reactor: ReactorBase = reactor
self.original_resolver = reactor.nameResolver self.original_resolver: IHostnameResolver = reactor.nameResolver
dnscache.limit = cache_size dnscache.limit = cache_size
@classmethod @classmethod
def from_crawler(cls, crawler, reactor): def from_crawler(cls, crawler: Crawler, reactor: ReactorBase) -> Self:
if crawler.settings.getbool("DNSCACHE_ENABLED"): if crawler.settings.getbool("DNSCACHE_ENABLED"):
cache_size = crawler.settings.getint("DNSCACHE_SIZE") cache_size = crawler.settings.getint("DNSCACHE_SIZE")
else: else:
cache_size = 0 cache_size = 0
return cls(reactor, cache_size) return cls(reactor, cache_size)
def install_on_reactor(self): def install_on_reactor(self) -> None:
self.reactor.installNameResolver(self) self.reactor.installNameResolver(self)
def resolveHostName( def resolveHostName(
self, self,
resolutionReceiver, resolutionReceiver: IResolutionReceiver,
hostName: str, hostName: str,
portNumber=0, portNumber: int = 0,
addressTypes=None, addressTypes: Optional[Sequence[Type[IAddress]]] = None,
transportSemantics="TCP", transportSemantics: str = "TCP",
): ) -> IHostResolution:
try: try:
addresses = dnscache[hostName] addresses = dnscache[hostName]
except KeyError: except KeyError: