Skip to content

[Bug] HttpxRequestAdapter: The silent deprecation of base_url is actually a breaking change #501

Closed

Description

#481 actually introduced a breaking change.

The parameter base_url is still there but no matter what value you provide, it's ignored. We did not pass a value for http_client, and only specified the base_url like this:

request_adapter = HttpxRequestAdapter(
    authentication_provider, base_url="https:/example.com"
)

We run this in a Fabric notebook where the version of the package was not pinned. So the moment it downloaded the new version, it failed with a misleading error.

To fix it, we had to do the following work around:

request_adapter = HttpxRequestAdapter(authentication_provider)
request_adapter.base_url = "https:/example.com"

I think it's best to deprecate this base_url parameter in the constructor if it's not supposed to be used.

Activity

  1. moved this to Needs Triage 🔍 in Kiotaon Nov 17, 2025
  2. HardMax71 commented on Oct 3, 2026

    @HardMax71
    Contributor

    Reproduced on kiota-http 1.14.1 and on main (ce04532). It started in 1.9.4 with #481, which drops the argument on purpose and adds a test asserting it's ignored (test_httpx_request_adapter.py#L629-L631). 1.9.3 still honors it.

    The error is only half of it. With a plain adapter the empty base URL turns into a relative request URL, so httpx fails with "Request URL is missing an 'http://' or 'https://' protocol." With the Azure auth provider it fails earlier with "Valid url scheme and host required", presumably the misleading error you saw (#470 had the same message).

    With a generated client there's no error at all. Every kiota-generated Python client fills an empty adapter base_url with its default server (msgraph's sets https://graph.microsoft.com/v1.0). So the URL passed to the constructor gets replaced and requests quietly go to a different host.

    The repro runs two local servers. "custom" is what gets passed as base_url, "default" stands in for a generated client's default server. Install with pip install microsoft-kiota-http==1.14.1 microsoft-kiota-authentication-azure==1.14.1.

    import asyncio
    import threading
    import time
    from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
    
    from azure.core.credentials import AccessToken
    from kiota_abstractions.authentication import AnonymousAuthenticationProvider
    from kiota_abstractions.method import Method
    from kiota_abstractions.request_information import RequestInformation
    from kiota_authentication_azure.azure_identity_authentication_provider import AzureIdentityAuthenticationProvider
    from kiota_http.httpx_request_adapter import HttpxRequestAdapter
    
    hits = []
    
    
    def serve(name):
        class Handler(BaseHTTPRequestHandler):
            def do_GET(self):
                hits.append(name + self.path)
                self.send_response(204)
                self.end_headers()
    
            def log_message(self, *args):
                pass
    
        server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
        threading.Thread(target=server.serve_forever, daemon=True).start()
        return f"http://127.0.0.1:{server.server_address[1]}"
    
    
    class FakeCredential:
        def get_token(self, *scopes, **kwargs):
            return AccessToken("fake", int(time.time()) + 3600)
    
    
    CUSTOM, DEFAULT = serve("custom"), serve("default")
    
    
    async def send(adapter):
        hits.clear()
        try:
            await adapter.send_no_response_content_async(RequestInformation(Method.GET, "{+baseurl}/ping", {}), None)
            return f"reached {hits}"
        except Exception as exc:
            return f"{type(exc).__name__}: {exc}"
    
    
    async def main():
        for auth in (AnonymousAuthenticationProvider(), AzureIdentityAuthenticationProvider(FakeCredential())):
            adapter = HttpxRequestAdapter(auth, base_url=CUSTOM)
            print(f"{type(auth).__name__}, adapter.base_url={adapter.base_url!r}")
            print(f"  plain adapter:    {await send(adapter)}")
            adapter = HttpxRequestAdapter(auth, base_url=CUSTOM)
            if not adapter.base_url:  # what every generated client constructor does
                adapter.base_url = DEFAULT
            print(f"  generated client: {await send(adapter)}")
            adapter = HttpxRequestAdapter(auth)
            adapter.base_url = CUSTOM
            print(f"  setter:           {await send(adapter)}")
    
    
    asyncio.run(main())

    Output on 1.14.1, and main gives the same:

    AnonymousAuthenticationProvider, adapter.base_url=''
      plain adapter:    UnsupportedProtocol: Request URL is missing an 'http://' or 'https://' protocol.
      generated client: reached ['default/ping']
      setter:           reached ['custom/ping']
    AzureIdentityAuthenticationProvider, adapter.base_url=''
      plain adapter:    HTTPError: Valid url scheme and host required
      generated client: reached ['default/ping']
      setter:           reached ['custom/ping']
    

    On 1.9.3 adapter.base_url is the custom URL and all six requests reach custom.

    #490 asks to remove the parameter, and the reply there asks whether it should be used instead. I'd go back to the pre-1.9.4 rule: an explicit base_url wins, otherwise take the client's. msgraph-core's BaseGraphRequestAdapter doesn't pass base_url, so that shouldn't bring back microsoftgraph/msgraph-beta-sdk-python#743, the bug #481 was fixing. I haven't run the beta SDK against it though. If removal is the plan, a DeprecationWarning when the argument is passed would at least end the silent part. Either way one PR can close this and #490, and I can send it once there's a call on the direction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Done ✔️

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions