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.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 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-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. 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 raise APIResponseError. Error detail is pulled from the JSON bodyโ€™s errors (Jamf) or detail (Patcher API) key, whichever is present.

Parameters:
  • status_code (int)

  • response_json (dict | None)

  • url (str | 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]

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