@@ -112,7 +112,7 @@ class URL:
112
112
"""
113
113
114
114
def __init__ (
115
- self , url : typing .Union ["URL" , str , RawURL ] = "" , params : QueryParamTypes = None
115
+ self , url : typing .Union ["URL" , str , RawURL ] = "" , ** kwargs : typing . Any
116
116
) -> None :
117
117
if isinstance (url , (str , tuple )):
118
118
if isinstance (url , tuple ):
@@ -144,14 +144,8 @@ def __init__(
144
144
f"Invalid type for url. Expected str or httpx.URL, got { type (url )} : { url !r} "
145
145
)
146
146
147
- # Add any query parameters, merging with any in the URL if needed.
148
- if params :
149
- if self ._uri_reference .query :
150
- url_params = QueryParams (self ._uri_reference .query ).merge (params )
151
- query_string = str (url_params )
152
- else :
153
- query_string = str (QueryParams (params ))
154
- self ._uri_reference = self ._uri_reference .copy_with (query = query_string )
147
+ if kwargs :
148
+ self ._uri_reference = self .copy_with (** kwargs )._uri_reference
155
149
156
150
@property
157
151
def scheme (self ) -> str :
@@ -293,12 +287,27 @@ def path(self) -> str:
293
287
def query (self ) -> bytes :
294
288
"""
295
289
The URL query string, as raw bytes, excluding the leading b"?".
296
- Note that URL decoding can only be applied on URL query strings
297
- at the point of decoding the individual parameter names/values.
290
+
291
+ This is neccessarily a bytewise interface, because we cannot
292
+ perform URL decoding of this representation until we've parsed
293
+ the keys and values into a QueryParams instance.
294
+
295
+ For example:
296
+
297
+ url = httpx.URL("https://example.com/?filter=some%20search%20terms")
298
+ assert url.query == b"filter=some%20search%20terms"
298
299
"""
299
300
query = self ._uri_reference .query or ""
300
301
return query .encode ("ascii" )
301
302
303
+ @property
304
+ def params (self ) -> "QueryParams" :
305
+ """
306
+ The URL query parameters, neatly parsed and packaged into an immutable
307
+ multidict representation.
308
+ """
309
+ return QueryParams (self ._uri_reference .query )
310
+
302
311
@property
303
312
def raw_path (self ) -> bytes :
304
313
"""
@@ -382,6 +391,7 @@ def copy_with(self, **kwargs: typing.Any) -> "URL":
382
391
"query" : bytes ,
383
392
"raw_path" : bytes ,
384
393
"fragment" : str ,
394
+ "params" : object ,
385
395
}
386
396
for key , value in kwargs .items ():
387
397
if key not in allowed :
@@ -434,12 +444,28 @@ def copy_with(self, **kwargs: typing.Any) -> "URL":
434
444
if kwargs .get ("path" ) is not None :
435
445
kwargs ["path" ] = quote (kwargs ["path" ])
436
446
437
- # Ensure query=<str> for rfc3986
438
447
if kwargs .get ("query" ) is not None :
448
+ # Ensure query=<str> for rfc3986
439
449
kwargs ["query" ] = kwargs ["query" ].decode ("ascii" )
440
450
451
+ if "params" in kwargs :
452
+ params = kwargs .pop ("params" )
453
+ kwargs ["query" ] = None if not params else str (QueryParams (params ))
454
+
441
455
return URL (self ._uri_reference .copy_with (** kwargs ).unsplit ())
442
456
457
+ def copy_set_param (self , key : str , value : typing .Any = None ) -> "URL" :
458
+ return self .copy_with (params = self .params .set (key , value ))
459
+
460
+ def copy_add_param (self , key : str , value : typing .Any = None ) -> "URL" :
461
+ return self .copy_with (params = self .params .add (key , value ))
462
+
463
+ def copy_remove_param (self , key : str ) -> "URL" :
464
+ return self .copy_with (params = self .params .remove (key ))
465
+
466
+ def copy_merge_params (self , params : QueryParamTypes ) -> "URL" :
467
+ return self .copy_with (params = self .params .merge (params ))
468
+
443
469
def join (self , url : URLTypes ) -> "URL" :
444
470
"""
445
471
Return an absolute URL, using this URL as the base.
@@ -595,7 +621,7 @@ def get(self, key: typing.Any, default: typing.Any = None) -> typing.Any:
595
621
return self ._dict [str (key )][0 ]
596
622
return default
597
623
598
- def get_list (self , key : typing . Any ) -> typing .List [str ]:
624
+ def get_list (self , key : str ) -> typing .List [str ]:
599
625
"""
600
626
Get all values from the query param for a given key.
601
627
@@ -606,7 +632,7 @@ def get_list(self, key: typing.Any) -> typing.List[str]:
606
632
"""
607
633
return list (self ._dict .get (str (key ), []))
608
634
609
- def set (self , key : typing . Any , value : typing .Any = None ) -> "QueryParams" :
635
+ def set (self , key : str , value : typing .Any = None ) -> "QueryParams" :
610
636
"""
611
637
Return a new QueryParams instance, setting the value of a key.
612
638
@@ -621,7 +647,7 @@ def set(self, key: typing.Any, value: typing.Any = None) -> "QueryParams":
621
647
q ._dict [str (key )] = [primitive_value_to_str (value )]
622
648
return q
623
649
624
- def add (self , key : typing . Any , value : typing .Any = None ) -> "QueryParams" :
650
+ def add (self , key : str , value : typing .Any = None ) -> "QueryParams" :
625
651
"""
626
652
Return a new QueryParams instance, setting or appending the value of a key.
627
653
@@ -636,7 +662,7 @@ def add(self, key: typing.Any, value: typing.Any = None) -> "QueryParams":
636
662
q ._dict [str (key )] = q .get_list (key ) + [primitive_value_to_str (value )]
637
663
return q
638
664
639
- def remove (self , key : typing . Any ) -> "QueryParams" :
665
+ def remove (self , key : str ) -> "QueryParams" :
640
666
"""
641
667
Return a new QueryParams instance, removing the value of a key.
642
668
@@ -681,6 +707,9 @@ def __iter__(self) -> typing.Iterator[typing.Any]:
681
707
def __len__ (self ) -> int :
682
708
return len (self ._dict )
683
709
710
+ def __bool__ (self ) -> bool :
711
+ return bool (self ._dict )
712
+
684
713
def __hash__ (self ) -> int :
685
714
return hash (str (self ))
686
715
@@ -971,7 +1000,9 @@ def __init__(
971
1000
self .method = method .decode ("ascii" ).upper ()
972
1001
else :
973
1002
self .method = method .upper ()
974
- self .url = URL (url , params = params )
1003
+ self .url = URL (url )
1004
+ if params is not None :
1005
+ self .url = self .url .copy_merge_params (params = params )
975
1006
self .headers = Headers (headers )
976
1007
if cookies :
977
1008
Cookies (cookies ).set_cookie_header (self )
0 commit comments