Skip to content

Commit 325d64a

Browse files
authored
Merge branch 'master' into tox-docs
2 parents cd7115d + c0dd684 commit 325d64a

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
@@ -761,7 +761,9 @@ class URL(object):
761761
query (tuple): The query parameters, as a dictionary or
762762
as an iterable of key-value pairs.
763763
fragment (unicode): The fragment part of the URL.
764-
rooted (bool): Whether or not the path begins with a slash.
764+
rooted (bool): A rooted URL is one which indicates an absolute path.
765+
This is True on any URL that includes a host, or any relative URL
766+
that starts with a slash.
765767
userinfo (unicode): The username or colon-separated
766768
username:password pair.
767769
uses_netloc (bool): Indicates whether two slashes appear
@@ -823,8 +825,12 @@ def __init__(self, scheme=None, host=None, path=(), query=(), fragment=u'',
823825
uses_netloc = scheme_uses_netloc(self._scheme, uses_netloc)
824826
self._uses_netloc = _typecheck("uses_netloc",
825827
uses_netloc, bool, NoneType)
826-
827-
return
828+
# fixup for rooted consistency
829+
if self._host:
830+
self._rooted = True
831+
if (not self._rooted) and self._path and self._path[0] == '':
832+
self._rooted = True
833+
self._path = self._path[1:]
828834

829835
def get_decoded_url(self, lazy=False):
830836
try:
@@ -985,7 +991,7 @@ def __eq__(self, other):
985991
if not isinstance(other, self.__class__):
986992
return NotImplemented
987993
for attr in ['scheme', 'userinfo', 'host', 'query',
988-
'fragment', 'port', 'uses_netloc']:
994+
'fragment', 'port', 'uses_netloc', 'rooted']:
989995
if getattr(self, attr) != getattr(other, attr):
990996
return False
991997
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)