22# Licensed under the MIT license.
33
44from typing import Any , Literal , Optional
5+ from urllib .parse import parse_qs , urlparse , urlunparse
56
67import httpx
78from tenacity import retry , stop_after_attempt , wait_fixed
@@ -21,6 +22,45 @@ def get_httpx_client(use_async: bool = False, debug: bool = False, **httpx_clien
2122 return client_class (proxy = proxy , verify = verify_certs , timeout = timeout , ** httpx_client_kwargs )
2223
2324
25+ def extract_url_parameters (url : str ) -> dict [str , str ]:
26+ """
27+ Extract query parameters from a URL.
28+
29+ Args:
30+ url (str): The URL to extract parameters from.
31+
32+ Returns:
33+ dict[str, str]: Dictionary of query parameters (flattened from lists).
34+ """
35+ parsed_url = urlparse (url )
36+ url_params = parse_qs (parsed_url .query )
37+ # Flatten params (parse_qs returns lists)
38+ return {k : v [0 ] if isinstance (v , list ) and len (v ) > 0 else "" for k , v in url_params .items ()}
39+
40+
41+ def remove_url_parameters (url : str ) -> str :
42+ """
43+ Remove query parameters from a URL, returning just the base URL.
44+
45+ Args:
46+ url (str): The URL to clean.
47+
48+ Returns:
49+ str: The URL without query parameters.
50+ """
51+ parsed_url = urlparse (url )
52+ return urlunparse (
53+ (
54+ parsed_url .scheme ,
55+ parsed_url .netloc ,
56+ parsed_url .path ,
57+ parsed_url .params ,
58+ "" , # Remove query string
59+ parsed_url .fragment ,
60+ )
61+ )
62+
63+
2464PostType = Literal ["json" , "data" ]
2565
2666
@@ -30,23 +70,39 @@ async def make_request_and_raise_if_error_async(
3070 method : str ,
3171 post_type : PostType = "json" ,
3272 debug : bool = False ,
33- params : Optional [dict [str , str ]] = None ,
73+ extra_url_parameters : Optional [dict [str , str ]] = None ,
3474 request_body : Optional [dict [str , object ]] = None ,
3575 files : Optional [dict [str , tuple ]] = None ,
3676 headers : Optional [dict [str , str ]] = None ,
3777 ** httpx_client_kwargs : Optional [Any ],
3878) -> httpx .Response :
39- """Make a request and raise an exception if it fails."""
79+ """
80+ Make a request and raise an exception if it fails.
81+
82+ Query parameters can be specified either:
83+ 1. In the endpoint_uri (e.g., "https://api.com/endpoint?api-version=2024-10-21")
84+ 2. Via the extra_url_parameters dict
85+ 3. Both (extra_url_parameters will be merged with URL query parameters, with extra_url_parameters taking precedence)
86+ """
4087 headers = headers or {}
4188 request_body = request_body or {}
4289
43- params = params or {}
90+ # Extract any existing query parameters from the URL
91+ url_params = extract_url_parameters (endpoint_uri )
92+
93+ # Merge URL parameters with provided extra_url_parameters (extra_url_parameters takes precedence)
94+ merged_params = url_params .copy ()
95+ if extra_url_parameters :
96+ merged_params .update (extra_url_parameters )
97+
98+ # Get clean URL without query string (we'll pass params separately to httpx)
99+ clean_url = remove_url_parameters (endpoint_uri )
44100
45101 async with get_httpx_client (debug = debug , use_async = True , ** httpx_client_kwargs ) as async_client :
46102 response = await async_client .request (
47103 method = method ,
48- params = params ,
49- url = endpoint_uri ,
104+ params = merged_params if merged_params else None ,
105+ url = clean_url ,
50106 json = request_body if request_body and post_type == "json" and not files else None ,
51107 data = request_body if request_body and post_type != "json" and not files else None ,
52108 files = files if files else None ,
0 commit comments