HTTPClient#
- class HTTPClient(max_concurrency: int = 5)[source]#
Async HTTP client with OS-trust-store TLS and bounded concurrency for all API calls.
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.
- 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, which label-fetch callers use 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. A 404 raises
NotFoundError.
- _raise_for_status(status_code: int, response_json: dict | None, url: str | None = None) None[source]#
The single status-code handler for every client built on this base.
2xx is a no-op. 404 raises
NotFoundError; other non-2xx raiseAPIResponseError. Error detail is pulled from the JSON bodyโserrors(Jamf) ordetail(Patcher API) key, whichever is present.
- 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:
Subclasses use _request to share the per-instance semaphore and the
httpx.RequestError โ APIResponseError translation; PatcherAPIClient._get
and _post route through it.