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_concurrency attribute 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 value is less than 1.

Return type:

None

property http: AsyncClient[source]¶

Lazily-constructed httpx.AsyncClient bound 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 call aclose (process exit reclaims resources), but library consumers should for clean shutdown.

Returns:

The shared httpx.AsyncClient for this instance.

Return type:

httpx.AsyncClient

Note

Not thread-safe. HTTPClient is intended for single-event-loop use. max_connections is bound to self.max_concurrency so the same ceiling that gates self.semaphore also 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 http will 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.RequestError to patcher.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:
  • method (str) – HTTP method (e.g. "GET", "POST").

  • url (str) – Fully-qualified URL.

  • kwargs (Any) – Forwarded to httpx.AsyncClient.request().

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.APIResponseError so callers see the same exception contract as the existing fetch_json() path, notably the not_found=True flag on 404 responses, which patcher.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-export on the Jamf CSV export endpoint). Forwarded to httpx, which handles URL encoding.

Returns:

The response body as a string.

Return type:

str

Raises:

APIResponseError – If the response is non-2xx, or if a network-level error (connect, DNS, timeout) prevents the request from completing. not_found=True is set on 404.

_raise_for_status(status_code: int, response_json: dict | None) None[source]¶

Raise patcher.core.exceptions.APIResponseError if status_code is non-2xx.

2xx is a no-op (control returns to the caller, which uses response_json directly). 404 carries not_found=True so 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 error field from the JSON body’s "errors" key when present; otherwise reports "No details".

Parameters:
  • status_code (int)

  • response_json (dict | None)

Return type:

None

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 (see http). Form-encoded vs JSON request bodies are selected based on the Content-Type header of the merged request headers; the same routing logic the prior curl-based implementation used. Non-2xx responses are translated via patcher.clients.HTTPClient._raise_for_status() into patcher.core.exceptions.APIResponseError (with not_found=True on 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-Type is application/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:

dict

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_concurrency requests are sent concurrently.

Parameters:
  • urls (list[str]) – list of URLs to fetch data from.

  • headers (dict[str, str] | None) – Optional headers to include in the request. Defaults to self.headers via the fetch_json() method.

  • query_params (dict[str, str] | None) – Additional query parameters to append to the URL. Defaults to None.

Returns:

A list of JSON dictionaries.

Return type:

list[dict]

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 the Authorization header. 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:

str

Raises:

APIResponseError – If the call is unauthorized, unsuccessful, or the response body doesn’t contain a token field.

async create_roles(token: str, jamf_url: str) bool[source]¶

Creates the necessary API roles using the provided basic token.

See also

ApiRoleModel

Parameters:
  • token (str) – The basic token to use for authentication.

  • jamf_url (str) – Jamf Server URL

Returns:

True if roles were successfully created, False otherwise.

Return type:

bool

async create_client(token: str, jamf_url: str) tuple[str, str][source]¶

Creates an API client and retrieves its client ID and client secret.

See also

ApiClientModel

Parameters:
  • token (str) – The basic token to use for authentication.

  • jamf_url (str) – Jamf Server URL

Returns:

A tuple containing the client ID and client secret.

Return type:

tuple[str, str]

Subclasses use _request to share the per-instance semaphore and the httpx.RequestError → APIResponseError translation; PatcherAPIClient._get and _post route through it.