Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wiseplat
GitHub Repository: wiseplat/python-code
Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/yarl/_url.py
7771 views
1
import functools
2
import sys
3
import warnings
4
from collections.abc import Mapping, Sequence
5
from ipaddress import ip_address
6
from urllib.parse import SplitResult, parse_qsl, urljoin, urlsplit, urlunsplit, quote
7
8
from multidict import MultiDict, MultiDictProxy
9
import idna
10
11
import math
12
13
14
from ._quoting import _Quoter, _Unquoter
15
16
17
DEFAULT_PORTS = {"http": 80, "https": 443, "ws": 80, "wss": 443}
18
19
sentinel = object()
20
21
22
def rewrite_module(obj: object) -> object:
23
obj.__module__ = "yarl"
24
return obj
25
26
27
class cached_property:
28
"""Use as a class method decorator. It operates almost exactly like
29
the Python `@property` decorator, but it puts the result of the
30
method it decorates into the instance dict after the first call,
31
effectively replacing the function it decorates with an instance
32
variable. It is, in Python parlance, a data descriptor.
33
34
"""
35
36
def __init__(self, wrapped):
37
self.wrapped = wrapped
38
try:
39
self.__doc__ = wrapped.__doc__
40
except AttributeError: # pragma: no cover
41
self.__doc__ = ""
42
self.name = wrapped.__name__
43
44
def __get__(self, inst, owner, _sentinel=sentinel):
45
if inst is None:
46
return self
47
val = inst._cache.get(self.name, _sentinel)
48
if val is not _sentinel:
49
return val
50
val = self.wrapped(inst)
51
inst._cache[self.name] = val
52
return val
53
54
def __set__(self, inst, value):
55
raise AttributeError("cached property is read-only")
56
57
58
@rewrite_module
59
class URL:
60
# Don't derive from str
61
# follow pathlib.Path design
62
# probably URL will not suffer from pathlib problems:
63
# it's intended for libraries like aiohttp,
64
# not to be passed into standard library functions like os.open etc.
65
66
# URL grammar (RFC 3986)
67
# pct-encoded = "%" HEXDIG HEXDIG
68
# reserved = gen-delims / sub-delims
69
# gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
70
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
71
# / "*" / "+" / "," / ";" / "="
72
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
73
# URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
74
# hier-part = "//" authority path-abempty
75
# / path-absolute
76
# / path-rootless
77
# / path-empty
78
# scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
79
# authority = [ userinfo "@" ] host [ ":" port ]
80
# userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
81
# host = IP-literal / IPv4address / reg-name
82
# IP-literal = "[" ( IPv6address / IPvFuture ) "]"
83
# IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
84
# IPv6address = 6( h16 ":" ) ls32
85
# / "::" 5( h16 ":" ) ls32
86
# / [ h16 ] "::" 4( h16 ":" ) ls32
87
# / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
88
# / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
89
# / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
90
# / [ *4( h16 ":" ) h16 ] "::" ls32
91
# / [ *5( h16 ":" ) h16 ] "::" h16
92
# / [ *6( h16 ":" ) h16 ] "::"
93
# ls32 = ( h16 ":" h16 ) / IPv4address
94
# ; least-significant 32 bits of address
95
# h16 = 1*4HEXDIG
96
# ; 16 bits of address represented in hexadecimal
97
# IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
98
# dec-octet = DIGIT ; 0-9
99
# / %x31-39 DIGIT ; 10-99
100
# / "1" 2DIGIT ; 100-199
101
# / "2" %x30-34 DIGIT ; 200-249
102
# / "25" %x30-35 ; 250-255
103
# reg-name = *( unreserved / pct-encoded / sub-delims )
104
# port = *DIGIT
105
# path = path-abempty ; begins with "/" or is empty
106
# / path-absolute ; begins with "/" but not "//"
107
# / path-noscheme ; begins with a non-colon segment
108
# / path-rootless ; begins with a segment
109
# / path-empty ; zero characters
110
# path-abempty = *( "/" segment )
111
# path-absolute = "/" [ segment-nz *( "/" segment ) ]
112
# path-noscheme = segment-nz-nc *( "/" segment )
113
# path-rootless = segment-nz *( "/" segment )
114
# path-empty = 0<pchar>
115
# segment = *pchar
116
# segment-nz = 1*pchar
117
# segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
118
# ; non-zero-length segment without any colon ":"
119
# pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
120
# query = *( pchar / "/" / "?" )
121
# fragment = *( pchar / "/" / "?" )
122
# URI-reference = URI / relative-ref
123
# relative-ref = relative-part [ "?" query ] [ "#" fragment ]
124
# relative-part = "//" authority path-abempty
125
# / path-absolute
126
# / path-noscheme
127
# / path-empty
128
# absolute-URI = scheme ":" hier-part [ "?" query ]
129
__slots__ = ("_cache", "_val")
130
131
_QUOTER = _Quoter(requote=False)
132
_REQUOTER = _Quoter()
133
_PATH_QUOTER = _Quoter(safe="@:", protected="/+", requote=False)
134
_PATH_REQUOTER = _Quoter(safe="@:", protected="/+")
135
_QUERY_QUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True, requote=False)
136
_QUERY_REQUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True)
137
_QUERY_PART_QUOTER = _Quoter(safe="?/:@", qs=True, requote=False)
138
_FRAGMENT_QUOTER = _Quoter(safe="?/:@", requote=False)
139
_FRAGMENT_REQUOTER = _Quoter(safe="?/:@")
140
141
_UNQUOTER = _Unquoter()
142
_PATH_UNQUOTER = _Unquoter(unsafe="+")
143
_QS_UNQUOTER = _Unquoter(qs=True)
144
145
def __new__(cls, val="", *, encoded=False, strict=None):
146
if strict is not None: # pragma: no cover
147
warnings.warn("strict parameter is ignored")
148
if type(val) is cls:
149
return val
150
if type(val) is str:
151
val = urlsplit(val)
152
elif type(val) is SplitResult:
153
if not encoded:
154
raise ValueError("Cannot apply decoding to SplitResult")
155
elif isinstance(val, str):
156
val = urlsplit(str(val))
157
else:
158
raise TypeError("Constructor parameter should be str")
159
160
if not encoded:
161
if not val[1]: # netloc
162
netloc = ""
163
host = ""
164
else:
165
host = val.hostname
166
if host is None:
167
raise ValueError("Invalid URL: host is required for absolute urls")
168
169
try:
170
port = val.port
171
except ValueError as e:
172
raise ValueError(
173
"Invalid URL: port can't be converted to integer"
174
) from e
175
176
netloc = cls._make_netloc(
177
val.username, val.password, host, port, encode=True, requote=True
178
)
179
path = cls._PATH_REQUOTER(val[2])
180
if netloc:
181
path = cls._normalize_path(path)
182
183
cls._validate_authority_uri_abs_path(host=host, path=path)
184
query = cls._QUERY_REQUOTER(val[3])
185
fragment = cls._FRAGMENT_REQUOTER(val[4])
186
val = SplitResult(val[0], netloc, path, query, fragment)
187
188
self = object.__new__(cls)
189
self._val = val
190
self._cache = {}
191
return self
192
193
@classmethod
194
def build(
195
cls,
196
*,
197
scheme="",
198
authority="",
199
user=None,
200
password=None,
201
host="",
202
port=None,
203
path="",
204
query=None,
205
query_string="",
206
fragment="",
207
encoded=False
208
):
209
"""Creates and returns a new URL"""
210
211
if authority and (user or password or host or port):
212
raise ValueError(
213
'Can\'t mix "authority" with "user", "password", "host" or "port".'
214
)
215
if port and not host:
216
raise ValueError('Can\'t build URL with "port" but without "host".')
217
if query and query_string:
218
raise ValueError('Only one of "query" or "query_string" should be passed')
219
if (
220
scheme is None
221
or authority is None
222
or path is None
223
or query_string is None
224
or fragment is None
225
):
226
raise TypeError(
227
'NoneType is illegal for "scheme", "authority", "path", '
228
'"query_string", and "fragment" args, use empty string instead.'
229
)
230
231
if authority:
232
if encoded:
233
netloc = authority
234
else:
235
tmp = SplitResult("", authority, "", "", "")
236
netloc = cls._make_netloc(
237
tmp.username, tmp.password, tmp.hostname, tmp.port, encode=True
238
)
239
elif not user and not password and not host and not port:
240
netloc = ""
241
else:
242
netloc = cls._make_netloc(
243
user, password, host, port, encode=not encoded, encode_host=not encoded
244
)
245
if not encoded:
246
path = cls._PATH_QUOTER(path)
247
if netloc:
248
path = cls._normalize_path(path)
249
250
cls._validate_authority_uri_abs_path(host=host, path=path)
251
query_string = cls._QUERY_QUOTER(query_string)
252
fragment = cls._FRAGMENT_QUOTER(fragment)
253
254
url = cls(
255
SplitResult(scheme, netloc, path, query_string, fragment), encoded=True
256
)
257
258
if query:
259
return url.with_query(query)
260
else:
261
return url
262
263
def __init_subclass__(cls):
264
raise TypeError("Inheriting a class {!r} from URL is forbidden".format(cls))
265
266
def __str__(self):
267
val = self._val
268
if not val.path and self.is_absolute() and (val.query or val.fragment):
269
val = val._replace(path="/")
270
return urlunsplit(val)
271
272
def __repr__(self):
273
return "{}('{}')".format(self.__class__.__name__, str(self))
274
275
def __bytes__(self):
276
return str(self).encode("ascii")
277
278
def __eq__(self, other):
279
if not type(other) is URL:
280
return NotImplemented
281
282
val1 = self._val
283
if not val1.path and self.is_absolute():
284
val1 = val1._replace(path="/")
285
286
val2 = other._val
287
if not val2.path and other.is_absolute():
288
val2 = val2._replace(path="/")
289
290
return val1 == val2
291
292
def __hash__(self):
293
ret = self._cache.get("hash")
294
if ret is None:
295
val = self._val
296
if not val.path and self.is_absolute():
297
val = val._replace(path="/")
298
ret = self._cache["hash"] = hash(val)
299
return ret
300
301
def __le__(self, other):
302
if not type(other) is URL:
303
return NotImplemented
304
return self._val <= other._val
305
306
def __lt__(self, other):
307
if not type(other) is URL:
308
return NotImplemented
309
return self._val < other._val
310
311
def __ge__(self, other):
312
if not type(other) is URL:
313
return NotImplemented
314
return self._val >= other._val
315
316
def __gt__(self, other):
317
if not type(other) is URL:
318
return NotImplemented
319
return self._val > other._val
320
321
def __truediv__(self, name):
322
name = self._PATH_QUOTER(name)
323
if name.startswith("/"):
324
raise ValueError(
325
"Appending path {!r} starting from slash is forbidden".format(name)
326
)
327
path = self._val.path
328
if path == "/":
329
new_path = "/" + name
330
elif not path and not self.is_absolute():
331
new_path = name
332
else:
333
parts = path.rstrip("/").split("/")
334
parts.append(name)
335
new_path = "/".join(parts)
336
if self.is_absolute():
337
new_path = self._normalize_path(new_path)
338
return URL(
339
self._val._replace(path=new_path, query="", fragment=""), encoded=True
340
)
341
342
def __mod__(self, query):
343
return self.update_query(query)
344
345
def __bool__(self) -> bool:
346
return bool(
347
self._val.netloc or self._val.path or self._val.query or self._val.fragment
348
)
349
350
def __getstate__(self):
351
return (self._val,)
352
353
def __setstate__(self, state):
354
if state[0] is None and isinstance(state[1], dict):
355
# default style pickle
356
self._val = state[1]["_val"]
357
else:
358
self._val, *unused = state
359
self._cache = {}
360
361
def is_absolute(self):
362
"""A check for absolute URLs.
363
364
Return True for absolute ones (having scheme or starting
365
with //), False otherwise.
366
367
"""
368
return self.raw_host is not None
369
370
def is_default_port(self):
371
"""A check for default port.
372
373
Return True if port is default for specified scheme,
374
e.g. 'http://python.org' or 'http://python.org:80', False
375
otherwise.
376
377
"""
378
if self.port is None:
379
return False
380
default = DEFAULT_PORTS.get(self.scheme)
381
if default is None:
382
return False
383
return self.port == default
384
385
def origin(self):
386
"""Return an URL with scheme, host and port parts only.
387
388
user, password, path, query and fragment are removed.
389
390
"""
391
# TODO: add a keyword-only option for keeping user/pass maybe?
392
if not self.is_absolute():
393
raise ValueError("URL should be absolute")
394
if not self._val.scheme:
395
raise ValueError("URL should have scheme")
396
v = self._val
397
netloc = self._make_netloc(None, None, v.hostname, v.port)
398
val = v._replace(netloc=netloc, path="", query="", fragment="")
399
return URL(val, encoded=True)
400
401
def relative(self):
402
"""Return a relative part of the URL.
403
404
scheme, user, password, host and port are removed.
405
406
"""
407
if not self.is_absolute():
408
raise ValueError("URL should be absolute")
409
val = self._val._replace(scheme="", netloc="")
410
return URL(val, encoded=True)
411
412
@property
413
def scheme(self):
414
"""Scheme for absolute URLs.
415
416
Empty string for relative URLs or URLs starting with //
417
418
"""
419
return self._val.scheme
420
421
@property
422
def raw_authority(self):
423
"""Encoded authority part of URL.
424
425
Empty string for relative URLs.
426
427
"""
428
return self._val.netloc
429
430
@cached_property
431
def authority(self):
432
"""Decoded authority part of URL.
433
434
Empty string for relative URLs.
435
436
"""
437
return self._make_netloc(
438
self.user, self.password, self.host, self.port, encode_host=False
439
)
440
441
@property
442
def raw_user(self):
443
"""Encoded user part of URL.
444
445
None if user is missing.
446
447
"""
448
# not .username
449
ret = self._val.username
450
if not ret:
451
return None
452
return ret
453
454
@cached_property
455
def user(self):
456
"""Decoded user part of URL.
457
458
None if user is missing.
459
460
"""
461
return self._UNQUOTER(self.raw_user)
462
463
@property
464
def raw_password(self):
465
"""Encoded password part of URL.
466
467
None if password is missing.
468
469
"""
470
return self._val.password
471
472
@cached_property
473
def password(self):
474
"""Decoded password part of URL.
475
476
None if password is missing.
477
478
"""
479
return self._UNQUOTER(self.raw_password)
480
481
@property
482
def raw_host(self):
483
"""Encoded host part of URL.
484
485
None for relative URLs.
486
487
"""
488
# Use host instead of hostname for sake of shortness
489
# May add .hostname prop later
490
return self._val.hostname
491
492
@cached_property
493
def host(self):
494
"""Decoded host part of URL.
495
496
None for relative URLs.
497
498
"""
499
raw = self.raw_host
500
if raw is None:
501
return None
502
if "%" in raw:
503
# Hack for scoped IPv6 addresses like
504
# fe80::2%Проверка
505
# presence of '%' sign means only IPv6 address, so idna is useless.
506
return raw
507
return _idna_decode(raw)
508
509
@property
510
def port(self):
511
"""Port part of URL, with scheme-based fallback.
512
513
None for relative URLs or URLs without explicit port and
514
scheme without default port substitution.
515
516
"""
517
return self._val.port or DEFAULT_PORTS.get(self._val.scheme)
518
519
@property
520
def explicit_port(self):
521
"""Port part of URL, without scheme-based fallback.
522
523
None for relative URLs or URLs without explicit port.
524
525
"""
526
return self._val.port
527
528
@property
529
def raw_path(self):
530
"""Encoded path of URL.
531
532
/ for absolute URLs without path part.
533
534
"""
535
ret = self._val.path
536
if not ret and self.is_absolute():
537
ret = "/"
538
return ret
539
540
@cached_property
541
def path(self):
542
"""Decoded path of URL.
543
544
/ for absolute URLs without path part.
545
546
"""
547
return self._PATH_UNQUOTER(self.raw_path)
548
549
@cached_property
550
def query(self):
551
"""A MultiDictProxy representing parsed query parameters in decoded
552
representation.
553
554
Empty value if URL has no query part.
555
556
"""
557
ret = MultiDict(parse_qsl(self.raw_query_string, keep_blank_values=True))
558
return MultiDictProxy(ret)
559
560
@property
561
def raw_query_string(self):
562
"""Encoded query part of URL.
563
564
Empty string if query is missing.
565
566
"""
567
return self._val.query
568
569
@cached_property
570
def query_string(self):
571
"""Decoded query part of URL.
572
573
Empty string if query is missing.
574
575
"""
576
return self._QS_UNQUOTER(self.raw_query_string)
577
578
@cached_property
579
def path_qs(self):
580
"""Decoded path of URL with query."""
581
if not self.query_string:
582
return self.path
583
return "{}?{}".format(self.path, self.query_string)
584
585
@cached_property
586
def raw_path_qs(self):
587
"""Encoded path of URL with query."""
588
if not self.raw_query_string:
589
return self.raw_path
590
return "{}?{}".format(self.raw_path, self.raw_query_string)
591
592
@property
593
def raw_fragment(self):
594
"""Encoded fragment part of URL.
595
596
Empty string if fragment is missing.
597
598
"""
599
return self._val.fragment
600
601
@cached_property
602
def fragment(self):
603
"""Decoded fragment part of URL.
604
605
Empty string if fragment is missing.
606
607
"""
608
return self._UNQUOTER(self.raw_fragment)
609
610
@cached_property
611
def raw_parts(self):
612
"""A tuple containing encoded *path* parts.
613
614
('/',) for absolute URLs if *path* is missing.
615
616
"""
617
path = self._val.path
618
if self.is_absolute():
619
if not path:
620
parts = ["/"]
621
else:
622
parts = ["/"] + path[1:].split("/")
623
else:
624
if path.startswith("/"):
625
parts = ["/"] + path[1:].split("/")
626
else:
627
parts = path.split("/")
628
return tuple(parts)
629
630
@cached_property
631
def parts(self):
632
"""A tuple containing decoded *path* parts.
633
634
('/',) for absolute URLs if *path* is missing.
635
636
"""
637
return tuple(self._UNQUOTER(part) for part in self.raw_parts)
638
639
@cached_property
640
def parent(self):
641
"""A new URL with last part of path removed and cleaned up query and
642
fragment.
643
644
"""
645
path = self.raw_path
646
if not path or path == "/":
647
if self.raw_fragment or self.raw_query_string:
648
return URL(self._val._replace(query="", fragment=""), encoded=True)
649
return self
650
parts = path.split("/")
651
val = self._val._replace(path="/".join(parts[:-1]), query="", fragment="")
652
return URL(val, encoded=True)
653
654
@cached_property
655
def raw_name(self):
656
"""The last part of raw_parts."""
657
parts = self.raw_parts
658
if self.is_absolute():
659
parts = parts[1:]
660
if not parts:
661
return ""
662
else:
663
return parts[-1]
664
else:
665
return parts[-1]
666
667
@cached_property
668
def name(self):
669
"""The last part of parts."""
670
return self._UNQUOTER(self.raw_name)
671
672
@staticmethod
673
def _validate_authority_uri_abs_path(host, path):
674
"""Ensure that path in URL with authority starts with a leading slash.
675
676
Raise ValueError if not.
677
"""
678
if len(host) > 0 and len(path) > 0 and not path.startswith("/"):
679
raise ValueError(
680
"Path in a URL with authority should start with a slash ('/') if set"
681
)
682
683
@classmethod
684
def _normalize_path(cls, path):
685
# Drop '.' and '..' from path
686
687
segments = path.split("/")
688
resolved_path = []
689
690
for seg in segments:
691
if seg == "..":
692
try:
693
resolved_path.pop()
694
except IndexError:
695
# ignore any .. segments that would otherwise cause an
696
# IndexError when popped from resolved_path if
697
# resolving for rfc3986
698
pass
699
elif seg == ".":
700
continue
701
else:
702
resolved_path.append(seg)
703
704
if segments[-1] in (".", ".."):
705
# do some post-processing here.
706
# if the last segment was a relative dir,
707
# then we need to append the trailing '/'
708
resolved_path.append("")
709
710
return "/".join(resolved_path)
711
712
if sys.version_info >= (3, 7):
713
714
@classmethod
715
def _encode_host(cls, host, human=False):
716
try:
717
ip, sep, zone = host.partition("%")
718
ip = ip_address(ip)
719
except ValueError:
720
host = host.lower()
721
# IDNA encoding is slow,
722
# skip it for ASCII-only strings
723
# Don't move the check into _idna_encode() helper
724
# to reduce the cache size
725
if human or host.isascii():
726
return host
727
host = _idna_encode(host)
728
else:
729
host = ip.compressed
730
if sep:
731
host += "%" + zone
732
if ip.version == 6:
733
host = "[" + host + "]"
734
return host
735
736
else:
737
# work around for missing str.isascii() in Python <= 3.6
738
@classmethod
739
def _encode_host(cls, host, human=False):
740
try:
741
ip, sep, zone = host.partition("%")
742
ip = ip_address(ip)
743
except ValueError:
744
host = host.lower()
745
if human:
746
return host
747
748
for char in host:
749
if char > "\x7f":
750
break
751
else:
752
return host
753
host = _idna_encode(host)
754
else:
755
host = ip.compressed
756
if sep:
757
host += "%" + zone
758
if ip.version == 6:
759
host = "[" + host + "]"
760
return host
761
762
@classmethod
763
def _make_netloc(
764
cls, user, password, host, port, encode=False, encode_host=True, requote=False
765
):
766
quoter = cls._REQUOTER if requote else cls._QUOTER
767
if encode_host:
768
ret = cls._encode_host(host)
769
else:
770
ret = host
771
if port:
772
ret = ret + ":" + str(port)
773
if password is not None:
774
if not user:
775
user = ""
776
else:
777
if encode:
778
user = quoter(user)
779
if encode:
780
password = quoter(password)
781
user = user + ":" + password
782
elif user and encode:
783
user = quoter(user)
784
if user:
785
ret = user + "@" + ret
786
return ret
787
788
def with_scheme(self, scheme):
789
"""Return a new URL with scheme replaced."""
790
# N.B. doesn't cleanup query/fragment
791
if not isinstance(scheme, str):
792
raise TypeError("Invalid scheme type")
793
if not self.is_absolute():
794
raise ValueError("scheme replacement is not allowed for relative URLs")
795
return URL(self._val._replace(scheme=scheme.lower()), encoded=True)
796
797
def with_user(self, user):
798
"""Return a new URL with user replaced.
799
800
Autoencode user if needed.
801
802
Clear user/password if user is None.
803
804
"""
805
# N.B. doesn't cleanup query/fragment
806
val = self._val
807
if user is None:
808
password = None
809
elif isinstance(user, str):
810
user = self._QUOTER(user)
811
password = val.password
812
else:
813
raise TypeError("Invalid user type")
814
if not self.is_absolute():
815
raise ValueError("user replacement is not allowed for relative URLs")
816
return URL(
817
self._val._replace(
818
netloc=self._make_netloc(user, password, val.hostname, val.port)
819
),
820
encoded=True,
821
)
822
823
def with_password(self, password):
824
"""Return a new URL with password replaced.
825
826
Autoencode password if needed.
827
828
Clear password if argument is None.
829
830
"""
831
# N.B. doesn't cleanup query/fragment
832
if password is None:
833
pass
834
elif isinstance(password, str):
835
password = self._QUOTER(password)
836
else:
837
raise TypeError("Invalid password type")
838
if not self.is_absolute():
839
raise ValueError("password replacement is not allowed for relative URLs")
840
val = self._val
841
return URL(
842
self._val._replace(
843
netloc=self._make_netloc(val.username, password, val.hostname, val.port)
844
),
845
encoded=True,
846
)
847
848
def with_host(self, host):
849
"""Return a new URL with host replaced.
850
851
Autoencode host if needed.
852
853
Changing host for relative URLs is not allowed, use .join()
854
instead.
855
856
"""
857
# N.B. doesn't cleanup query/fragment
858
if not isinstance(host, str):
859
raise TypeError("Invalid host type")
860
if not self.is_absolute():
861
raise ValueError("host replacement is not allowed for relative URLs")
862
if not host:
863
raise ValueError("host removing is not allowed")
864
val = self._val
865
return URL(
866
self._val._replace(
867
netloc=self._make_netloc(val.username, val.password, host, val.port)
868
),
869
encoded=True,
870
)
871
872
def with_port(self, port):
873
"""Return a new URL with port replaced.
874
875
Clear port to default if None is passed.
876
877
"""
878
# N.B. doesn't cleanup query/fragment
879
if port is not None and not isinstance(port, int):
880
raise TypeError("port should be int or None, got {}".format(type(port)))
881
if not self.is_absolute():
882
raise ValueError("port replacement is not allowed for relative URLs")
883
val = self._val
884
return URL(
885
self._val._replace(
886
netloc=self._make_netloc(val.username, val.password, val.hostname, port)
887
),
888
encoded=True,
889
)
890
891
def with_path(self, path, *, encoded=False):
892
"""Return a new URL with path replaced."""
893
if not encoded:
894
path = self._PATH_QUOTER(path)
895
if self.is_absolute():
896
path = self._normalize_path(path)
897
if len(path) > 0 and path[0] != "/":
898
path = "/" + path
899
return URL(self._val._replace(path=path, query="", fragment=""), encoded=True)
900
901
@classmethod
902
def _query_seq_pairs(cls, quoter, pairs):
903
for key, val in pairs:
904
if isinstance(val, (list, tuple)):
905
for v in val:
906
yield quoter(key) + "=" + quoter(cls._query_var(v))
907
else:
908
yield quoter(key) + "=" + quoter(cls._query_var(val))
909
910
@staticmethod
911
def _query_var(v):
912
cls = type(v)
913
if issubclass(cls, str):
914
return v
915
if issubclass(cls, float):
916
if math.isinf(v):
917
raise ValueError("float('inf') is not supported")
918
if math.isnan(v):
919
raise ValueError("float('nan') is not supported")
920
return str(float(v))
921
if issubclass(cls, int) and cls is not bool:
922
return str(int(v))
923
raise TypeError(
924
"Invalid variable type: value "
925
"should be str, int or float, got {!r} "
926
"of type {}".format(v, cls)
927
)
928
929
def _get_str_query(self, *args, **kwargs):
930
if kwargs:
931
if len(args) > 0:
932
raise ValueError(
933
"Either kwargs or single query parameter must be present"
934
)
935
query = kwargs
936
elif len(args) == 1:
937
query = args[0]
938
else:
939
raise ValueError("Either kwargs or single query parameter must be present")
940
941
if query is None:
942
query = ""
943
elif isinstance(query, Mapping):
944
quoter = self._QUERY_PART_QUOTER
945
query = "&".join(self._query_seq_pairs(quoter, query.items()))
946
elif isinstance(query, str):
947
query = self._QUERY_QUOTER(query)
948
elif isinstance(query, (bytes, bytearray, memoryview)):
949
raise TypeError(
950
"Invalid query type: bytes, bytearray and memoryview are forbidden"
951
)
952
elif isinstance(query, Sequence):
953
quoter = self._QUERY_PART_QUOTER
954
# We don't expect sequence values if we're given a list of pairs
955
# already; only mappings like builtin `dict` which can't have the
956
# same key pointing to multiple values are allowed to use
957
# `_query_seq_pairs`.
958
query = "&".join(
959
quoter(k) + "=" + quoter(self._query_var(v)) for k, v in query
960
)
961
else:
962
raise TypeError(
963
"Invalid query type: only str, mapping or "
964
"sequence of (key, value) pairs is allowed"
965
)
966
967
return query
968
969
def with_query(self, *args, **kwargs):
970
"""Return a new URL with query part replaced.
971
972
Accepts any Mapping (e.g. dict, multidict.MultiDict instances)
973
or str, autoencode the argument if needed.
974
975
A sequence of (key, value) pairs is supported as well.
976
977
It also can take an arbitrary number of keyword arguments.
978
979
Clear query if None is passed.
980
981
"""
982
# N.B. doesn't cleanup query/fragment
983
984
new_query = self._get_str_query(*args, **kwargs)
985
return URL(
986
self._val._replace(path=self._val.path, query=new_query), encoded=True
987
)
988
989
def update_query(self, *args, **kwargs):
990
"""Return a new URL with query part updated."""
991
s = self._get_str_query(*args, **kwargs)
992
new_query = MultiDict(parse_qsl(s, keep_blank_values=True))
993
query = MultiDict(self.query)
994
query.update(new_query)
995
996
return URL(self._val._replace(query=self._get_str_query(query)), encoded=True)
997
998
def with_fragment(self, fragment):
999
"""Return a new URL with fragment replaced.
1000
1001
Autoencode fragment if needed.
1002
1003
Clear fragment to default if None is passed.
1004
1005
"""
1006
# N.B. doesn't cleanup query/fragment
1007
if fragment is None:
1008
raw_fragment = ""
1009
elif not isinstance(fragment, str):
1010
raise TypeError("Invalid fragment type")
1011
else:
1012
raw_fragment = self._FRAGMENT_QUOTER(fragment)
1013
if self.raw_fragment == raw_fragment:
1014
return self
1015
return URL(self._val._replace(fragment=raw_fragment), encoded=True)
1016
1017
def with_name(self, name):
1018
"""Return a new URL with name (last part of path) replaced.
1019
1020
Query and fragment parts are cleaned up.
1021
1022
Name is encoded if needed.
1023
1024
"""
1025
# N.B. DOES cleanup query/fragment
1026
if not isinstance(name, str):
1027
raise TypeError("Invalid name type")
1028
if "/" in name:
1029
raise ValueError("Slash in name is not allowed")
1030
name = self._PATH_QUOTER(name)
1031
if name in (".", ".."):
1032
raise ValueError(". and .. values are forbidden")
1033
parts = list(self.raw_parts)
1034
if self.is_absolute():
1035
if len(parts) == 1:
1036
parts.append(name)
1037
else:
1038
parts[-1] = name
1039
parts[0] = "" # replace leading '/'
1040
else:
1041
parts[-1] = name
1042
if parts[0] == "/":
1043
parts[0] = "" # replace leading '/'
1044
return URL(
1045
self._val._replace(path="/".join(parts), query="", fragment=""),
1046
encoded=True,
1047
)
1048
1049
def join(self, url):
1050
"""Join URLs
1051
1052
Construct a full (“absolute”) URL by combining a “base URL”
1053
(self) with another URL (url).
1054
1055
Informally, this uses components of the base URL, in
1056
particular the addressing scheme, the network location and
1057
(part of) the path, to provide missing components in the
1058
relative URL.
1059
1060
"""
1061
# See docs for urllib.parse.urljoin
1062
if not isinstance(url, URL):
1063
raise TypeError("url should be URL")
1064
return URL(urljoin(str(self), str(url)), encoded=True)
1065
1066
def human_repr(self):
1067
"""Return decoded human readable string for URL representation."""
1068
user = _human_quote(self.user, "#/:?@")
1069
password = _human_quote(self.password, "#/:?@")
1070
host = self.host
1071
if host:
1072
host = self._encode_host(self.host, human=True)
1073
path = _human_quote(self.path, "#?")
1074
query_string = "&".join(
1075
"{}={}".format(_human_quote(k, "#&+;="), _human_quote(v, "#&+;="))
1076
for k, v in self.query.items()
1077
)
1078
fragment = _human_quote(self.fragment, "")
1079
return urlunsplit(
1080
SplitResult(
1081
self.scheme,
1082
self._make_netloc(
1083
user,
1084
password,
1085
host,
1086
self._val.port,
1087
encode_host=False,
1088
),
1089
path,
1090
query_string,
1091
fragment,
1092
)
1093
)
1094
1095
1096
def _human_quote(s, unsafe):
1097
if not s:
1098
return s
1099
for c in "%" + unsafe:
1100
if c in s:
1101
s = s.replace(c, "%{:02X}".format(ord(c)))
1102
if s.isprintable():
1103
return s
1104
return "".join(c if c.isprintable() else quote(c) for c in s)
1105
1106
1107
_MAXCACHE = 256
1108
1109
1110
@functools.lru_cache(_MAXCACHE)
1111
def _idna_decode(raw):
1112
try:
1113
return idna.decode(raw.encode("ascii"))
1114
except UnicodeError: # e.g. '::1'
1115
return raw.encode("ascii").decode("idna")
1116
1117
1118
@functools.lru_cache(_MAXCACHE)
1119
def _idna_encode(host):
1120
try:
1121
return idna.encode(host, uts46=True).decode("ascii")
1122
except UnicodeError:
1123
return host.encode("idna").decode("ascii")
1124
1125
1126
@rewrite_module
1127
def cache_clear():
1128
_idna_decode.cache_clear()
1129
_idna_encode.cache_clear()
1130
1131
1132
@rewrite_module
1133
def cache_info():
1134
return {
1135
"idna_encode": _idna_encode.cache_info(),
1136
"idna_decode": _idna_decode.cache_info(),
1137
}
1138
1139
1140
@rewrite_module
1141
def cache_configure(*, idna_encode_size=_MAXCACHE, idna_decode_size=_MAXCACHE):
1142
global _idna_decode, _idna_encode
1143
1144
_idna_encode = functools.lru_cache(idna_encode_size)(_idna_encode.__wrapped__)
1145
_idna_decode = functools.lru_cache(idna_decode_size)(_idna_decode.__wrapped__)
1146
1147