mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 13:51:08 -05:00
okay fine
This commit is contained in:
17
.venv/lib/python3.12/site-packages/hyperlink/__init__.py
Normal file
17
.venv/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",
|
||||
)
|
||||
53
.venv/lib/python3.12/site-packages/hyperlink/_socket.py
Normal file
53
.venv/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
.venv/lib/python3.12/site-packages/hyperlink/_url.py
Normal file
2448
.venv/lib/python3.12/site-packages/hyperlink/_url.py
Normal file
File diff suppressed because it is too large
Load Diff
321
.venv/lib/python3.12/site-packages/hyperlink/hypothesis.py
Normal file
321
.venv/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
.venv/lib/python3.12/site-packages/hyperlink/py.typed
Normal file
1
.venv/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()
|
||||
68
.venv/lib/python3.12/site-packages/hyperlink/test/common.py
Normal file
68
.venv/lib/python3.12/site-packages/hyperlink/test/common.py
Normal file
@@ -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
|
||||
116
.venv/lib/python3.12/site-packages/hyperlink/test/test_common.py
Normal file
116
.venv/lib/python3.12/site-packages/hyperlink/test/test_common.py
Normal file
@@ -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_[AWS-SECRET-REMOVED]ion(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_[AWS-SECRET-REMOVED]ption(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
.venv/lib/python3.12/site-packages/hyperlink/test/test_url.py
Normal file
1495
.venv/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