Metadata-Version: 2.4
Name: istari-digital-client
Version: 13.2.0
Summary: Python client for the Istari Digital platform
License: MIT License
         
         Copyright (c) 2025 Istari Digital, Inc.
         
         Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
         No license is hereby implied or granted to any patent or patent application relating to the Istari Digital platform itself. The list of patents applicable to the Istari Digital platform may be found at istaridigital.com/patent-list.
License-File: LICENSE
Author: Istari Digital Developers
Requires-Python: >=3.10,<4.0
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Provides-Extra: fips
Provides-Extra: s3
Provides-Extra: s3-crt
Requires-Dist: awscrt (>=0.19.0) ; extra == "s3-crt"
Requires-Dist: boto3 (>=1.26.0) ; extra == "s3" or extra == "s3-crt"
Requires-Dist: cryptography (>=49.0.0)
Requires-Dist: deprecation (>=2.1.0,<3.0.0)
Requires-Dist: inflection (>=0.5.1,<0.6.0)
Requires-Dist: istari-digital-core (==7.3.2)
Requires-Dist: istari-digital-core-fips (==7.3.2) ; extra == "fips"
Requires-Dist: pydantic (>=2.9.0,<3.0.0)
Requires-Dist: pyjwt (>=2.15.1,<3.0.0)
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
Requires-Dist: types-python-dateutil (>=2.9.0.20240906,<3.0.0.0)
Requires-Dist: typing-extensions (>=4.12.2,<5.0.0)
Requires-Dist: urllib3 (>=2.8.0,<3.0.0)
Project-URL: Documentation, https://docs.istaridigital.com
Project-URL: Homepage, https://istaridigital.com
Description-Content-Type: text/markdown

# Istari Digital Client

