Skip to content

Commit 0d373b2

Browse files
authored
Merge branch 'master' into mypy
2 parents 387af6f + c0dd684 commit 0d373b2

2 files changed

Lines changed: 46 additions & 4 deletions

File tree

src/hyperlink/_url.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,9 @@ class URL(object):
808808
query: The query parameters, as a dictionary or as an iterable of
809809
key-value pairs.
810810
fragment: The fragment part of the URL.
811-
rooted: Whether or not the path begins with a slash.
811+
rooted: A rooted URL is one which indicates an absolute path.
812+
This is True on any URL that includes a host, or any relative URL
813+
that starts with a slash.
812814
userinfo: The username or colon-separated username:password pair.
813815
uses_netloc: Indicates whether two slashes appear between the scheme
814816
and the host (``http://eg.com`` vs. ``mailto:e@g.com``).
@@ -880,8 +882,12 @@ def __init__(
880882
uses_netloc = scheme_uses_netloc(self._scheme, uses_netloc)
881883
self._uses_netloc = _typecheck("uses_netloc",
882884
uses_netloc, bool, NoneType)
883-
884-
return
885+
# fixup for rooted consistency
886+
if self._host:
887+
self._rooted = True
888+
if (not self._rooted) and self._path and self._path[0] == '':
889+
self._rooted = True
890+
self._path = self._path[1:]
885891

886892
def get_decoded_url(self, lazy=False):
887893
# type: (bool) -> DecodedURL
@@ -1051,7 +1057,7 @@ def __eq__(self, other):
10511057
if not isinstance(other, self.__class__):
10521058
return NotImplemented
10531059
for attr in ['scheme', 'userinfo', 'host', 'query',
1054-
'fragment', 'port', 'uses_netloc']:
1060+
'fragment', 'port', 'uses_netloc', 'rooted']:
10551061
if getattr(self, attr) != getattr(other, attr):
10561062
return False
10571063
if (

src/hyperlink/test/test_url.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,42 @@ def test_netloc_slashes(self):
10811081

10821082
return
10831083

1084+
def test_rooted_to_relative(self):
1085+
# type: () -> None
1086+
"""
1087+
On host-relative URLs, the C{rooted} flag can be updated to indicate
1088+
that the path should no longer be treated as absolute.
1089+
"""
1090+
a = URL(path=['hello'])
1091+
self.assertEqual(a.to_text(), 'hello')
1092+
b = a.replace(rooted=True)
1093+
self.assertEqual(b.to_text(), '/hello')
1094+
self.assertNotEqual(a, b)
1095+
1096+
def test_autorooted(self):
1097+
# type: () -> None
1098+
"""
1099+
The C{rooted} flag can be updated in some cases, but it cannot be made
1100+
to conflict with other facts surrounding the URL; for example, all URLs
1101+
involving an authority (host) are inherently rooted because it is not
1102+
syntactically possible to express otherwise; also, once an unrooted URL
1103+
gains a path that starts with an empty string, that empty string is
1104+
elided and it becomes rooted, because these cases are syntactically
1105+
indistinguisable in real URL text.
1106+
"""
1107+
relative_path_rooted = URL(path=['', 'foo'], rooted=False)
1108+
self.assertEqual(relative_path_rooted.rooted, True)
1109+
relative_flag_rooted = URL(path=['foo'], rooted=True)
1110+
self.assertEqual(relative_flag_rooted.rooted, True)
1111+
self.assertEqual(relative_path_rooted, relative_flag_rooted)
1112+
1113+
attempt_unrooted_absolute = URL(host="foo", path=['bar'], rooted=False)
1114+
normal_absolute = URL(host="foo", path=["bar"])
1115+
attempted_rooted_replacement = normal_absolute.replace(rooted=True)
1116+
self.assertEqual(attempt_unrooted_absolute, normal_absolute)
1117+
self.assertEqual(normal_absolute.rooted, True)
1118+
self.assertEqual(attempt_unrooted_absolute.rooted, True)
1119+
10841120
def test_wrong_constructor(self):
10851121
# type: () -> None
10861122
with self.assertRaises(ValueError):

0 commit comments

Comments
 (0)