RAHHH
This commit is contained in:
17
backend/lib/python3.12/site-packages/hyperlink/__init__.py
Normal file
17
backend/lib/python3.12/site-packages/hyperlink/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from ._url import (
|
||||
parse,
|
||||
register_scheme,
|
||||
URL,
|
||||
EncodedURL,
|
||||
DecodedURL,
|
||||
URLParseError,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
"parse",
|
||||
"register_scheme",
|
||||
"URL",
|
||||
"EncodedURL",
|
||||
"DecodedURL",
|
||||
"URLParseError",
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
53
backend/lib/python3.12/site-packages/hyperlink/_socket.py
Normal file
53
backend/lib/python3.12/site-packages/hyperlink/_socket.py
Normal file
@@ -0,0 +1,53 @@
|
||||
try:
|
||||
from socket import inet_pton
|
||||
except ImportError:
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
# based on https://gist.github.com/nnemkin/4966028
|
||||
# this code only applies on Windows Python 2.7
|
||||
import ctypes
|
||||
import socket
|
||||
|
||||
class SockAddr(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("sa_family", ctypes.c_short),
|
||||
("__pad1", ctypes.c_ushort),
|
||||
("ipv4_addr", ctypes.c_byte * 4),
|
||||
("ipv6_addr", ctypes.c_byte * 16),
|
||||
("__pad2", ctypes.c_ulong),
|
||||
]
|
||||
|
||||
WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA
|
||||
WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA
|
||||
|
||||
def inet_pton(address_family, ip_string):
|
||||
# type: (int, str) -> bytes
|
||||
addr = SockAddr()
|
||||
ip_string_bytes = ip_string.encode("ascii")
|
||||
addr.sa_family = address_family
|
||||
addr_size = ctypes.c_int(ctypes.sizeof(addr))
|
||||
|
||||
try:
|
||||
attribute, size = {
|
||||
socket.AF_INET: ("ipv4_addr", 4),
|
||||
socket.AF_INET6: ("ipv6_addr", 16),
|
||||
}[address_family]
|
||||
except KeyError:
|
||||
raise socket.error("unknown address family")
|
||||
|
||||
if (
|
||||
WSAStringToAddressA(
|
||||
ip_string_bytes,
|
||||
address_family,
|
||||
None,
|
||||
ctypes.byref(addr),
|
||||
ctypes.byref(addr_size),
|
||||
)
|
||||
!= 0
|
||||
):
|
||||
raise socket.error(ctypes.FormatError())
|
||||
|
||||
return ctypes.string_at(getattr(addr, attribute), size)
|
||||
2448
backend/lib/python3.12/site-packages/hyperlink/_url.py
Normal file
2448
backend/lib/python3.12/site-packages/hyperlink/_url.py
Normal file
File diff suppressed because it is too large
Load Diff
321
backend/lib/python3.12/site-packages/hyperlink/hypothesis.py
Normal file
321
backend/lib/python3.12/site-packages/hyperlink/hypothesis.py
Normal file
@@ -0,0 +1,321 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Hypothesis strategies.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
import hypothesis
|
||||
|
||||
del hypothesis
|
||||
except ImportError:
|
||||
from typing import Tuple
|
||||
|
||||
__all__ = () # type: Tuple[str, ...]
|
||||
else:
|
||||
from csv import reader as csv_reader
|
||||
from os.path import dirname, join
|
||||
from string import ascii_letters, digits
|
||||
from sys import maxunicode
|
||||
from typing import (
|
||||
Callable,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Text,
|
||||
TypeVar,
|
||||
cast,
|
||||
)
|
||||
from gzip import open as open_gzip
|
||||
|
||||
from . import DecodedURL, EncodedURL
|
||||
|
||||
from hypothesis import assume
|
||||
from hypothesis.strategies import (
|
||||
composite,
|
||||
integers,
|
||||
lists,
|
||||
sampled_from,
|
||||
text,
|
||||
)
|
||||
|
||||
from idna import IDNAError, check_label, encode as idna_encode
|
||||
|
||||
__all__ = (
|
||||
"decoded_urls",
|
||||
"encoded_urls",
|
||||
"hostname_labels",
|
||||
"hostnames",
|
||||
"idna_text",
|
||||
"paths",
|
||||
"port_numbers",
|
||||
)
|
||||
|
||||
T = TypeVar("T")
|
||||
DrawCallable = Callable[[Callable[..., T]], T]
|
||||
|
||||
try:
|
||||
unichr
|
||||
except NameError: # Py3
|
||||
unichr = chr # type: Callable[[int], Text]
|
||||
|
||||
def idna_characters():
|
||||
# type: () -> Text
|
||||
"""
|
||||
Returns a string containing IDNA characters.
|
||||
"""
|
||||
global _idnaCharacters
|
||||
|
||||
if not _idnaCharacters:
|
||||
result = []
|
||||
|
||||
# Data source "IDNA Derived Properties":
|
||||
# https://www.iana.org/assignments/idna-tables-6.3.0/
|
||||
# idna-tables-6.3.0.xhtml#idna-tables-properties
|
||||
dataFileName = join(
|
||||
dirname(__file__), "idna-tables-properties.csv.gz"
|
||||
)
|
||||
with open_gzip(dataFileName) as dataFile:
|
||||
reader = csv_reader(
|
||||
(line.decode("utf-8") for line in dataFile),
|
||||
delimiter=",",
|
||||
)
|
||||
next(reader) # Skip header row
|
||||
for row in reader:
|
||||
codes, prop, description = row
|
||||
|
||||
if prop != "PVALID":
|
||||
# CONTEXTO or CONTEXTJ are also allowed, but they come
|
||||
# with rules, so we're punting on those here.
|
||||
# See: https://tools.ietf.org/html/rfc5892
|
||||
continue
|
||||
|
||||
startEnd = row[0].split("-", 1)
|
||||
if len(startEnd) == 1:
|
||||
# No end of range given; use start
|
||||
startEnd.append(startEnd[0])
|
||||
start, end = (int(i, 16) for i in startEnd)
|
||||
|
||||
for i in range(start, end + 1):
|
||||
if i > maxunicode: # Happens using Py2 on Windows
|
||||
break
|
||||
result.append(unichr(i))
|
||||
|
||||
_idnaCharacters = u"".join(result)
|
||||
|
||||
return _idnaCharacters
|
||||
|
||||
_idnaCharacters = "" # type: Text
|
||||
|
||||
@composite
|
||||
def idna_text(draw, min_size=1, max_size=None):
|
||||
# type: (DrawCallable, int, Optional[int]) -> Text
|
||||
"""
|
||||
A strategy which generates IDNA-encodable text.
|
||||
|
||||
@param min_size: The minimum number of characters in the text.
|
||||
C{None} is treated as C{0}.
|
||||
|
||||
@param max_size: The maximum number of characters in the text.
|
||||
Use C{None} for an unbounded size.
|
||||
"""
|
||||
alphabet = idna_characters()
|
||||
|
||||
assert min_size >= 1
|
||||
|
||||
if max_size is not None:
|
||||
assert max_size >= 1
|
||||
|
||||
result = cast(
|
||||
Text,
|
||||
draw(text(min_size=min_size, max_size=max_size, alphabet=alphabet)),
|
||||
)
|
||||
|
||||
# FIXME: There should be a more efficient way to ensure we produce
|
||||
# valid IDNA text.
|
||||
try:
|
||||
idna_encode(result)
|
||||
except IDNAError:
|
||||
assume(False)
|
||||
|
||||
return result
|
||||
|
||||
@composite
|
||||
def port_numbers(draw, allow_zero=False):
|
||||
# type: (DrawCallable, bool) -> int
|
||||
"""
|
||||
A strategy which generates port numbers.
|
||||
|
||||
@param allow_zero: Whether to allow port C{0} as a possible value.
|
||||
"""
|
||||
if allow_zero:
|
||||
min_value = 0
|
||||
else:
|
||||
min_value = 1
|
||||
|
||||
return cast(int, draw(integers(min_value=min_value, max_value=65535)))
|
||||
|
||||
@composite
|
||||
def hostname_labels(draw, allow_idn=True):
|
||||
# type: (DrawCallable, bool) -> Text
|
||||
"""
|
||||
A strategy which generates host name labels.
|
||||
|
||||
@param allow_idn: Whether to allow non-ASCII characters as allowed by
|
||||
internationalized domain names (IDNs).
|
||||
"""
|
||||
if allow_idn:
|
||||
label = cast(Text, draw(idna_text(min_size=1, max_size=63)))
|
||||
|
||||
try:
|
||||
label.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
# If the label doesn't encode to ASCII, then we need to check
|
||||
# the length of the label after encoding to punycode and adding
|
||||
# the xn-- prefix.
|
||||
while len(label.encode("punycode")) > 63 - len("xn--"):
|
||||
# Rather than bombing out, just trim from the end until it
|
||||
# is short enough, so hypothesis doesn't have to generate
|
||||
# new data.
|
||||
label = label[:-1]
|
||||
|
||||
else:
|
||||
label = cast(
|
||||
Text,
|
||||
draw(
|
||||
text(
|
||||
min_size=1,
|
||||
max_size=63,
|
||||
alphabet=Text(ascii_letters + digits + u"-"),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
# Filter invalid labels.
|
||||
# It would be better to reliably avoid generation of bogus labels in
|
||||
# the first place, but it's hard...
|
||||
try:
|
||||
check_label(label)
|
||||
except UnicodeError: # pragma: no cover (not always drawn)
|
||||
assume(False)
|
||||
|
||||
return label
|
||||
|
||||
@composite
|
||||
def hostnames(draw, allow_leading_digit=True, allow_idn=True):
|
||||
# type: (DrawCallable, bool, bool) -> Text
|
||||
"""
|
||||
A strategy which generates host names.
|
||||
|
||||
@param allow_leading_digit: Whether to allow a leading digit in host
|
||||
names; they were not allowed prior to RFC 1123.
|
||||
|
||||
@param allow_idn: Whether to allow non-ASCII characters as allowed by
|
||||
internationalized domain names (IDNs).
|
||||
"""
|
||||
# Draw first label, filtering out labels with leading digits if needed
|
||||
labels = [
|
||||
cast(
|
||||
Text,
|
||||
draw(
|
||||
hostname_labels(allow_idn=allow_idn).filter(
|
||||
lambda l: (
|
||||
True if allow_leading_digit else l[0] not in digits
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
]
|
||||
# Draw remaining labels
|
||||
labels += cast(
|
||||
List[Text],
|
||||
draw(
|
||||
lists(
|
||||
hostname_labels(allow_idn=allow_idn),
|
||||
min_size=1,
|
||||
max_size=4,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
# Trim off labels until the total host name length fits in 252
|
||||
# characters. This avoids having to filter the data.
|
||||
while sum(len(label) for label in labels) + len(labels) - 1 > 252:
|
||||
labels = labels[:-1]
|
||||
|
||||
return u".".join(labels)
|
||||
|
||||
def path_characters():
|
||||
# type: () -> str
|
||||
"""
|
||||
Returns a string containing valid URL path characters.
|
||||
"""
|
||||
global _path_characters
|
||||
|
||||
if _path_characters is None:
|
||||
|
||||
def chars():
|
||||
# type: () -> Iterable[Text]
|
||||
for i in range(maxunicode):
|
||||
c = unichr(i)
|
||||
|
||||
# Exclude reserved characters
|
||||
if c in "#/?":
|
||||
continue
|
||||
|
||||
# Exclude anything not UTF-8 compatible
|
||||
try:
|
||||
c.encode("utf-8")
|
||||
except UnicodeEncodeError:
|
||||
continue
|
||||
|
||||
yield c
|
||||
|
||||
_path_characters = "".join(chars())
|
||||
|
||||
return _path_characters
|
||||
|
||||
_path_characters = None # type: Optional[str]
|
||||
|
||||
@composite
|
||||
def paths(draw):
|
||||
# type: (DrawCallable) -> Sequence[Text]
|
||||
return cast(
|
||||
List[Text],
|
||||
draw(
|
||||
lists(text(min_size=1, alphabet=path_characters()), max_size=10)
|
||||
),
|
||||
)
|
||||
|
||||
@composite
|
||||
def encoded_urls(draw):
|
||||
# type: (DrawCallable) -> EncodedURL
|
||||
"""
|
||||
A strategy which generates L{EncodedURL}s.
|
||||
Call the L{EncodedURL.to_uri} method on each URL to get an HTTP
|
||||
protocol-friendly URI.
|
||||
"""
|
||||
port = cast(Optional[int], draw(port_numbers(allow_zero=True)))
|
||||
host = cast(Text, draw(hostnames()))
|
||||
path = cast(Sequence[Text], draw(paths()))
|
||||
|
||||
if port == 0:
|
||||
port = None
|
||||
|
||||
return EncodedURL(
|
||||
scheme=cast(Text, draw(sampled_from((u"http", u"https")))),
|
||||
host=host,
|
||||
port=port,
|
||||
path=path,
|
||||
)
|
||||
|
||||
@composite
|
||||
def decoded_urls(draw):
|
||||
# type: (DrawCallable) -> DecodedURL
|
||||
"""
|
||||
A strategy which generates L{DecodedURL}s.
|
||||
Call the L{EncodedURL.to_uri} method on each URL to get an HTTP
|
||||
protocol-friendly URI.
|
||||
"""
|
||||
return DecodedURL(draw(encoded_urls()))
|
||||
Binary file not shown.
1
backend/lib/python3.12/site-packages/hyperlink/py.typed
Normal file
1
backend/lib/python3.12/site-packages/hyperlink/py.typed
Normal file
@@ -0,0 +1 @@
|
||||
# See: https://www.python.org/dev/peps/pep-0561/
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for hyperlink
|
||||
"""
|
||||
|
||||
__all = ()
|
||||
|
||||
|
||||
def _init_hypothesis():
|
||||
# type: () -> None
|
||||
from os import environ
|
||||
|
||||
if "CI" in environ:
|
||||
try:
|
||||
from hypothesis import HealthCheck, settings
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
settings.register_profile(
|
||||
"patience",
|
||||
settings(
|
||||
suppress_health_check=[
|
||||
HealthCheck.too_slow,
|
||||
HealthCheck.filter_too_much,
|
||||
]
|
||||
),
|
||||
)
|
||||
settings.load_profile("patience")
|
||||
|
||||
|
||||
_init_hypothesis()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,68 @@
|
||||
from typing import Any, Callable, Optional, Type
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class HyperlinkTestCase(TestCase):
|
||||
"""This type mostly exists to provide a backwards-compatible
|
||||
assertRaises method for Python 2.6 testing.
|
||||
"""
|
||||
|
||||
def assertRaises( # type: ignore[override]
|
||||
self,
|
||||
expected_exception, # type: Type[BaseException]
|
||||
callableObj=None, # type: Optional[Callable[..., Any]]
|
||||
*args, # type: Any
|
||||
**kwargs # type: Any
|
||||
):
|
||||
# type: (...) -> Any
|
||||
"""Fail unless an exception of class expected_exception is raised
|
||||
by callableObj when invoked with arguments args and keyword
|
||||
arguments kwargs. If a different type of exception is
|
||||
raised, it will not be caught, and the test case will be
|
||||
deemed to have suffered an error, exactly as for an
|
||||
unexpected exception.
|
||||
|
||||
If called with callableObj omitted or None, will return a
|
||||
context object used like this::
|
||||
|
||||
with self.assertRaises(SomeException):
|
||||
do_something()
|
||||
|
||||
The context manager keeps a reference to the exception as
|
||||
the 'exception' attribute. This allows you to inspect the
|
||||
exception after the assertion::
|
||||
|
||||
with self.assertRaises(SomeException) as cm:
|
||||
do_something()
|
||||
the_exception = cm.exception
|
||||
self.assertEqual(the_exception.error_code, 3)
|
||||
"""
|
||||
context = _AssertRaisesContext(expected_exception, self)
|
||||
if callableObj is None:
|
||||
return context
|
||||
with context:
|
||||
callableObj(*args, **kwargs)
|
||||
|
||||
|
||||
class _AssertRaisesContext(object):
|
||||
"A context manager used to implement HyperlinkTestCase.assertRaises."
|
||||
|
||||
def __init__(self, expected, test_case):
|
||||
# type: (Type[BaseException], TestCase) -> None
|
||||
self.expected = expected
|
||||
self.failureException = test_case.failureException
|
||||
|
||||
def __enter__(self):
|
||||
# type: () -> "_AssertRaisesContext"
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
# type: (Optional[Type[BaseException]], Any, Any) -> bool
|
||||
if exc_type is None:
|
||||
exc_name = self.expected.__name__
|
||||
raise self.failureException("%s not raised" % (exc_name,))
|
||||
if not issubclass(exc_type, self.expected):
|
||||
# let unexpected exceptions pass through
|
||||
return False
|
||||
self.exception = exc_value # store for later retrieval
|
||||
return True
|
||||
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Tests for hyperlink.test.common
|
||||
"""
|
||||
from typing import Any
|
||||
from unittest import TestCase
|
||||
from .common import HyperlinkTestCase
|
||||
|
||||
|
||||
class _ExpectedException(Exception):
|
||||
"""An exception used to test HyperlinkTestCase.assertRaises."""
|
||||
|
||||
|
||||
class _UnexpectedException(Exception):
|
||||
"""An exception used to test HyperlinkTestCase.assertRaises."""
|
||||
|
||||
|
||||
class TestHyperlink(TestCase):
|
||||
"""Tests for HyperlinkTestCase"""
|
||||
|
||||
def setUp(self):
|
||||
# type: () -> None
|
||||
self.hyperlink_test = HyperlinkTestCase("run")
|
||||
|
||||
def test_assertRaisesWithCallable(self):
|
||||
# type: () -> None
|
||||
"""HyperlinkTestCase.assertRaises does not raise an AssertionError
|
||||
when given a callable that, when called with the provided
|
||||
arguments, raises the expected exception.
|
||||
|
||||
"""
|
||||
called_with = []
|
||||
|
||||
def raisesExpected(*args, **kwargs):
|
||||
# type: (Any, Any) -> None
|
||||
called_with.append((args, kwargs))
|
||||
raise _ExpectedException
|
||||
|
||||
self.hyperlink_test.assertRaises(
|
||||
_ExpectedException, raisesExpected, 1, keyword=True
|
||||
)
|
||||
self.assertEqual(called_with, [((1,), {"keyword": True})])
|
||||
|
||||
def test_assertRaisesWithCallableUnexpectedException(self):
|
||||
# type: () -> None
|
||||
"""When given a callable that raises an unexpected exception,
|
||||
HyperlinkTestCase.assertRaises raises that exception.
|
||||
|
||||
"""
|
||||
|
||||
def doesNotRaiseExpected(*args, **kwargs):
|
||||
# type: (Any, Any) -> None
|
||||
raise _UnexpectedException
|
||||
|
||||
try:
|
||||
self.hyperlink_test.assertRaises(
|
||||
_ExpectedException, doesNotRaiseExpected
|
||||
)
|
||||
except _UnexpectedException:
|
||||
pass
|
||||
|
||||
def test_assertRaisesWithCallableDoesNotRaise(self):
|
||||
# type: () -> None
|
||||
"""HyperlinkTestCase.assertRaises raises an AssertionError when given
|
||||
a callable that, when called, does not raise any exception.
|
||||
|
||||
"""
|
||||
|
||||
def doesNotRaise(*args, **kwargs):
|
||||
# type: (Any, Any) -> None
|
||||
pass
|
||||
|
||||
try:
|
||||
self.hyperlink_test.assertRaises(_ExpectedException, doesNotRaise)
|
||||
except AssertionError:
|
||||
pass
|
||||
|
||||
def test_assertRaisesContextManager(self):
|
||||
# type: () -> None
|
||||
"""HyperlinkTestCase.assertRaises does not raise an AssertionError
|
||||
when used as a context manager with a suite that raises the
|
||||
expected exception. The context manager stores the exception
|
||||
instance under its `exception` instance variable.
|
||||
|
||||
"""
|
||||
with self.hyperlink_test.assertRaises(_ExpectedException) as cm:
|
||||
raise _ExpectedException
|
||||
|
||||
self.assertTrue( # type: ignore[unreachable]
|
||||
isinstance(cm.exception, _ExpectedException)
|
||||
)
|
||||
|
||||
def test_assertRaisesContextManagerUnexpectedException(self):
|
||||
# type: () -> None
|
||||
"""When used as a context manager with a block that raises an
|
||||
unexpected exception, HyperlinkTestCase.assertRaises raises
|
||||
that unexpected exception.
|
||||
|
||||
"""
|
||||
try:
|
||||
with self.hyperlink_test.assertRaises(_ExpectedException):
|
||||
raise _UnexpectedException
|
||||
except _UnexpectedException:
|
||||
pass
|
||||
|
||||
def test_assertRaisesContextManagerDoesNotRaise(self):
|
||||
# type: () -> None
|
||||
"""HyperlinkTestcase.assertRaises raises an AssertionError when used
|
||||
as a context manager with a block that does not raise any
|
||||
exception.
|
||||
|
||||
"""
|
||||
try:
|
||||
with self.hyperlink_test.assertRaises(_ExpectedException):
|
||||
pass
|
||||
except AssertionError:
|
||||
pass
|
||||
@@ -0,0 +1,228 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from typing import Dict, Union
|
||||
from .. import DecodedURL, URL
|
||||
from .._url import _percent_decode
|
||||
from .common import HyperlinkTestCase
|
||||
|
||||
BASIC_URL = "http://example.com/#"
|
||||
TOTAL_URL = (
|
||||
"https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080/"
|
||||
"a/nice%20nice/./path/?zot=23%25&zut#frég"
|
||||
)
|
||||
|
||||
|
||||
class TestURL(HyperlinkTestCase):
|
||||
def test_durl_basic(self):
|
||||
# type: () -> None
|
||||
bdurl = DecodedURL.from_text(BASIC_URL)
|
||||
assert bdurl.scheme == "http"
|
||||
assert bdurl.host == "example.com"
|
||||
assert bdurl.port == 80
|
||||
assert bdurl.path == ("",)
|
||||
assert bdurl.fragment == ""
|
||||
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
|
||||
assert durl.scheme == "https"
|
||||
assert durl.host == "bücher.ch"
|
||||
assert durl.port == 8080
|
||||
assert durl.path == ("a", "nice nice", ".", "path", "")
|
||||
assert durl.fragment == "frég"
|
||||
assert durl.get("zot") == ["23%"]
|
||||
|
||||
assert durl.user == "user"
|
||||
assert durl.userinfo == ("user", "\0\0\0\0")
|
||||
|
||||
def test_passthroughs(self):
|
||||
# type: () -> None
|
||||
|
||||
# just basic tests for the methods that more or less pass straight
|
||||
# through to the underlying URL
|
||||
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
assert durl.sibling("te%t").path[-1] == "te%t"
|
||||
assert durl.child("../test2%").path[-1] == "../test2%"
|
||||
assert durl.child() == durl
|
||||
assert durl.child() is durl
|
||||
assert durl.click("/").path[-1] == ""
|
||||
assert durl.user == "user"
|
||||
|
||||
assert "." in durl.path
|
||||
assert "." not in durl.normalize().path
|
||||
|
||||
assert durl.to_uri().fragment == "fr%C3%A9g"
|
||||
assert " " in durl.to_iri().path[1]
|
||||
|
||||
assert durl.to_text(with_password=True) == TOTAL_URL
|
||||
|
||||
assert durl.absolute
|
||||
assert durl.rooted
|
||||
|
||||
assert durl == durl.encoded_url.get_decoded_url()
|
||||
|
||||
durl2 = DecodedURL.from_text(TOTAL_URL, lazy=True)
|
||||
assert durl2 == durl2.encoded_url.get_decoded_url(lazy=True)
|
||||
|
||||
assert (
|
||||
str(DecodedURL.from_text(BASIC_URL).child(" "))
|
||||
== "http://example.com/%20"
|
||||
)
|
||||
|
||||
assert not (durl == 1)
|
||||
assert durl != 1
|
||||
|
||||
def test_repr(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
assert repr(durl) == "DecodedURL(url=" + repr(durl._url) + ")"
|
||||
|
||||
def test_query_manipulation(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
|
||||
assert durl.get("zot") == ["23%"]
|
||||
durl = durl.add(" ", "space")
|
||||
assert durl.get(" ") == ["space"]
|
||||
durl = durl.set(" ", "spa%ed")
|
||||
assert durl.get(" ") == ["spa%ed"]
|
||||
|
||||
durl = DecodedURL(url=durl.to_uri())
|
||||
assert durl.get(" ") == ["spa%ed"]
|
||||
durl = durl.remove(" ")
|
||||
assert durl.get(" ") == []
|
||||
|
||||
durl = DecodedURL.from_text("/?%61rg=b&arg=c")
|
||||
assert durl.get("arg") == ["b", "c"]
|
||||
|
||||
assert durl.set("arg", "d").get("arg") == ["d"]
|
||||
|
||||
durl = DecodedURL.from_text(
|
||||
"https://example.com/a/b/?fóó=1&bar=2&fóó=3"
|
||||
)
|
||||
assert durl.remove("fóó") == DecodedURL.from_text(
|
||||
"https://example.com/a/b/?bar=2"
|
||||
)
|
||||
assert durl.remove("fóó", value="1") == DecodedURL.from_text(
|
||||
"https://example.com/a/b/?bar=2&fóó=3"
|
||||
)
|
||||
assert durl.remove("fóó", limit=1) == DecodedURL.from_text(
|
||||
"https://example.com/a/b/?bar=2&fóó=3"
|
||||
)
|
||||
assert durl.remove("fóó", value="1", limit=0) == DecodedURL.from_text(
|
||||
"https://example.com/a/b/?fóó=1&bar=2&fóó=3"
|
||||
)
|
||||
|
||||
def test_equality_and_hashability(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
durl2 = DecodedURL.from_text(TOTAL_URL)
|
||||
burl = DecodedURL.from_text(BASIC_URL)
|
||||
durl_uri = durl.to_uri()
|
||||
|
||||
assert durl == durl
|
||||
assert durl == durl2
|
||||
assert durl != burl
|
||||
assert durl is not None
|
||||
assert durl != durl._url
|
||||
|
||||
AnyURL = Union[URL, DecodedURL]
|
||||
|
||||
durl_map = {} # type: Dict[AnyURL, AnyURL]
|
||||
durl_map[durl] = durl
|
||||
durl_map[durl2] = durl2
|
||||
|
||||
assert len(durl_map) == 1
|
||||
|
||||
durl_map[burl] = burl
|
||||
|
||||
assert len(durl_map) == 2
|
||||
|
||||
durl_map[durl_uri] = durl_uri
|
||||
|
||||
assert len(durl_map) == 3
|
||||
|
||||
def test_replace_roundtrip(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
|
||||
durl2 = durl.replace(
|
||||
scheme=durl.scheme,
|
||||
host=durl.host,
|
||||
path=durl.path,
|
||||
query=durl.query,
|
||||
fragment=durl.fragment,
|
||||
port=durl.port,
|
||||
rooted=durl.rooted,
|
||||
userinfo=durl.userinfo,
|
||||
uses_netloc=durl.uses_netloc,
|
||||
)
|
||||
|
||||
assert durl == durl2
|
||||
|
||||
def test_replace_userinfo(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
with self.assertRaises(ValueError):
|
||||
durl.replace(
|
||||
userinfo=( # type: ignore[arg-type]
|
||||
"user",
|
||||
"pw",
|
||||
"thiswillcauseafailure",
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
def test_twisted_compat(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
|
||||
assert durl == DecodedURL.fromText(TOTAL_URL)
|
||||
assert "to_text" in dir(durl)
|
||||
assert "asText" not in dir(durl)
|
||||
assert durl.to_text() == durl.asText()
|
||||
|
||||
def test_percent_decode_mixed(self):
|
||||
# type: () -> None
|
||||
|
||||
# See https://github.com/python-hyper/hyperlink/pull/59 for a
|
||||
# nice discussion of the possibilities
|
||||
assert _percent_decode("abcdé%C3%A9éfg") == "abcdéééfg"
|
||||
|
||||
# still allow percent encoding in the case of an error
|
||||
assert _percent_decode("abcdé%C3éfg") == "abcdé%C3éfg"
|
||||
|
||||
# ...unless explicitly told otherwise
|
||||
with self.assertRaises(UnicodeDecodeError):
|
||||
_percent_decode("abcdé%C3éfg", raise_subencoding_exc=True)
|
||||
|
||||
# when not encodable as subencoding
|
||||
assert _percent_decode("é%25é", subencoding="ascii") == "é%25é"
|
||||
|
||||
def test_click_decoded_url(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text(TOTAL_URL)
|
||||
durl_dest = DecodedURL.from_text("/tëst")
|
||||
|
||||
clicked = durl.click(durl_dest)
|
||||
assert clicked.host == durl.host
|
||||
assert clicked.path == durl_dest.path
|
||||
assert clicked.path == ("tëst",)
|
||||
|
||||
def test_decode_plus(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text("/x+y%2B?a=b+c%2B")
|
||||
assert durl.path == ("x+y+",)
|
||||
assert durl.get("a") == ["b c+"]
|
||||
assert durl.query == (("a", "b c+"),)
|
||||
|
||||
def test_decode_nonplussed(self):
|
||||
# type: () -> None
|
||||
durl = DecodedURL.from_text(
|
||||
"/x+y%2B?a=b+c%2B", query_plus_is_space=False
|
||||
)
|
||||
assert durl.path == ("x+y+",)
|
||||
assert durl.get("a") == ["b+c+"]
|
||||
assert durl.query == (("a", "b+c+"),)
|
||||
@@ -0,0 +1,214 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for hyperlink.hypothesis.
|
||||
"""
|
||||
|
||||
try:
|
||||
import hypothesis
|
||||
|
||||
del hypothesis
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
from string import digits
|
||||
from typing import Sequence, Text
|
||||
|
||||
try:
|
||||
from unittest.mock import patch
|
||||
except ImportError:
|
||||
from mock import patch # type: ignore[misc]
|
||||
|
||||
from hypothesis import given, settings
|
||||
from hypothesis.strategies import SearchStrategy, data
|
||||
|
||||
from idna import IDNAError, check_label, encode as idna_encode
|
||||
|
||||
from .common import HyperlinkTestCase
|
||||
from .. import DecodedURL, EncodedURL
|
||||
from ..hypothesis import (
|
||||
DrawCallable,
|
||||
composite,
|
||||
decoded_urls,
|
||||
encoded_urls,
|
||||
hostname_labels,
|
||||
hostnames,
|
||||
idna_text,
|
||||
paths,
|
||||
port_numbers,
|
||||
)
|
||||
|
||||
class TestHypothesisStrategies(HyperlinkTestCase):
|
||||
"""
|
||||
Tests for hyperlink.hypothesis.
|
||||
"""
|
||||
|
||||
@given(idna_text())
|
||||
def test_idna_text_valid(self, text):
|
||||
# type: (Text) -> None
|
||||
"""
|
||||
idna_text() generates IDNA-encodable text.
|
||||
"""
|
||||
try:
|
||||
idna_encode(text)
|
||||
except IDNAError: # pragma: no cover
|
||||
raise AssertionError("Invalid IDNA text: {!r}".format(text))
|
||||
|
||||
@given(data())
|
||||
def test_idna_text_min_max(self, data):
|
||||
# type: (SearchStrategy) -> None
|
||||
"""
|
||||
idna_text() raises AssertionError if min_size is < 1.
|
||||
"""
|
||||
self.assertRaises(AssertionError, data.draw, idna_text(min_size=0))
|
||||
self.assertRaises(AssertionError, data.draw, idna_text(max_size=0))
|
||||
|
||||
@given(port_numbers())
|
||||
def test_port_numbers_bounds(self, port):
|
||||
# type: (int) -> None
|
||||
"""
|
||||
port_numbers() generates integers between 1 and 65535, inclusive.
|
||||
"""
|
||||
self.assertGreaterEqual(port, 1)
|
||||
self.assertLessEqual(port, 65535)
|
||||
|
||||
@given(port_numbers(allow_zero=True))
|
||||
def test_port_numbers_bounds_allow_zero(self, port):
|
||||
# type: (int) -> None
|
||||
"""
|
||||
port_numbers(allow_zero=True) generates integers between 0 and
|
||||
65535, inclusive.
|
||||
"""
|
||||
self.assertGreaterEqual(port, 0)
|
||||
self.assertLessEqual(port, 65535)
|
||||
|
||||
@given(hostname_labels())
|
||||
def test_hostname_labels_valid_idn(self, label):
|
||||
# type: (Text) -> None
|
||||
"""
|
||||
hostname_labels() generates IDN host name labels.
|
||||
"""
|
||||
try:
|
||||
check_label(label)
|
||||
idna_encode(label)
|
||||
except UnicodeError: # pragma: no cover
|
||||
raise AssertionError("Invalid IDN label: {!r}".format(label))
|
||||
|
||||
@given(data())
|
||||
@settings(max_examples=10)
|
||||
def test_hostname_labels_long_idn_punycode(self, data):
|
||||
# type: (SearchStrategy) -> None
|
||||
"""
|
||||
hostname_labels() handles case where idna_text() generates text
|
||||
that encoded to punycode ends up as longer than allowed.
|
||||
"""
|
||||
|
||||
@composite
|
||||
def mock_idna_text(draw, min_size, max_size):
|
||||
# type: (DrawCallable, int, int) -> Text
|
||||
# We want a string that does not exceed max_size, but when
|
||||
# encoded to punycode, does exceed max_size.
|
||||
# So use a unicode character that is larger when encoded,
|
||||
# "á" being a great example, and use it max_size times, which
|
||||
# will be max_size * 3 in size when encoded.
|
||||
return u"\N{LATIN SMALL LETTER A WITH ACUTE}" * max_size
|
||||
|
||||
with patch("hyperlink.hypothesis.idna_text", mock_idna_text):
|
||||
label = data.draw(hostname_labels())
|
||||
try:
|
||||
check_label(label)
|
||||
idna_encode(label)
|
||||
except UnicodeError: # pragma: no cover
|
||||
raise AssertionError(
|
||||
"Invalid IDN label: {!r}".format(label)
|
||||
)
|
||||
|
||||
@given(hostname_labels(allow_idn=False))
|
||||
def test_hostname_labels_valid_ascii(self, label):
|
||||
# type: (Text) -> None
|
||||
"""
|
||||
hostname_labels() generates a ASCII host name labels.
|
||||
"""
|
||||
try:
|
||||
check_label(label)
|
||||
label.encode("ascii")
|
||||
except UnicodeError: # pragma: no cover
|
||||
raise AssertionError("Invalid ASCII label: {!r}".format(label))
|
||||
|
||||
@given(hostnames())
|
||||
def test_hostnames_idn(self, hostname):
|
||||
# type: (Text) -> None
|
||||
"""
|
||||
hostnames() generates a IDN host names.
|
||||
"""
|
||||
try:
|
||||
for label in hostname.split(u"."):
|
||||
check_label(label)
|
||||
idna_encode(hostname)
|
||||
except UnicodeError: # pragma: no cover
|
||||
raise AssertionError(
|
||||
"Invalid IDN host name: {!r}".format(hostname)
|
||||
)
|
||||
|
||||
@given(hostnames(allow_leading_digit=False))
|
||||
def test_hostnames_idn_nolead(self, hostname):
|
||||
# type: (Text) -> None
|
||||
"""
|
||||
hostnames(allow_leading_digit=False) generates a IDN host names
|
||||
without leading digits.
|
||||
"""
|
||||
self.assertTrue(hostname == hostname.lstrip(digits))
|
||||
|
||||
@given(hostnames(allow_idn=False))
|
||||
def test_hostnames_ascii(self, hostname):
|
||||
# type: (Text) -> None
|
||||
"""
|
||||
hostnames() generates a ASCII host names.
|
||||
"""
|
||||
try:
|
||||
for label in hostname.split(u"."):
|
||||
check_label(label)
|
||||
hostname.encode("ascii")
|
||||
except UnicodeError: # pragma: no cover
|
||||
raise AssertionError(
|
||||
"Invalid ASCII host name: {!r}".format(hostname)
|
||||
)
|
||||
|
||||
@given(hostnames(allow_leading_digit=False, allow_idn=False))
|
||||
def test_hostnames_ascii_nolead(self, hostname):
|
||||
# type: (Text) -> None
|
||||
"""
|
||||
hostnames(allow_leading_digit=False, allow_idn=False) generates
|
||||
ASCII host names without leading digits.
|
||||
"""
|
||||
self.assertTrue(hostname == hostname.lstrip(digits))
|
||||
|
||||
@given(paths())
|
||||
def test_paths(self, path):
|
||||
# type: (Sequence[Text]) -> None
|
||||
"""
|
||||
paths() generates sequences of URL path components.
|
||||
"""
|
||||
text = u"/".join(path)
|
||||
try:
|
||||
text.encode("utf-8")
|
||||
except UnicodeError: # pragma: no cover
|
||||
raise AssertionError("Invalid URL path: {!r}".format(path))
|
||||
|
||||
for segment in path:
|
||||
self.assertNotIn("#/?", segment)
|
||||
|
||||
@given(encoded_urls())
|
||||
def test_encoded_urls(self, url):
|
||||
# type: (EncodedURL) -> None
|
||||
"""
|
||||
encoded_urls() generates EncodedURLs.
|
||||
"""
|
||||
self.assertIsInstance(url, EncodedURL)
|
||||
|
||||
@given(decoded_urls())
|
||||
def test_decoded_urls(self, url):
|
||||
# type: (DecodedURL) -> None
|
||||
"""
|
||||
decoded_urls() generates DecodedURLs.
|
||||
"""
|
||||
self.assertIsInstance(url, DecodedURL)
|
||||
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import HyperlinkTestCase
|
||||
from hyperlink import parse, EncodedURL, DecodedURL
|
||||
|
||||
BASIC_URL = "http://example.com/#"
|
||||
TOTAL_URL = (
|
||||
"https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080"
|
||||
"/a/nice%20nice/./path/?zot=23%25&zut#frég"
|
||||
)
|
||||
UNDECODABLE_FRAG_URL = TOTAL_URL + "%C3"
|
||||
# the %C3 above percent-decodes to an unpaired \xc3 byte which makes this
|
||||
# invalid utf8
|
||||
|
||||
|
||||
class TestURL(HyperlinkTestCase):
|
||||
def test_parse(self):
|
||||
# type: () -> None
|
||||
purl = parse(TOTAL_URL)
|
||||
assert isinstance(purl, DecodedURL)
|
||||
assert purl.user == "user"
|
||||
assert purl.get("zot") == ["23%"]
|
||||
assert purl.fragment == "frég"
|
||||
|
||||
purl2 = parse(TOTAL_URL, decoded=False)
|
||||
assert isinstance(purl2, EncodedURL)
|
||||
assert purl2.get("zot") == ["23%25"]
|
||||
|
||||
with self.assertRaises(UnicodeDecodeError):
|
||||
purl3 = parse(UNDECODABLE_FRAG_URL)
|
||||
|
||||
purl3 = parse(UNDECODABLE_FRAG_URL, lazy=True)
|
||||
|
||||
with self.assertRaises(UnicodeDecodeError):
|
||||
purl3.fragment
|
||||
@@ -0,0 +1,82 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from typing import cast
|
||||
|
||||
|
||||
from .. import _url
|
||||
from .common import HyperlinkTestCase
|
||||
from .._url import register_scheme, URL, DecodedURL
|
||||
|
||||
|
||||
class TestSchemeRegistration(HyperlinkTestCase):
|
||||
def setUp(self):
|
||||
# type: () -> None
|
||||
self._orig_scheme_port_map = dict(_url.SCHEME_PORT_MAP)
|
||||
self._orig_no_netloc_schemes = set(_url.NO_NETLOC_SCHEMES)
|
||||
|
||||
def tearDown(self):
|
||||
# type: () -> None
|
||||
_url.SCHEME_PORT_MAP = self._orig_scheme_port_map
|
||||
_url.NO_NETLOC_SCHEMES = self._orig_no_netloc_schemes
|
||||
|
||||
def test_register_scheme_basic(self):
|
||||
# type: () -> None
|
||||
register_scheme("deltron", uses_netloc=True, default_port=3030)
|
||||
|
||||
u1 = URL.from_text("deltron://example.com")
|
||||
assert u1.scheme == "deltron"
|
||||
assert u1.port == 3030
|
||||
assert u1.uses_netloc is True
|
||||
|
||||
# test netloc works even when the original gives no indication
|
||||
u2 = URL.from_text("deltron:")
|
||||
u2 = u2.replace(host="example.com")
|
||||
assert u2.to_text() == "deltron://example.com"
|
||||
|
||||
# test default port means no emission
|
||||
u3 = URL.from_text("deltron://example.com:3030")
|
||||
assert u3.to_text() == "deltron://example.com"
|
||||
|
||||
register_scheme("nonetron", default_port=3031)
|
||||
u4 = URL(scheme="nonetron")
|
||||
u4 = u4.replace(host="example.com")
|
||||
assert u4.to_text() == "nonetron://example.com"
|
||||
|
||||
def test_register_no_netloc_scheme(self):
|
||||
# type: () -> None
|
||||
register_scheme("noloctron", uses_netloc=False)
|
||||
u4 = URL(scheme="noloctron")
|
||||
u4 = u4.replace(path=("example", "path"))
|
||||
assert u4.to_text() == "noloctron:example/path"
|
||||
|
||||
def test_register_no_netloc_with_port(self):
|
||||
# type: () -> None
|
||||
with self.assertRaises(ValueError):
|
||||
register_scheme("badnetlocless", uses_netloc=False, default_port=7)
|
||||
|
||||
def test_invalid_uses_netloc(self):
|
||||
# type: () -> None
|
||||
with self.assertRaises(ValueError):
|
||||
register_scheme("badnetloc", uses_netloc=cast(bool, None))
|
||||
with self.assertRaises(ValueError):
|
||||
register_scheme("badnetloc", uses_netloc=cast(bool, object()))
|
||||
|
||||
def test_register_invalid_uses_netloc(self):
|
||||
# type: () -> None
|
||||
with self.assertRaises(ValueError):
|
||||
register_scheme("lol", uses_netloc=cast(bool, object()))
|
||||
|
||||
def test_register_invalid_port(self):
|
||||
# type: () -> None
|
||||
with self.assertRaises(ValueError):
|
||||
register_scheme("nope", default_port=cast(bool, object()))
|
||||
|
||||
def test_register_no_quote_plus_scheme(self):
|
||||
# type: () -> None
|
||||
register_scheme("keepplus", query_plus_is_space=False)
|
||||
plus_is_not_space = DecodedURL.from_text(
|
||||
"keepplus://example.com/?q=a+b"
|
||||
)
|
||||
plus_is_space = DecodedURL.from_text("https://example.com/?q=a+b")
|
||||
assert plus_is_not_space.get("q") == ["a+b"]
|
||||
assert plus_is_space.get("q") == ["a b"]
|
||||
@@ -0,0 +1,45 @@
|
||||
# mypy: always-true=inet_pton
|
||||
|
||||
try:
|
||||
from socket import inet_pton
|
||||
except ImportError:
|
||||
inet_pton = None # type: ignore[assignment]
|
||||
|
||||
if not inet_pton:
|
||||
import socket
|
||||
|
||||
from .common import HyperlinkTestCase
|
||||
from .._socket import inet_pton
|
||||
|
||||
class TestSocket(HyperlinkTestCase):
|
||||
def test_inet_pton_ipv4_valid(self):
|
||||
# type: () -> None
|
||||
data = inet_pton(socket.AF_INET, "127.0.0.1")
|
||||
assert isinstance(data, bytes)
|
||||
|
||||
def test_inet_pton_ipv4_bogus(self):
|
||||
# type: () -> None
|
||||
with self.assertRaises(socket.error):
|
||||
inet_pton(socket.AF_INET, "blah")
|
||||
|
||||
def test_inet_pton_ipv6_valid(self):
|
||||
# type: () -> None
|
||||
data = inet_pton(socket.AF_INET6, "::1")
|
||||
assert isinstance(data, bytes)
|
||||
|
||||
def test_inet_pton_ipv6_bogus(self):
|
||||
# type: () -> None
|
||||
with self.assertRaises(socket.error):
|
||||
inet_pton(socket.AF_INET6, "blah")
|
||||
|
||||
def test_inet_pton_bogus_family(self):
|
||||
# type: () -> None
|
||||
# Find an integer not associated with a known address family
|
||||
i = int(socket.AF_INET6)
|
||||
while True:
|
||||
if i != socket.AF_INET and i != socket.AF_INET6:
|
||||
break
|
||||
i += 100
|
||||
|
||||
with self.assertRaises(socket.error):
|
||||
inet_pton(i, "127.0.0.1")
|
||||
1495
backend/lib/python3.12/site-packages/hyperlink/test/test_url.py
Normal file
1495
backend/lib/python3.12/site-packages/hyperlink/test/test_url.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user