The `istari-digital-client` library is a client SDK for interacting with the [Istari Digital platform](https://www.istaridigital.com).

- **Install:** `pip install istari-digital-client`
- **Documentation and Usage:** Please see [docs.istaridigital.com](https://docs.istaridigital.com).
- **Supported Versions:** This library supports Python 3.10, 3.11, 3.12, and 3.14
- **License:** This library is released under an MIT license with the following clarification:

> No license is hereby implied or granted to any patent or patent application relating to the Istari Digital platform itself. The list of patents applicable to the Istari Digital platform may be found at istaridigital.com/patent-list.

## Direct S3 Upload

For environments with direct access to the backing S3 bucket, the client can bypass presigned URLs and upload via `boto3` instead. This can improve throughput and simplifies large-file handling (boto3 manages multipart automatically).

Install the optional dependency:

```bash
# S3-compatible storage (minio, AWS, etc.)
pip install istari-digital-client[s3]

# AWS with high-speed CRT transfers (recommended for EC2 containers in same s3 environment as the data plane buckets)
pip install istari-digital-client[s3-crt]
```

Then configure via environment variables or constructor arguments:

| Environment Variable | Constructor Arg | Description |
|---|---|---|
| `ISTARI_CLIENT_S3_DIRECT_UPLOAD` | `s3_direct_upload_enabled` | Set to `true` to enable direct S3 uploads |
| `ISTARI_CLIENT_S3_BUCKET_NAME` | `s3_bucket_name` | Target S3 bucket name (required when enabled) |

Standard AWS credentials (environment variables, profile, or instance role) must be available for `boto3` to authenticate.

## Proxy Support

The client honors the conventional proxy environment variables — `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and `NO_PROXY` (upper or lower case; lowercase wins) — for all of its HTTP traffic, including presigned object-store downloads. No configuration is needed on a host that already has these set.

There are two common proxy shapes:

- **CONNECT tunneling** (the proxy passes TLS through untouched): proxy settings alone are enough.
- **TLS termination** (the proxy re-signs TLS with its own certificate authority): additionally provide the CA bundle that trusts the proxy's CA, via `REQUESTS_CA_BUNDLE` or `SSL_CERT_FILE`, or the `ca_bundle` setting. Certificate verification is always required — there is no insecure mode.

| Environment Variable | Constructor Arg | Description |
|---|---|---|
| `ISTARI_CLIENT_PROXY_URL` | `proxy_url` | Explicit proxy for HTTP(S) traffic; overrides `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`. Credentials may be embedded (`http://<user>:<password>@proxy:8080`) and are sent as a `Proxy-Authorization` header. |
| `ISTARI_CLIENT_CA_BUNDLE` | `ca_bundle` | PEM CA bundle for TLS verification; overrides `REQUESTS_CA_BUNDLE`/`SSL_CERT_FILE`. |
| `ISTARI_CLIENT_TRUST_ENV` | `trust_env` | Set to `false` to ignore the proxy and CA environment variables entirely (explicit settings above still apply). Defaults to `true`. |

`NO_PROXY` is honored with exact and dot-boundary suffix matching (`NO_PROXY=internal.example` bypasses the proxy for `registry.internal.example`), including when `proxy_url` is set explicitly. When the Istari control plane is reached over a direct route (for example a site-to-site VPN) while object storage requires the proxy, list the control-plane host in `NO_PROXY`.

SOCKS proxies are not supported: an explicit SOCKS `proxy_url` is rejected with an error, while a SOCKS URL arriving via environment variables (for example `ALL_PROXY=socks5://...`) is ignored with a warning and the client connects directly — it does **not** route traffic through the SOCKS proxy.

## Identity-service API version

`Configuration.identity_api_version` selects which identity-service API the client uses, when identity-service is enabled (see "Choosing identity-service or a personal access token" below), for its token and for `IstariAdmin.identity`. The default, `auto`, detects the version, so most callers leave it unset.

| Environment Variable | Constructor Arg | Description |
|---|---|---|
| `ISTARI_IDENTITY_API_VERSION` | `identity_api_version` | `auto` (default), `v1` or `v2`; see below. |

The constructor argument takes an `IdentityApiVersion` member (`AUTO`, `V1` or `V2`) or its string, so existing code passing `"auto"`, `"v1"` or `"v2"` keeps working. Import the enum from `istari_digital_client`, `istari_digital_client.sdk`, `istari_digital_client.legacy` or `istari_digital_client.identity`. `identity_api_version` holds the member once the configuration is built, and `resolved_identity_api_version()`, `IdentityServiceClient.api_version` and `IdentityServiceClient.configured_api_version` return members. Members compare equal to their strings, and `str()` and f-strings give the bare value.

- `auto` sends nothing when the configuration is built. The first token fetch tries `/api/v2/oauth2/token`; if that answers 404 with a body lacking identity-service's `error` field, the identity-service does not serve `/api/v2`, and the client uses `/oauth2/token` and v1 from then on. The result holds for that configuration once the v2 endpoint returns a token or answers that 404. Any other failure, such as a 401, a 5xx or a timeout, is raised as `IdentityServiceError` rather than read as "v1 only", leaves `auto` undecided, and the next token fetch checks again.
- `v2` issues tokens at `/api/v2/oauth2/token`, and `v1` at `/oauth2/token`. Either explicit value skips the check.
- `IstariAdmin.identity` (tenants, memberships, principals, keys, roles, OAuth) works when the version in use is v2, and raises `ConfigurationError` when it is v1. Under `auto`, a call made before the version is decided fetches a token first.
- `Configuration.resolved_identity_api_version()` and `IdentityServiceClient.api_version` report the version in use. If `auto` has not been decided yet they fetch a token first, and raise `IdentityServiceError` if that fetch fails.
- `IdentityServiceClient` built directly defaults to `v2` without detection. Pass `api_version="auto"` to detect, or `api_version="v1"` to use `/oauth2/token`.
- An explicit argument wins over the environment variable. A value other than `auto`, `v1` or `v2` raises `ConfigurationError` when the configuration is built.

### The deprecated key surface

`IstariAdmin.keys`, `client.keys` on the legacy clients, and the `istari_digital_client.identity.v1` helpers work under every setting, and callers do not need to set `identity_api_version` or `api_version` for them. Key management needs a client configured with identity-service credentials; the PAT exchange needs only the personal access token. Their key-management and exchange methods take `api_version`, which defaults to `"auto"` and accepts an `IdentityApiVersion` member or its string; `None` also means `"auto"`, and any other value raises `ConfigurationError`.

A key-management call on a v1 route always carries a token from the v1 token endpoint, `/oauth2/token`, minted from the client's identity-service credentials, because identity-service's v1 key routes accept a v2 token only for `"me"`. Results have the same types whichever route serves a call; a v2 key listing's `principal_id` and `active` arrive as `PrincipalKeys.user_uuid` and `enabled`. Each operation warns once per process with a `DeprecationWarning`, and only when a call reaches a v1 route.

Under `"auto"`, key management (`register_key`, `list_keys`, `get_key`, `revoke_key`, `generate_keypair_and_register`) works as follows:

- When the version in use is v1, every call uses the v1 routes.
- When it is v2, a principal id other than `"me"` is treated as a v1 id, so ids that worked against v1 keep working: the call first looks it up over v2, by `client_id` with `kind=AGENT` (agent client ids are often UUIDs that are not principal ids), otherwise by `upstream_user_id`. Unless an exception in the next item applies, a call uses the v2 key routes (`/api/v2/principals/{id}/keys`) for:
  - `"me"`, except with `kind=AGENT`;
  - an id the lookup resolves to exactly one principal;
  - a UUID in hyphenated 8-4-4-4-12 form that the lookup matches to no principal, used as the principal UUID.
- When it is v2, these calls use the v1 routes, with a v1 token, instead:
  - a register call with `username` or `display_name`, which only v1 accepts, to seed an agent that does not exist yet when an administrator registers its first key;
  - an id that is not a UUID and that the lookup matches to no principal, an id it matches to several, or a lookup refused with a 403; the id goes to v1 unchanged;
  - an id the lookup matches to one person whose holder id (the id the person's keys authenticate as) differs from the id passed;
  - `"me"` with `kind=AGENT` (`client.keys` rejects this with `ValueError` under every `api_version`).

  The `username`/`display_name` and `"me"`-with-`kind=AGENT` cases also log a warning through the SDK logger on every call, naming the argument.
- Any other lookup failure, such as a 401, a 5xx or a timeout, raises `KeyRegistrationError`.
- A 404 from a v2 key route raises `KeyRegistrationError`; there is no v1 retry. Against an identity-service that issues v2 tokens but lacks the v2 key routes, pass `api_version="v1"`.
- `generate_keypair_and_register` served by v2 lists the principal's keys first, so it can bind the returned credentials to the client id the key authenticates as.

The PAT exchange (`exchange_pat`, `generate_keypair_and_exchange`) uses v1 with the personal access token under `"auto"` and `"v1"`, and raises `ConfigurationError` under `"v2"`; identity-service has no v2 exchange.

The explicit values override the routing:

- `"v1"` uses the v1 routes with a v1 token, under any configuration.
- `"v2"` uses the v2 routes only and never falls back to v1. It raises `ConfigurationError` before any request for a register call with `username` or `display_name`, for `"me"` with `kind=AGENT`, and for the PAT exchange. An id the lookup cannot resolve raises `KeyRegistrationError`: `code` `"principal_not_found"` with `status` 404 when no principal visible to the caller matches (it may not exist, or the caller may lack permission to see it) or, for a person, when the matched principal's key holder is not the id passed, and `code` `"principal_ambiguous"` when several do.

## Choosing identity-service or a personal access token

Identity-service authentication is opt-in. A client without identity-service credentials authenticates to the registry with a personal access token. Configuring an identity-service secret turns identity-service on while `identity_service_enabled` (`ISTARI_DIGITAL_IDENTITY_SERVICE_ENABLED`) is unset and an identity-service URL is configured (see below), unless a personal access token is passed as an argument and the secret comes only from the environment (case 4); a personal access token alone never turns it on. `identity_service_required` (`ISTARI_DIGITAL_IDENTITY_SERVICE_REQUIRED`, default false) requires identity-service and raises `ConfigurationError` if it cannot be used. With identity-service on, the client uses identity-service's v2 API unless you select v1 (see `identity_api_version` above).

- A secret is `identity_service_secret` / `ISTARI_CLIENT_IDENTITY_SERVICE_SECRET` or `identity_service_secret_file` / `ISTARI_CLIENT_IDENTITY_SERVICE_SECRET_FILE`.
- A personal access token is `registry_auth_token` / `ISTARI_REGISTRY_AUTH_TOKEN`.

The first case that applies wins. Cases 4 to 7 apply once identity-service is on: `identity_service_enabled` is true, or it is unset and a secret and an identity-service URL are configured.

1. **Required** (`ISTARI_DIGITAL_IDENTITY_SERVICE_REQUIRED=true`): identity-service, regardless of `identity_service_enabled`. Without a secret, construction raises `ConfigurationError` instead of using the personal access token.
2. **Disabled** (`identity_service_enabled=False`, or `ISTARI_DIGITAL_IDENTITY_SERVICE_ENABLED` or `ISTARI_CLIENT_IDENTITY_SERVICE_ENABLED` set to false; when both are set, the `ISTARI_DIGITAL_` one decides): the personal access token, even when a secret is configured. With no token, the legacy `Client` raises `ConfigurationError` at construction; the SDK `Configuration` builds and sends no credentials.
3. **Unset, and no secret or no identity-service URL:** the personal access token. A secret without a URL logs one warning naming `ISTARI_DIGITAL_API_URL` and `ISTARI_IDENTITY_URL`, except when the secret comes only from the environment and a personal access token is passed as an argument. With no token, the legacy `Client` raises `ConfigurationError` at construction; the SDK `Configuration` builds and sends no credentials.
4. **Secret configured, personal access token passed as an argument, and the secret comes only from the environment:** the personal access token. This is logged at debug level. A client given one user's token keeps using it even when the process environment holds a secret.
5. **Secret configured:** identity-service. This includes a secret and a personal access token that both come from the environment. A secret that is unreadable or malformed raises `ConfigurationError`.
6. **No secret, personal access token** (`identity_service_enabled` set to true): the personal access token. The fallback is logged at debug level.
7. **No secret, no token** (`identity_service_enabled` set to true): construction raises `ConfigurationError`.

Passing `None` for a credential argument leaves it unset instead of reading its environment variable.

When the client uses identity-service, it signs in with an auto-refreshed identity-service token and needs `ISTARI_DIGITAL_API_URL` (the gateway) or `ISTARI_IDENTITY_URL` (identity-service directly). While `identity_service_enabled` is unset, a secret with neither leaves identity-service off. With `identity_service_required`, or with `identity_service_enabled` true and a secret that no personal access token argument overrides, a missing URL raises `ConfigurationError`. Each setting also accepts an `ISTARI_CLIENT_` alias (`ISTARI_CLIENT_IDENTITY_SERVICE_ENABLED`, `ISTARI_CLIENT_IDENTITY_SERVICE_REQUIRED`); when both are set, the `ISTARI_DIGITAL_` name wins. To force identity-service's v1 API, set `ISTARI_IDENTITY_API_VERSION=v1`; the default `auto` switches to v1 automatically when identity-service does not serve `/api/v2`.

## Contributing

See the [contributing doc](./CONTRIBUTING.md) for additional info.

