Загрузить файлы в «venv/Lib/site-packages/dns»
This commit is contained in:
72
venv/Lib/site-packages/dns/__init__.py
Normal file
72
venv/Lib/site-packages/dns/__init__.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2003-2007, 2009, 2011 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
"""dnspython DNS toolkit"""
|
||||
|
||||
__all__ = [
|
||||
"asyncbackend",
|
||||
"asyncquery",
|
||||
"asyncresolver",
|
||||
"btree",
|
||||
"btreezone",
|
||||
"dnssec",
|
||||
"dnssecalgs",
|
||||
"dnssectypes",
|
||||
"e164",
|
||||
"edns",
|
||||
"entropy",
|
||||
"exception",
|
||||
"flags",
|
||||
"immutable",
|
||||
"inet",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"message",
|
||||
"name",
|
||||
"namedict",
|
||||
"node",
|
||||
"opcode",
|
||||
"query",
|
||||
"quic",
|
||||
"rcode",
|
||||
"rdata",
|
||||
"rdataclass",
|
||||
"rdataset",
|
||||
"rdatatype",
|
||||
"renderer",
|
||||
"resolver",
|
||||
"reversename",
|
||||
"rrset",
|
||||
"serial",
|
||||
"set",
|
||||
"tokenizer",
|
||||
"transaction",
|
||||
"tsig",
|
||||
"tsigkeyring",
|
||||
"ttl",
|
||||
"rdtypes",
|
||||
"update",
|
||||
"version",
|
||||
"versioned",
|
||||
"wire",
|
||||
"xfr",
|
||||
"zone",
|
||||
"zonetypes",
|
||||
"zonefile",
|
||||
]
|
||||
|
||||
from dns.version import version as __version__ # noqa
|
||||
100
venv/Lib/site-packages/dns/_asyncbackend.py
Normal file
100
venv/Lib/site-packages/dns/_asyncbackend.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# This is a nullcontext for both sync and async. 3.7 has a nullcontext,
|
||||
# but it is only for sync use.
|
||||
|
||||
|
||||
class NullContext:
|
||||
def __init__(self, enter_result=None):
|
||||
self.enter_result = enter_result
|
||||
|
||||
def __enter__(self):
|
||||
return self.enter_result
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
pass
|
||||
|
||||
async def __aenter__(self):
|
||||
return self.enter_result
|
||||
|
||||
async def __aexit__(self, exc_type, exc_value, traceback):
|
||||
pass
|
||||
|
||||
|
||||
# These are declared here so backends can import them without creating
|
||||
# circular dependencies with dns.asyncbackend.
|
||||
|
||||
|
||||
class Socket: # pragma: no cover
|
||||
def __init__(self, family: int, type: int):
|
||||
self.family = family
|
||||
self.type = type
|
||||
|
||||
async def close(self):
|
||||
pass
|
||||
|
||||
async def getpeername(self):
|
||||
raise NotImplementedError
|
||||
|
||||
async def getsockname(self):
|
||||
raise NotImplementedError
|
||||
|
||||
async def getpeercert(self, timeout):
|
||||
raise NotImplementedError
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_value, traceback):
|
||||
await self.close()
|
||||
|
||||
|
||||
class DatagramSocket(Socket): # pragma: no cover
|
||||
async def sendto(self, what, destination, timeout):
|
||||
raise NotImplementedError
|
||||
|
||||
async def recvfrom(self, size, timeout):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class StreamSocket(Socket): # pragma: no cover
|
||||
async def sendall(self, what, timeout):
|
||||
raise NotImplementedError
|
||||
|
||||
async def recv(self, size, timeout):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class NullTransport:
|
||||
async def connect_tcp(self, host, port, timeout, local_address):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Backend: # pragma: no cover
|
||||
def name(self) -> str:
|
||||
return "unknown"
|
||||
|
||||
async def make_socket(
|
||||
self,
|
||||
af,
|
||||
socktype,
|
||||
proto=0,
|
||||
source=None,
|
||||
destination=None,
|
||||
timeout=None,
|
||||
ssl_context=None,
|
||||
server_hostname=None,
|
||||
):
|
||||
raise NotImplementedError
|
||||
|
||||
def datagram_connection_required(self):
|
||||
return False
|
||||
|
||||
async def sleep(self, interval):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_transport_class(self):
|
||||
raise NotImplementedError
|
||||
|
||||
async def wait_for(self, awaitable, timeout):
|
||||
raise NotImplementedError
|
||||
276
venv/Lib/site-packages/dns/_asyncio_backend.py
Normal file
276
venv/Lib/site-packages/dns/_asyncio_backend.py
Normal file
@@ -0,0 +1,276 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
"""asyncio library query support"""
|
||||
|
||||
import asyncio
|
||||
import socket
|
||||
import sys
|
||||
|
||||
import dns._asyncbackend
|
||||
import dns._features
|
||||
import dns.exception
|
||||
import dns.inet
|
||||
|
||||
_is_win32 = sys.platform == "win32"
|
||||
|
||||
|
||||
def _get_running_loop():
|
||||
try:
|
||||
return asyncio.get_running_loop()
|
||||
except AttributeError: # pragma: no cover
|
||||
return asyncio.get_event_loop()
|
||||
|
||||
|
||||
class _DatagramProtocol:
|
||||
def __init__(self):
|
||||
self.transport = None
|
||||
self.recvfrom = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
|
||||
def datagram_received(self, data, addr):
|
||||
if self.recvfrom and not self.recvfrom.done():
|
||||
self.recvfrom.set_result((data, addr))
|
||||
|
||||
def error_received(self, exc): # pragma: no cover
|
||||
if self.recvfrom and not self.recvfrom.done():
|
||||
self.recvfrom.set_exception(exc)
|
||||
|
||||
def connection_lost(self, exc):
|
||||
if self.recvfrom and not self.recvfrom.done():
|
||||
if exc is None:
|
||||
# EOF we triggered. Is there a better way to do this?
|
||||
try:
|
||||
raise EOFError("EOF")
|
||||
except EOFError as e:
|
||||
self.recvfrom.set_exception(e)
|
||||
else:
|
||||
self.recvfrom.set_exception(exc)
|
||||
|
||||
def close(self):
|
||||
if self.transport is not None:
|
||||
self.transport.close()
|
||||
|
||||
|
||||
async def _maybe_wait_for(awaitable, timeout):
|
||||
if timeout is not None:
|
||||
try:
|
||||
return await asyncio.wait_for(awaitable, timeout)
|
||||
except asyncio.TimeoutError:
|
||||
raise dns.exception.Timeout(timeout=timeout)
|
||||
else:
|
||||
return await awaitable
|
||||
|
||||
|
||||
class DatagramSocket(dns._asyncbackend.DatagramSocket):
|
||||
def __init__(self, family, transport, protocol):
|
||||
super().__init__(family, socket.SOCK_DGRAM)
|
||||
self.transport = transport
|
||||
self.protocol = protocol
|
||||
|
||||
async def sendto(self, what, destination, timeout): # pragma: no cover
|
||||
# no timeout for asyncio sendto
|
||||
self.transport.sendto(what, destination)
|
||||
return len(what)
|
||||
|
||||
async def recvfrom(self, size, timeout):
|
||||
# ignore size as there's no way I know to tell protocol about it
|
||||
done = _get_running_loop().create_future()
|
||||
try:
|
||||
assert self.protocol.recvfrom is None
|
||||
self.protocol.recvfrom = done
|
||||
await _maybe_wait_for(done, timeout)
|
||||
return done.result()
|
||||
finally:
|
||||
self.protocol.recvfrom = None
|
||||
|
||||
async def close(self):
|
||||
self.protocol.close()
|
||||
|
||||
async def getpeername(self):
|
||||
return self.transport.get_extra_info("peername")
|
||||
|
||||
async def getsockname(self):
|
||||
return self.transport.get_extra_info("sockname")
|
||||
|
||||
async def getpeercert(self, timeout):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class StreamSocket(dns._asyncbackend.StreamSocket):
|
||||
def __init__(self, af, reader, writer):
|
||||
super().__init__(af, socket.SOCK_STREAM)
|
||||
self.reader = reader
|
||||
self.writer = writer
|
||||
|
||||
async def sendall(self, what, timeout):
|
||||
self.writer.write(what)
|
||||
return await _maybe_wait_for(self.writer.drain(), timeout)
|
||||
|
||||
async def recv(self, size, timeout):
|
||||
return await _maybe_wait_for(self.reader.read(size), timeout)
|
||||
|
||||
async def close(self):
|
||||
self.writer.close()
|
||||
|
||||
async def getpeername(self):
|
||||
return self.writer.get_extra_info("peername")
|
||||
|
||||
async def getsockname(self):
|
||||
return self.writer.get_extra_info("sockname")
|
||||
|
||||
async def getpeercert(self, timeout):
|
||||
return self.writer.get_extra_info("peercert")
|
||||
|
||||
|
||||
if dns._features.have("doh"):
|
||||
import anyio
|
||||
import httpcore
|
||||
import httpcore._backends.anyio
|
||||
import httpx
|
||||
|
||||
_CoreAsyncNetworkBackend = httpcore.AsyncNetworkBackend
|
||||
_CoreAnyIOStream = httpcore._backends.anyio.AnyIOStream # pyright: ignore
|
||||
|
||||
from dns.query import _compute_times, _expiration_for_this_attempt, _remaining
|
||||
|
||||
class _NetworkBackend(_CoreAsyncNetworkBackend):
|
||||
def __init__(self, resolver, local_port, bootstrap_address, family):
|
||||
super().__init__()
|
||||
self._local_port = local_port
|
||||
self._resolver = resolver
|
||||
self._bootstrap_address = bootstrap_address
|
||||
self._family = family
|
||||
if local_port != 0:
|
||||
raise NotImplementedError(
|
||||
"the asyncio transport for HTTPX cannot set the local port"
|
||||
)
|
||||
|
||||
async def connect_tcp(
|
||||
self, host, port, timeout=None, local_address=None, socket_options=None
|
||||
): # pylint: disable=signature-differs
|
||||
addresses = []
|
||||
_, expiration = _compute_times(timeout)
|
||||
if dns.inet.is_address(host):
|
||||
addresses.append(host)
|
||||
elif self._bootstrap_address is not None:
|
||||
addresses.append(self._bootstrap_address)
|
||||
else:
|
||||
timeout = _remaining(expiration)
|
||||
family = self._family
|
||||
if local_address:
|
||||
family = dns.inet.af_for_address(local_address)
|
||||
answers = await self._resolver.resolve_name(
|
||||
host, family=family, lifetime=timeout
|
||||
)
|
||||
addresses = answers.addresses()
|
||||
for address in addresses:
|
||||
try:
|
||||
attempt_expiration = _expiration_for_this_attempt(2.0, expiration)
|
||||
timeout = _remaining(attempt_expiration)
|
||||
with anyio.fail_after(timeout):
|
||||
stream = await anyio.connect_tcp(
|
||||
remote_host=address,
|
||||
remote_port=port,
|
||||
local_host=local_address,
|
||||
)
|
||||
return _CoreAnyIOStream(stream)
|
||||
except Exception:
|
||||
pass
|
||||
raise httpcore.ConnectError
|
||||
|
||||
async def connect_unix_socket(
|
||||
self, path, timeout=None, socket_options=None
|
||||
): # pylint: disable=signature-differs
|
||||
raise NotImplementedError
|
||||
|
||||
async def sleep(self, seconds): # pylint: disable=signature-differs
|
||||
await anyio.sleep(seconds)
|
||||
|
||||
class _HTTPTransport(httpx.AsyncHTTPTransport):
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
local_port=0,
|
||||
bootstrap_address=None,
|
||||
resolver=None,
|
||||
family=socket.AF_UNSPEC,
|
||||
**kwargs,
|
||||
):
|
||||
if resolver is None and bootstrap_address is None:
|
||||
# pylint: disable=import-outside-toplevel,redefined-outer-name
|
||||
import dns.asyncresolver
|
||||
|
||||
resolver = dns.asyncresolver.Resolver()
|
||||
super().__init__(*args, **kwargs)
|
||||
self._pool._network_backend = _NetworkBackend(
|
||||
resolver, local_port, bootstrap_address, family
|
||||
)
|
||||
|
||||
else:
|
||||
_HTTPTransport = dns._asyncbackend.NullTransport # type: ignore
|
||||
|
||||
|
||||
class Backend(dns._asyncbackend.Backend):
|
||||
def name(self):
|
||||
return "asyncio"
|
||||
|
||||
async def make_socket(
|
||||
self,
|
||||
af,
|
||||
socktype,
|
||||
proto=0,
|
||||
source=None,
|
||||
destination=None,
|
||||
timeout=None,
|
||||
ssl_context=None,
|
||||
server_hostname=None,
|
||||
):
|
||||
loop = _get_running_loop()
|
||||
if socktype == socket.SOCK_DGRAM:
|
||||
if _is_win32 and source is None:
|
||||
# Win32 wants explicit binding before recvfrom(). This is the
|
||||
# proper fix for [#637].
|
||||
source = (dns.inet.any_for_af(af), 0)
|
||||
transport, protocol = await loop.create_datagram_endpoint(
|
||||
_DatagramProtocol, # pyright: ignore
|
||||
source,
|
||||
family=af,
|
||||
proto=proto,
|
||||
remote_addr=destination,
|
||||
)
|
||||
return DatagramSocket(af, transport, protocol)
|
||||
elif socktype == socket.SOCK_STREAM:
|
||||
if destination is None:
|
||||
# This shouldn't happen, but we check to make code analysis software
|
||||
# happier.
|
||||
raise ValueError("destination required for stream sockets")
|
||||
(r, w) = await _maybe_wait_for(
|
||||
asyncio.open_connection(
|
||||
destination[0],
|
||||
destination[1],
|
||||
ssl=ssl_context,
|
||||
family=af,
|
||||
proto=proto,
|
||||
local_addr=source,
|
||||
server_hostname=server_hostname,
|
||||
),
|
||||
timeout,
|
||||
)
|
||||
return StreamSocket(af, r, w)
|
||||
raise NotImplementedError(
|
||||
"unsupported socket " + f"type {socktype}"
|
||||
) # pragma: no cover
|
||||
|
||||
async def sleep(self, interval):
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
def datagram_connection_required(self):
|
||||
return False
|
||||
|
||||
def get_transport_class(self):
|
||||
return _HTTPTransport
|
||||
|
||||
async def wait_for(self, awaitable, timeout):
|
||||
return await _maybe_wait_for(awaitable, timeout)
|
||||
154
venv/Lib/site-packages/dns/_ddr.py
Normal file
154
venv/Lib/site-packages/dns/_ddr.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
#
|
||||
# Support for Discovery of Designated Resolvers
|
||||
|
||||
import socket
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import dns.asyncbackend
|
||||
import dns.inet
|
||||
import dns.name
|
||||
import dns.nameserver
|
||||
import dns.query
|
||||
import dns.rdtypes.svcbbase
|
||||
|
||||
# The special name of the local resolver when using DDR
|
||||
_local_resolver_name = dns.name.from_text("_dns.resolver.arpa")
|
||||
|
||||
|
||||
#
|
||||
# Processing is split up into I/O independent and I/O dependent parts to
|
||||
# make supporting sync and async versions easy.
|
||||
#
|
||||
|
||||
|
||||
class _SVCBInfo:
|
||||
def __init__(self, bootstrap_address, port, hostname, nameservers):
|
||||
self.bootstrap_address = bootstrap_address
|
||||
self.port = port
|
||||
self.hostname = hostname
|
||||
self.nameservers = nameservers
|
||||
|
||||
def ddr_check_certificate(self, cert):
|
||||
"""Verify that the _SVCBInfo's address is in the cert's subjectAltName (SAN)"""
|
||||
for name, value in cert["subjectAltName"]:
|
||||
if name == "IP Address" and value == self.bootstrap_address:
|
||||
return True
|
||||
return False
|
||||
|
||||
def make_tls_context(self):
|
||||
ssl = dns.query.ssl
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
return ctx
|
||||
|
||||
def ddr_tls_check_sync(self, lifetime):
|
||||
ctx = self.make_tls_context()
|
||||
expiration = time.time() + lifetime
|
||||
with socket.create_connection(
|
||||
(self.bootstrap_address, self.port), lifetime
|
||||
) as s:
|
||||
with ctx.wrap_socket(s, server_hostname=self.hostname) as ts:
|
||||
ts.settimeout(dns.query._remaining(expiration))
|
||||
ts.do_handshake()
|
||||
cert = ts.getpeercert()
|
||||
return self.ddr_check_certificate(cert)
|
||||
|
||||
async def ddr_tls_check_async(self, lifetime, backend=None):
|
||||
if backend is None:
|
||||
backend = dns.asyncbackend.get_default_backend()
|
||||
ctx = self.make_tls_context()
|
||||
expiration = time.time() + lifetime
|
||||
async with await backend.make_socket(
|
||||
dns.inet.af_for_address(self.bootstrap_address),
|
||||
socket.SOCK_STREAM,
|
||||
0,
|
||||
None,
|
||||
(self.bootstrap_address, self.port),
|
||||
lifetime,
|
||||
ctx,
|
||||
self.hostname,
|
||||
) as ts:
|
||||
cert = await ts.getpeercert(dns.query._remaining(expiration))
|
||||
return self.ddr_check_certificate(cert)
|
||||
|
||||
|
||||
def _extract_nameservers_from_svcb(answer):
|
||||
bootstrap_address = answer.nameserver
|
||||
if not dns.inet.is_address(bootstrap_address):
|
||||
return []
|
||||
infos = []
|
||||
for rr in answer.rrset.processing_order():
|
||||
nameservers = []
|
||||
param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.ALPN)
|
||||
if param is None:
|
||||
continue
|
||||
alpns = set(param.ids)
|
||||
host = rr.target.to_text(omit_final_dot=True)
|
||||
port = None
|
||||
param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.PORT)
|
||||
if param is not None:
|
||||
port = param.port
|
||||
# For now we ignore address hints and address resolution and always use the
|
||||
# bootstrap address
|
||||
if b"h2" in alpns:
|
||||
param = rr.params.get(dns.rdtypes.svcbbase.ParamKey.DOHPATH)
|
||||
if param is None or not param.value.endswith(b"{?dns}"):
|
||||
continue
|
||||
path = param.value[:-6].decode()
|
||||
if not path.startswith("/"):
|
||||
path = "/" + path
|
||||
if port is None:
|
||||
port = 443
|
||||
url = f"https://{host}:{port}{path}"
|
||||
# check the URL
|
||||
try:
|
||||
urlparse(url)
|
||||
nameservers.append(dns.nameserver.DoHNameserver(url, bootstrap_address))
|
||||
except Exception:
|
||||
# continue processing other ALPN types
|
||||
pass
|
||||
if b"dot" in alpns:
|
||||
if port is None:
|
||||
port = 853
|
||||
nameservers.append(
|
||||
dns.nameserver.DoTNameserver(bootstrap_address, port, host)
|
||||
)
|
||||
if b"doq" in alpns:
|
||||
if port is None:
|
||||
port = 853
|
||||
nameservers.append(
|
||||
dns.nameserver.DoQNameserver(bootstrap_address, port, True, host)
|
||||
)
|
||||
if len(nameservers) > 0:
|
||||
infos.append(_SVCBInfo(bootstrap_address, port, host, nameservers))
|
||||
return infos
|
||||
|
||||
|
||||
def _get_nameservers_sync(answer, lifetime):
|
||||
"""Return a list of TLS-validated resolver nameservers extracted from an SVCB
|
||||
answer."""
|
||||
nameservers = []
|
||||
infos = _extract_nameservers_from_svcb(answer)
|
||||
for info in infos:
|
||||
try:
|
||||
if info.ddr_tls_check_sync(lifetime):
|
||||
nameservers.extend(info.nameservers)
|
||||
except Exception:
|
||||
pass
|
||||
return nameservers
|
||||
|
||||
|
||||
async def _get_nameservers_async(answer, lifetime):
|
||||
"""Return a list of TLS-validated resolver nameservers extracted from an SVCB
|
||||
answer."""
|
||||
nameservers = []
|
||||
infos = _extract_nameservers_from_svcb(answer)
|
||||
for info in infos:
|
||||
try:
|
||||
if await info.ddr_tls_check_async(lifetime):
|
||||
nameservers.extend(info.nameservers)
|
||||
except Exception:
|
||||
pass
|
||||
return nameservers
|
||||
95
venv/Lib/site-packages/dns/_features.py
Normal file
95
venv/Lib/site-packages/dns/_features.py
Normal file
@@ -0,0 +1,95 @@
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import importlib.metadata
|
||||
import itertools
|
||||
import string
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
def _tuple_from_text(version: str) -> Tuple:
|
||||
text_parts = version.split(".")
|
||||
int_parts = []
|
||||
for text_part in text_parts:
|
||||
digit_prefix = "".join(
|
||||
itertools.takewhile(lambda x: x in string.digits, text_part)
|
||||
)
|
||||
try:
|
||||
int_parts.append(int(digit_prefix))
|
||||
except Exception:
|
||||
break
|
||||
return tuple(int_parts)
|
||||
|
||||
|
||||
def _version_check(
|
||||
requirement: str,
|
||||
) -> bool:
|
||||
"""Is the requirement fulfilled?
|
||||
|
||||
The requirement must be of the form
|
||||
|
||||
package>=version
|
||||
"""
|
||||
package, minimum = requirement.split(">=")
|
||||
try:
|
||||
version = importlib.metadata.version(package)
|
||||
# This shouldn't happen, but it apparently can.
|
||||
if version is None:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
t_version = _tuple_from_text(version)
|
||||
t_minimum = _tuple_from_text(minimum)
|
||||
if t_version < t_minimum:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
_cache: Dict[str, bool] = {}
|
||||
|
||||
|
||||
def have(feature: str) -> bool:
|
||||
"""Is *feature* available?
|
||||
|
||||
This tests if all optional packages needed for the
|
||||
feature are available and recent enough.
|
||||
|
||||
Returns ``True`` if the feature is available,
|
||||
and ``False`` if it is not or if metadata is
|
||||
missing.
|
||||
"""
|
||||
value = _cache.get(feature)
|
||||
if value is not None:
|
||||
return value
|
||||
requirements = _requirements.get(feature)
|
||||
if requirements is None:
|
||||
# we make a cache entry here for consistency not performance
|
||||
_cache[feature] = False
|
||||
return False
|
||||
ok = True
|
||||
for requirement in requirements:
|
||||
if not _version_check(requirement):
|
||||
ok = False
|
||||
break
|
||||
_cache[feature] = ok
|
||||
return ok
|
||||
|
||||
|
||||
def force(feature: str, enabled: bool) -> None:
|
||||
"""Force the status of *feature* to be *enabled*.
|
||||
|
||||
This method is provided as a workaround for any cases
|
||||
where importlib.metadata is ineffective, or for testing.
|
||||
"""
|
||||
_cache[feature] = enabled
|
||||
|
||||
|
||||
_requirements: Dict[str, List[str]] = {
|
||||
### BEGIN generated requirements
|
||||
"dnssec": ["cryptography>=45"],
|
||||
"doh": ["httpcore>=1.0.0", "httpx>=0.28.0", "h2>=4.2.0"],
|
||||
"doq": ["aioquic>=1.2.0"],
|
||||
"idna": ["idna>=3.10"],
|
||||
"trio": ["trio>=0.30"],
|
||||
"wmi": ["wmi>=1.5.1"],
|
||||
### END generated requirements
|
||||
}
|
||||
Reference in New Issue
Block a user