From ca848115d2ea908eb220c4696842cf76129b53fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pere=20Pic=C3=B3?= Date: Wed, 30 Sep 2026 09:49:57 +0200 Subject: [PATCH] fix: make AsyncUnleashClient a top level import That way, users can do "from UnleashClients import AsyncUnleashClient" like they already do for the sync one. The shenanigans with typechecking are there so that aiohttp is not suddenly mandatory for users that are already importing the sync client. --- CHANGELOG.md | 1 + UnleashClient/__init__.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bf974f..a77f9998 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ > went on. It needs to be consolidated into what will eventually become the > final changelog + documentation changes (if any). +* (Minor): `AsyncUnleashClient` is now exported from the package root: `from UnleashClient import AsyncUnleashClient`. It still requires the `async` extra (`pip install UnleashClient[async]`), and importing it without `aiohttp` raises an `ImportError` asking for it. `import UnleashClient` keeps working without `aiohttp`, so nothing changes for code using `UnleashClient`. * (Bugfix): `UnleashClient` can be imported again without the optional `aiohttp` dependency. Importing the package used to fail with an `ImportError` asking for `UnleashClient[async]`, even for code that never touches the asynchronous client. Only the asynchronous client requires `aiohttp` now, and importing it without `aiohttp` still raises that `ImportError`. * (Minor): The in-progress asynchronous client now exposes `feature_definitions()`. It returns the same dict as `UnleashClient`, keyed by feature name with each toggle's `type` and `project`. It is a plain method, not a coroutine, and it does not wait for the server: before the first fetch it answers from the cached state, and without any state it returns an empty dict. Every method on the client is now implemented. The client is still unexported. Nothing changes for code using `UnleashClient`. * (Minor): The in-progress asynchronous client can now resolve variants. `get_variant()` returns the same variant dict as `UnleashClient` and emits the same impression events. It is a plain method, not a coroutine, and it does not wait for the server: before the first fetch it answers from the cached state, and a toggle the client does not know resolves to the disabled variant. When an initialized client is asked for a toggle it does not know, it logs at `verbose_log_level` that the client does not know the toggle. `feature_definitions()` still raises `NotImplementedError`. The client is still unexported. Nothing changes for code using `UnleashClient`. diff --git a/UnleashClient/__init__.py b/UnleashClient/__init__.py index 018f0605..8d6f36d9 100644 --- a/UnleashClient/__init__.py +++ b/UnleashClient/__init__.py @@ -1,7 +1,20 @@ # ruff: noqa: F401 +from typing import TYPE_CHECKING + from .clients import ( INSTANCES, ExperimentalMode, UnleashClient, build_ready_callback, ) + +if TYPE_CHECKING: + from .clients.async_unleash_client import AsyncUnleashClient + + +def __getattr__(name: str): + if name == "AsyncUnleashClient": + from .clients.async_unleash_client import AsyncUnleashClient # noqa: PLC0415 + + return AsyncUnleashClient + raise AttributeError(f"module {__name__!r} has no attribute {name!r}")