HTTPClient¶
- class HTTPClient(max_concurrency: int = 5)[source]¶
The HTTPClient class controls concurrency settings and secure connections for all API calls.
This class forms the backbone of Patcher’s ability to interact with external APIs. It manages the number of API requests that can be made simultaneously, ensuring the tool is both efficient and does not overload any servers.
Warning
Changing the max_concurrency value could lead to your Jamf server being unable to perform other basic tasks. It is strongly recommended to limit API call concurrency to no more than 5 connections. See Jamf Developer Guide for more information.
- Parameters:
max_concurrency (int) – The maximum number of API requests that can be sent at once. Defaults to
5.
- set_concurrency(value: int) None[source]¶
Set the maximum concurrency level for outbound API requests, with validation.
Read access is via the
self.max_concurrencyattribute directly; this method exists specifically for validated writes. It is recommended to keep the value at no more than 5 to avoid overloading Jamf. See the class docstring for the upstream guidance.- Parameters:
value (int) – The new maximum concurrency level.
- Raises:
PatcherError – If
valueis less than 1.- Return type:
None
- property http: AsyncClient[source]¶
Lazily-constructed
httpx.AsyncClientbound to this HTTPClient instance.First access constructs the client; subsequent accesses return the same instance. Call
aclose()to release the underlying connection pool when this HTTPClient is no longer needed. The CLI doesn’t strictly need to callaclose(process exit reclaims resources), but library consumers should for clean shutdown.- Returns:
The shared
httpx.AsyncClientfor this instance.- Return type:
httpx.AsyncClient
Note
Not thread-safe. HTTPClient is intended for single-event-loop use.
max_connectionsis bound toself.max_concurrencyso the same ceiling that gatesself.semaphorealso applies at the HTTP layer.
- async aclose() None[source]¶
Release the underlying httpx connection pool, if one was created.
Idempotent: safe to call multiple times. After calling, the next access to
httpwill construct a fresh client.- Return type:
None
- async _request(method: str, url: str, **kwargs: Any) Response[source]¶
Single httpx call wrapped with the per-instance semaphore and translation of
httpx.RequestErrortopatcher.core.exceptions.APIResponseError.Subclasses use this to share the network-error handling without reimplementing the semaphore + try/except plumbing. The caller is responsible for status-code handling on the returned response.
- Parameters:
- Returns:
The raw
httpx.Response.- Return type:
httpx.Response- Raises:
APIResponseError – On any
httpx.RequestError(network, DNS, timeout).
- async fetch_text(url: str, *, headers: dict[str, str] | None = None, params: dict[str, Any] | list[tuple[str, Any]] | None = None) str[source]¶
Fetch the body of a URL as text via httpx.
Translates httpx-native errors to Patcher’s
patcher.core.exceptions.APIResponseErrorso callers see the same exception contract as the existingfetch_json()path, notably thenot_found=Trueflag on 404 responses, whichpatcher.clients.installomator.InstallomatorClient.match()uses to short-circuit gracefully.- Parameters:
url (str) – The URL to fetch.
headers (dict[str, str] | None) – Optional request headers. If omitted, only httpx defaults are sent.
params (dict[str, Any] | list[tuple[str, Any]] | None) – Optional query parameters. Accepts a mapping for unique keys, or a list of
(key, value)tuples when the same key needs to repeat (e.g.,columns-to-exporton the Jamf CSV export endpoint). Forwarded to httpx, which handles URL encoding.
- Returns:
The response body as a string.
- Return type:
- Raises:
APIResponseError – If the response is non-2xx, or if a network-level error (connect, DNS, timeout) prevents the request from completing.
not_found=Trueis set on 404.
- _raise_for_status(status_code: int, response_json: dict | None) None[source]¶
Raise
patcher.core.exceptions.APIResponseErrorifstatus_codeis non-2xx.2xx is a no-op (control returns to the caller, which uses
response_jsondirectly). 404 carriesnot_found=Trueso callers can distinguish missing-resource from other client errors. 4xx, 5xx, and anything outside 200-599 each get a distinct error message.Pulls a human-readable
errorfield from the JSON body’s"errors"key when present; otherwise reports"No details".
- async fetch_json(url: str, headers: dict[str, str] | None = None, method: str = 'GET', data: dict[str, str] | None = None, query_params: dict[str, str] | None = None) dict[source]¶
Asynchronously fetches JSON data from the specified URL using the specified HTTP method.
Routes the request through the per-instance
httpx.AsyncClient(seehttp). Form-encoded vs JSON request bodies are selected based on theContent-Typeheader of the merged request headers; the same routing logic the prior curl-based implementation used. Non-2xx responses are translated viapatcher.clients.HTTPClient._raise_for_status()intopatcher.core.exceptions.APIResponseError(withnot_found=Trueon 404), preserving the public exception contract for callers.- Parameters:
url (str) – The URL to fetch data from.
headers (dict[str, str] | None) – Optional headers to include in the request. Defaults to
self.default_headers.method (str) – HTTP method to use (“GET” or “POST”). Defaults to “GET”.
data (dict[str, str] | None) – Optional request body. Form-encoded when the request
Content-Typeisapplication/x-www-form-urlencoded; JSON-encoded otherwise.query_params (dict[str, str] | None) – Additional query parameters to append to the URL.
- Returns:
The decoded JSON response body.
- Return type:
- Raises:
APIResponseError – If the response is non-2xx, if a network error prevents the request from completing, or if the response body is not valid JSON.
- async fetch_batch(urls: list[str], headers: dict[str, str] | None = None, query_params: dict[str, str] | None = None) list[dict][source]¶
Fetches JSON data in batches to respect the concurrency limit.
Data is fetched from each URL in the provided list, ensuring that no more than
patcher.clients.HTTPClient.max_concurrencyrequests are sent concurrently.- Parameters:
- Returns:
A list of JSON dictionaries.
- Return type:
- async fetch_basic_token(username: str, password: str, jamf_url: str) str[source]¶
Asynchronously retrieves a basic token using HTTP Basic authentication.
This method is intended for initial setup to obtain client credentials for API clients and roles. It should not be used for regular token retrieval after setup.
The password is passed via httpx’s
auth=tuple parameter, which encodes it in theAuthorizationheader. It never appears in the URL, request body, or log output, so no credential-sanitization step is required on the error path.- Parameters:
username (str) – Username of admin Jamf Pro account for authentication. Not permanently stored, only used for initial token retrieval.
password (str) – Password of admin Jamf Pro account. Not permanently stored, only used for initial token retrieval.
jamf_url (str) – Jamf Server URL (See
server).
- Returns:
The BasicToken string.
- Return type:
- Raises:
APIResponseError – If the call is unauthorized, unsuccessful, or the response body doesn’t contain a
tokenfield.
- async create_roles(token: str, jamf_url: str) bool[source]¶
Creates the necessary API roles using the provided basic token.
See also
Subclasses use _request to share the per-instance semaphore and the
httpx.RequestError → APIResponseError translation; PatcherAPIClient._get
and _post route through it.