Configuration, Transport and Errors
smarter.core.conf
The configuration of the Smarter SDK.
A client needs an api key and the base url of a Smarter platform. Each is taken from the first of these that sets it:
the arguments of
smarter.Smarter,environment variables:
SMARTER_API_KEY,SMARTER_BASE_URL,SMARTER_ENVIRONMENT,SMARTER_ROOT_DOMAIN,SMARTER_TIMEOUT,SMARTER_MAX_RETRIESandSMARTER_LOG(SmarterSettings),the
smartercli’s configuration file,~/.smarter/config.yaml(SMARTER_CONFIG_FILE), so that a developer who has runsmarter configureneeds no other setup,the defaults: the
prodenvironment ofplatform.smarter.sh.
Without an explicit base url, the url is derived from the environment as the cli derives it:
local is http://localhost:9357, alpha, beta and next are
https://<environment>.<root domain>, and prod is https://<root domain>.
This follows the Smarter platform’s settings design (ADR-007): one validated Pydantic settings model is the only place that reads the environment.
- smarter.core.conf.SmarterEnvironment
The environments of the smarter cli’s configuration file.
- smarter.core.conf.LOCAL_URL
The url of the Smarter platform’s local development server.
- smarter.core.conf.API_PATH
The path of the Smarter api, which every request url is relative to.
- smarter.core.conf.DEFAULT_TIMEOUT
Seconds.
Prompts that call several tools can take tens of seconds.
- class smarter.core.conf.SmarterSettings
Bases:
pydantic_settings.main.BaseSettingsThe SDK’s settings, from
SMARTER_*environment variables.- api_key: Optional
A Smarter api key: 64 characters.
- base_url: Optional
- environment: Optional
- root_domain: Optional
The platform’s root domain, e.g. platform.smarter.sh.
- config_file: Path
- log: Optional
Log the SDK’s requests to stderr.
- class smarter.core.conf.CliConfig
Bases:
pydantic.main.BaseModelThe parts of the smarter cli’s
~/.smarter/config.yamlthat the SDK reads.- environment: Optional
- root_domain: Optional
- api_key: Optional
The top-level api_key, used by any environment.
- classmethod load(path: pathlib.Path) 'CliConfig'
Read the cli’s configuration file.
A missing or unreadable file is an empty configuration: the file is optional.
- Parameters:
path – The file’s path.
- Returns:
The configuration.
- class smarter.core.conf.ClientConfig
Bases:
pydantic.main.BaseModelA client’s resolved configuration.
- api_key: SecretStr
- environment: Literal
- smarter.core.conf.environment_url(environment: Literal['local', 'alpha', 'beta', 'next', 'prod'], root_domain: str) str
The platform url of an environment, as the smarter cli derives it.
- Parameters:
environment – The environment.
root_domain – The platform’s root domain, e.g.
platform.smarter.sh.
- Returns:
e.g.
https://alpha.platform.smarter.sh.
- smarter.core.conf.api_base_url(url: str) str
The api’s base url for a platform url.
https://example.comandhttps://example.com/api/v1both becomehttps://example.com/api/v1/.- Parameters:
url – A platform url, with or without the api’s path.
- Returns:
The url, with the api’s path and a trailing slash.
- Raises:
SmarterConfigurationError – If the url is not an http or https url.
- smarter.core.conf.resolve_client_config(*, api_key: str | None = None, base_url: str | None = None, environment: Literal['local', 'alpha', 'beta', 'next', 'prod'] | None = None, timeout: float | None = None, max_retries: int | None = None, settings: smarter.core.conf.SmarterSettings | None = None) smarter.core.conf.ClientConfig
Resolve a client’s configuration from its arguments, the environment, and the cli’s configuration file.
- Parameters:
api_key – The api key, or None to look it up.
base_url – The platform’s url, or None to derive it from the environment.
environment – The environment, or None to look it up.
timeout – The request timeout in seconds, or None for the setting.
max_retries – How many times to retry a failed request, or None for the setting.
settings – The settings to use instead of reading the environment variables, for tests.
- Returns:
The configuration.
- Raises:
SmarterConfigurationError – If no api key is found, or the base url is invalid.
smarter.core.exceptions
The exceptions of the Smarter Python SDK.
Every exception that the SDK raises is a SmarterError, so a caller can catch them all with
one except clause. This follows the Smarter platform’s own rule (ADR-012) that exceptions
subclass one hierarchy.
The api errors mirror the http status that the Smarter api returned:
SmarterError
├── SmarterConfigurationError the client has no api key or base url
├── SmarterValueError an argument is invalid, before any request is sent
├── ResourceNotFoundError a resource was not found, by the api (NotFoundError) or by find()
└── APIError
├── APIConnectionError the request never got a response
│ └── APITimeoutError
├── APIResponseValidationError the response does not match the SDK's model of it
└── APIStatusError the api returned a 4xx or 5xx status
├── BadRequestError 400
├── AuthenticationError 401
├── PermissionDeniedError 403
├── NotFoundError 404, also a ResourceNotFoundError
├── ConflictError 409
├── UnprocessableEntityError 422
├── RateLimitError 429
└── InternalServerError 5xx
- class smarter.core.exceptions.SmarterError
Bases:
builtins.ExceptionThe base class of every exception that the Smarter SDK raises.
- class smarter.core.exceptions.SmarterConfigurationError
Bases:
smarter.core.exceptions.SmarterErrorThe client is missing configuration that it needs, e.g. an api key or a base url.
- class smarter.core.exceptions.SmarterValueError
Bases:
smarter.core.exceptions.SmarterError,builtins.ValueErrorAn argument is invalid.
Raised before a request is sent.
- class smarter.core.exceptions.ResourceNotFoundError
Bases:
smarter.core.exceptions.SmarterError,builtins.LookupErrorA resource was not found: the api answered 404 (
NotFoundError), orfind()found no resource of that name.
- class smarter.core.exceptions.APIError(message: str, request: httpx.Request, *, body: object = None) None
Bases:
smarter.core.exceptions.SmarterErrorA request to the Smarter api failed.
- Parameters:
message – A human-readable description of the error.
request – The http request that failed.
body – The response’s parsed JSON body, or its text, or None when there is no response.
- class smarter.core.exceptions.APIConnectionError(request: httpx.Request, message: str = 'Connection error.') None
Bases:
smarter.core.exceptions.APIErrorThe request did not get a response, e.g. the server is unreachable.
- class smarter.core.exceptions.APITimeoutError(request: httpx.Request) None
Bases:
smarter.core.exceptions.APIConnectionErrorThe request timed out.
- class smarter.core.exceptions.APIResponseValidationError(response: httpx.Response, body: object, message: str) None
Bases:
smarter.core.exceptions.APIErrorThe api’s response does not match the SDK’s model of it.
This usually means that the SDK and the Smarter platform are of incompatible versions.
- Parameters:
response – The http response.
body – The response’s parsed JSON body.
message – A description of the mismatch.
- class smarter.core.exceptions.APIStatusError(message: str, *, response: httpx.Response, body: object, error: ForwardRef('SAMJournalApiErrorBody') | None = None) None
Bases:
smarter.core.exceptions.APIErrorThe api returned a 4xx or 5xx status.
- Parameters:
message – The error’s description, from the response when it has one.
response – The http response.
body – The response’s parsed JSON body, or its text.
error – The Smarter Journal error body, when the response is a journaled error.
- property error_class
The name of the server-side exception class, e.g.
SAMBrokerErrorNotFound, when the platform sent it.
- class smarter.core.exceptions.BadRequestError
Bases:
smarter.core.exceptions.APIStatusError400: the request is invalid, e.g. a malformed manifest.
- class smarter.core.exceptions.AuthenticationError
Bases:
smarter.core.exceptions.APIStatusError401: the api key is missing, invalid, or inactive.
- class smarter.core.exceptions.PermissionDeniedError
Bases:
smarter.core.exceptions.APIStatusError403: the api key’s user may not perform the operation.
- class smarter.core.exceptions.NotFoundError
Bases:
smarter.core.exceptions.APIStatusError,smarter.core.exceptions.ResourceNotFoundError404: the resource does not exist, or the api key’s user may not read it.
- class smarter.core.exceptions.ConflictError
Bases:
smarter.core.exceptions.APIStatusError409: the request conflicts with the resource’s current state.
- class smarter.core.exceptions.UnprocessableEntityError
Bases:
smarter.core.exceptions.APIStatusError422: the request is well-formed but cannot be processed.
- class smarter.core.exceptions.RateLimitError
Bases:
smarter.core.exceptions.APIStatusError429: too many requests.
- class smarter.core.exceptions.InternalServerError
Bases:
smarter.core.exceptions.APIStatusError5xx: the platform failed to process the request.
smarter.core.http
The SDK’s http transport: authentication, retries, error mapping, and response parsing.
SmarterHttpClient sends every request of a smarter.Smarter client. It adds the
api key and the SDK’s headers, retries requests that failed for transient reasons, raises a
smarter.core.exceptions error for a 4xx or 5xx response, and validates the response’s
JSON against a Pydantic model.
- smarter.core.http.QueryParamValue
A query parameter’s value.
None leaves the parameter out.
- smarter.core.http.CLIENT_NAME
The SDK’s
X-Smarter-Clientheader, which identifies it to the platform’s logs and journal.
- smarter.core.http.parse_body(response: httpx.Response) object
A response’s body: its JSON, or its text when it is not JSON.
- Parameters:
response – The response, which must have been read.
- Returns:
The parsed JSON, the text, or None for an empty body.
- smarter.core.http.journal_error(body: object) smarter.models.journal.SAMJournalApiErrorBody | None
The Smarter Journal error of a response body, if it is a journaled error response.
- Parameters:
body – A parsed response body.
- Returns:
The error body, or None.
- smarter.core.http.error_message(response: httpx.Response, body: object, error: smarter.models.journal.SAMJournalApiErrorBody | None) str
A human-readable message for an error response.
The platform’s views return errors in several shapes: the journal’s
errorobject, anerrorstring, Django REST framework’sdetail, plain text, or a Django error page.- Parameters:
response – The response.
body – Its parsed body.
error – Its journal error, if any.
- Returns:
The message.
- smarter.core.http.make_status_error(response: httpx.Response) smarter.core.exceptions.APIStatusError
The exception for a 4xx or 5xx response, by its status.
- Parameters:
response – The response, which must have been read.
- Returns:
e.g. a
NotFoundErrorfor a 404.
- smarter.core.http.is_retryable(response: httpx.Response) bool
True if a failed response may succeed when the request is sent again.
A 503 is retried only when it is not a journaled response. The Smarter platform itself answers 503 for a resource that is not ready (e.g.
SAMBrokerErrorNotReady), which a retry won’t change, whereas a 503 from a load balancer or proxy means the platform is briefly unavailable.- Parameters:
response – The response, which must have been read.
- Returns:
True to retry.
- smarter.core.http.retry_delay(attempt: int, response: httpx.Response | None = None) float
Seconds to wait before a retry: the response’s
Retry-After, or exponential backoff with jitter.- Parameters:
attempt – The number of the attempt that failed, from 0.
response – The failed response, if there was one.
- Returns:
The delay.
- smarter.core.http.query_params(params: collections.abc.Mapping[str, str | int | float | bool | NoneType] | None) dict[str, str]
Query parameters as strings: booleans as
trueorfalse, and None values left out.- Parameters:
params – The parameters.
- Returns:
The parameters that httpx sends.
- class smarter.core.http.SmarterHttpClient(config: smarter.core.conf.ClientConfig, *, default_headers: collections.abc.Mapping[str, str] | None = None, http_client: httpx.Client | None = None) None
Sends the requests of a Smarter client.
- Parameters:
config – The client’s resolved configuration.
default_headers – Headers to add to every request.
http_client – An
httpx.Clientto use, e.g. with a proxy or a test transport. Its own base url and timeout are ignored.
- property base_url
The api’s base url, e.g.
https://platform.smarter.sh/api/v1/.
- property max_retries
How many times a failed request is retried.
- url(path: str) str
The absolute url of an api path.
- Parameters:
path – A path relative to the base url, e.g.
cli/whoami/. An absolute url is returned as is.- Returns:
The url.
- headers(extra: collections.abc.Mapping[str, str] | None = None) dict[str, str]
The headers of a request.
- Parameters:
extra – Headers for this request, which override the defaults.
- Returns:
The headers.
- build_request(method: str, path: str, *, params: collections.abc.Mapping[str, str | int | float | bool | NoneType] | None = None, json: object = None, content: str | bytes | NoneType = None, files: collections.abc.Mapping[str, Any] | None = None, data: collections.abc.Mapping[str, Any] | None = None, headers: collections.abc.Mapping[str, str] | None = None, timeout: float | None = None) httpx.Request
Build a request.
See
request()for the parameters.- Returns:
The request.
- send(request: httpx.Request, *, stream: bool = False) httpx.Response
Send a request, with retries, and raise for an error status.
- Parameters:
request – The request.
stream – True to return before the body is read, for Server-Sent Events. The caller must close the response.
- Returns:
The successful response.
- Raises:
APIConnectionError – If no response was received after the retries.
APITimeoutError – If the request timed out after the retries.
APIStatusError – For a 4xx or 5xx response, as the status’s subclass.
- request(method: str, path: str, *, cast_to: Any, params: collections.abc.Mapping[str, str | int | float | bool | NoneType] | None = None, json: object = None, content: str | bytes | NoneType = None, files: collections.abc.Mapping[str, Any] | None = None, data: collections.abc.Mapping[str, Any] | None = None, headers: collections.abc.Mapping[str, str] | None = None, timeout: float | None = None) Any
Send a request, and validate its JSON response.
- Parameters:
method – The http method, e.g.
GET.path – The path, relative to the api’s base url, e.g.
guardrails/.cast_to – The type of the response’s JSON, e.g.
Guardrailorlist[Guardrail].Noneignores the body.params – Query parameters.
json – A JSON body.
content – A raw body, e.g. a YAML manifest.
files – Files, for a multipart/form-data body.
data – Form fields, for a multipart/form-data body.
headers – Headers for this request.
timeout – The timeout in seconds, instead of the client’s.
- Returns:
The validated response, an instance of
cast_to.- Raises:
APIResponseValidationError – If the response does not match
cast_to.
- stream_events(method: str, path: str, *, json: object = None, headers: collections.abc.Mapping[str, str] | None = None, timeout: float | None = None) collections.abc.Iterator[collections.abc.Iterator[smarter.core.streaming.ServerSentEvent]]
Send a request that asks for Server-Sent Events, and iterate its events.
with http.stream_events("POST", "llm-clients/5/prompt/", json=body) as events: for event in events: ...
- Parameters:
method – The http method.
path – The path, relative to the api’s base url.
json – A JSON body.
headers – Headers for this request.
timeout – The timeout in seconds, instead of the client’s. It applies to each read.
- Returns:
A context manager whose value iterates the events. The response is closed on exit.
smarter.core.streaming
A parser of Server-Sent Events.
The Smarter prompt api streams a prompt’s progress as Server-Sent Events when the request’s
Accept header includes text/event-stream: progress events while it runs, then one
result event with the same JSON as a non-streaming response.
- class smarter.core.streaming.ServerSentEvent(event: str | None, data: str, id: str | None = None, retry: int | None = None) None
One Server-Sent Event.
- Parameters:
event – The event’s type, e.g.
progress, or None for an unnamed event.data – The event’s data: its
data:lines, joined by newlines.id – The event’s id, if any.
retry – The reconnection time that the server asked for, in milliseconds, if any.
- json() JsonValue
The event’s data, parsed as JSON.
- Returns:
The parsed data.
- Raises:
json.JSONDecodeError – If the data is not JSON.
- smarter.core.streaming.iter_sse(lines: collections.abc.Iterable[str]) collections.abc.Iterator[smarter.core.streaming.ServerSentEvent]
Parse a stream’s lines into events, as the HTML Living Standard specifies.
A blank line ends an event. Comments (lines that start with
:), and blocks that set onlyretry, yield nothing.- Parameters:
lines – The stream’s lines, without their line endings.
- Returns:
The events, in order.
smarter.core.log
Logging for the Smarter SDK.
The SDK logs to the smarter logger hierarchy (smarter.client, smarter.resources...) and,
as a library should, adds no handlers unless asked to. Set SMARTER_LOG=info to log each
request’s method, url, status and duration to stderr, or SMARTER_LOG=debug to log their bodies
too. Or configure the smarter logger as you would any other.
The Smarter platform controls its logging per module with waffle switches (ADR-011). The SDK’s
equivalent is the logger hierarchy: e.g. logging.getLogger("smarter.client").setLevel(...).
Api keys are never logged: see mask_secret().
smarter.core.typing
Type aliases of the Smarter SDK.
These mirror smarter.lib.typing in the Smarter platform. This module imports only from the
standard library and pydantic, so that any module of the SDK can import it without an import cycle.
- smarter.core.typing.SmarterDictType
A mapping whose values really can be of any type.
Prefer a Pydantic model when the shape is known.
- smarter.core.typing.SmarterJsonType
A value that is arbitrary JSON: a string, number, boolean, null, or a list or object of them.
- smarter.core.typing.SmarterJsonObjectType
A JSON object whose shape is not fixed, e.g. a manifest’s
spec, whose fields depend on its kind.
- smarter.core.typing.SmarterIdType
A resource’s id: its integer primary key, or its hashed id, e.g.
rMTAwMDAwNQx.