|
| 1 | +"""``client.models`` — the model namespace on the existing client. |
| 2 | +
|
| 3 | +What makes it a namespace rather than a second client object: it is reachable |
| 4 | +from a client you already constructed, and it reads that client's *live* |
| 5 | +configuration — credentials, base URL, transport, timeout — instead of a copy |
| 6 | +taken at construction. Both properties are asserted here, including a config |
| 7 | +change made on the client after construction being visible through ``models``. |
| 8 | +""" |
| 9 | + |
| 10 | +from __future__ import annotations |
| 11 | + |
| 12 | +import httpx |
| 13 | + |
| 14 | +import comfy_sdk |
| 15 | +from comfy_sdk import AsyncComfy, Comfy |
| 16 | +from comfy_sdk.models import AsyncModels, Models |
| 17 | + |
| 18 | + |
| 19 | +def test_models_is_reachable_from_a_constructed_client(server) -> None: |
| 20 | + with Comfy() as client: |
| 21 | + assert isinstance(client.models, Models) |
| 22 | + |
| 23 | + |
| 24 | +async def test_async_models_is_reachable_from_a_constructed_client(server) -> None: |
| 25 | + async with AsyncComfy() as client: |
| 26 | + assert isinstance(client.models, AsyncModels) |
| 27 | + |
| 28 | + |
| 29 | +def test_models_holds_the_host_clients_transport(server) -> None: |
| 30 | + # Identity, not equality: the same transport object means the same |
| 31 | + # connection pool, credential, base URL and timeout — nothing to keep in |
| 32 | + # sync, and nothing a second client would have forked. |
| 33 | + with Comfy() as client: |
| 34 | + assert client.models._low is client._low |
| 35 | + |
| 36 | + |
| 37 | +async def test_async_models_holds_the_host_clients_transport(server) -> None: |
| 38 | + async with AsyncComfy() as client: |
| 39 | + assert client.models._low is client._low |
| 40 | + |
| 41 | + |
| 42 | +def test_models_reports_the_host_clients_base_url(server) -> None: |
| 43 | + with Comfy() as client: |
| 44 | + assert client.models.base_url == client._low.base_url == server.base_url |
| 45 | + |
| 46 | + |
| 47 | +async def test_async_models_reports_the_host_clients_base_url(server) -> None: |
| 48 | + async with AsyncComfy() as client: |
| 49 | + assert client.models.base_url == client._low.base_url == server.base_url |
| 50 | + |
| 51 | + |
| 52 | +def test_a_timeout_change_on_the_client_is_visible_through_models(server) -> None: |
| 53 | + with Comfy(timeout=30.0) as client: |
| 54 | + assert client.models.timeout.read == 30.0 |
| 55 | + # Changed on the client *after* construction: models must follow it, |
| 56 | + # which a copied-config namespace would not. |
| 57 | + client._low._client.timeout = httpx.Timeout(1.25) |
| 58 | + assert client.models.timeout.read == 1.25 |
| 59 | + |
| 60 | + |
| 61 | +async def test_a_timeout_change_on_the_async_client_is_visible_through_models(server) -> None: |
| 62 | + async with AsyncComfy(timeout=30.0) as client: |
| 63 | + assert client.models.timeout.read == 30.0 |
| 64 | + client._low._client.timeout = httpx.Timeout(1.25) |
| 65 | + assert client.models.timeout.read == 1.25 |
| 66 | + |
| 67 | + |
| 68 | +def test_models_sends_the_host_clients_credentials(server) -> None: |
| 69 | + server.state.require_auth = True |
| 70 | + with Comfy(api_key="k-first") as client: |
| 71 | + # The transport a model request would go out on is the client's own, |
| 72 | + # so it carries the client's bearer token to the client's base URL. |
| 73 | + client.models._low.get_job("job_01") |
| 74 | + assert server.state.last_auth_header == "Bearer k-first" |
| 75 | + |
| 76 | + # And a credential rotated on the client is picked up through models. |
| 77 | + client._low._p.api_key = "k-rotated" |
| 78 | + client.models._low.get_job("job_01") |
| 79 | + assert server.state.last_auth_header == "Bearer k-rotated" |
| 80 | + |
| 81 | + |
| 82 | +async def test_async_models_sends_the_host_clients_credentials(server) -> None: |
| 83 | + server.state.require_auth = True |
| 84 | + async with AsyncComfy(api_key="k-async") as client: |
| 85 | + await client.models._low.get_job("job_01") |
| 86 | + assert server.state.last_auth_header == "Bearer k-async" |
| 87 | + |
| 88 | + |
| 89 | +def test_two_clients_get_independent_namespaces(server) -> None: |
| 90 | + with Comfy() as one, Comfy() as two: |
| 91 | + assert one.models is not two.models |
| 92 | + assert one.models._low is not two.models._low |
| 93 | + |
| 94 | + |
| 95 | +def test_models_repr_names_the_shared_base_url(server) -> None: |
| 96 | + with Comfy() as client: |
| 97 | + assert repr(client.models) == f"Models(base_url={server.base_url!r})" |
| 98 | + |
| 99 | + |
| 100 | +def test_the_namespace_adds_no_new_top_level_import_path() -> None: |
| 101 | + # ``from comfy_sdk import Comfy`` stays the only path a caller needs: |
| 102 | + # the namespace is reached as ``client.models``, so neither class is |
| 103 | + # exported at the top level. |
| 104 | + for name in ("Models", "AsyncModels"): |
| 105 | + assert name not in comfy_sdk.__all__ |
| 106 | + assert not hasattr(comfy_sdk, name) |
0 commit comments