Patcher API Client¶
See also
Endpoints — the API surface this client wraps.
- class PatcherAPIClient(base_url: str = 'https://api.patcherctl.dev', *, max_concurrency: int = 5)[source]¶
Construct a
PatcherAPIClientpointed at a Patcher API instance.- Parameters:
base_url (str) – The API root. Defaults to the public instance at
https://api.patcherctl.dev. Override for self-hosted deployments or local development againstmake serve-api.max_concurrency (int) – Max concurrent in-flight requests. The API is cached behind Cloudflare with per-deploy ETags, so even modest concurrency rarely hits origin.
- async list_apps(*, vendor: str | None = None, source: str | None = None, exclude_source: str | None = None, limit: int = 100, offset: int = 0) list[App][source]¶
GET /apps— list catalog apps with optional filters and pagination.- Parameters:
vendor (str | None) – Case-insensitive exact vendor match. None disables.
source (str | None) – Include only apps whose
sourcesarray contains this token (installomator,homebrew_cask,autopkg,mas,jamf_app_installer).exclude_source (str | None) – Drop apps whose
sourcesarray contains this token.limit (int) – Maximum rows to return. Server caps at 1000.
offset (int) – Number of filtered rows to skip.
- Returns:
Catalog records, ordered by slug for deterministic paging.
- Raises:
APIResponseError – Network failure, non-2xx response, or unparseable body.
- Return type:
- async get_app(slug: str) App | None[source]¶
GET /apps/{slug}— fetch one app by slug. ReturnsNoneon 404.- Parameters:
slug (str) – URL-friendly app identifier (e.g.
"firefox").- Returns:
The catalog record, or
Noneif the slug isn’t in the catalog.- Raises:
APIResponseError – For any non-2xx status other than 404.
- Return type:
App | None
- async get_app_sources(slug: str) AppSources | None[source]¶
GET /apps/{slug}/sources— fetch per-source payloads for a slug.Returns
Noneon 404. Source values inside the returnedAppSourcesobject areNonefor sources that didn’t contribute data for this slug.- Parameters:
slug (str)
- Return type:
AppSources | None
- async list_drift(*, vendor: str | None = None, source: str | None = None, limit: int = 100, offset: int = 0) DriftResponse[source]¶
GET /apps/drift— scan the catalog for cross-source version drift.Iterates apps with at least two versioned sources (Installomator and Homebrew Cask, today) and returns those whose sources disagree on what “latest” means. A vendor or source filter narrows the result; pagination operates on the filtered count.
- Parameters:
vendor (str | None) – Case-insensitive exact vendor match. None disables.
source (str | None) – Drop entries where this source did not participate in the disagreement (
installomatororhomebrew_cask).limit (int) – Maximum entries on this page. Server caps at 1000.
offset (int) – Entries to skip before the page.
- Returns:
Drift entries plus aggregate counts.
- Raises:
APIResponseError – Network failure, non-2xx response, or unparseable body.
- Return type:
- async get_app_drift(slug: str) DriftEntry | None[source]¶
GET /apps/{slug}/drift— drift detection for a single app.Returns
Nonein two cases: the slug isn’t in the catalog (404), or the app exists but has no drift (200 withnullbody — either fewer than two versioned sources, or every source agrees). Callers that need to distinguish should callget_app()first to confirm existence.- Parameters:
slug (str) – URL-friendly app identifier.
- Returns:
A drift entry, or
Nonefor “no drift to report.”- Raises:
APIResponseError – For any non-2xx status other than 404.
- Return type:
DriftEntry | None
- async generate_label(slug: str) GeneratedLabel | None[source]¶
POST /apps/{slug}/generate-label— server-side label projection.Returns
Noneon 404. The returnedGeneratedLabelcarries awarningsarray surfacing fields that couldn’t be resolved (most commonlyexpectedTeamIDfor Cask-only apps).- Parameters:
slug (str)
- Return type:
GeneratedLabel | None
Response models¶
Pydantic models that mirror the API’s wire format. Returned from the client methods above; useful for type-hinting your own code.
- class App(*, slug: str, bundle_id: str | None = None, name: str, vendor: str | None = None, current_version: str | None = None, latest_release_date: date | None = None, download_url: HttpUrl | None = None, install_method: InstallMethod | None = None, sha256: str | None = None, sources: list[str])[source]¶
A stitched catalog record. One row per app slug.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- Parameters:
- model_config = {'extra': 'ignore'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class AppSources(*, installomator: InstallomatorSource | None = None, homebrew_cask: HomebrewCaskSource | None = None, autopkg: AutopkgSource | None = None, mas: MasSource | None = None, jamf_app_installer: JamfAppInstallerSource | None = None)[source]¶
Per-source payloads for a single app slug. Source values are
Nonewhen that source didn’t contribute.Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- Parameters:
installomator (InstallomatorSource | None)
homebrew_cask (HomebrewCaskSource | None)
autopkg (AutopkgSource | None)
mas (MasSource | None)
jamf_app_installer (JamfAppInstallerSource | None)
- model_config = {'extra': 'ignore'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class InstallomatorSource(*, label_name: str, label_url: HttpUrl, raw: dict[str, Any])[source]¶
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class HomebrewCaskSource(*, token: str, cask_json: dict[str, Any])[source]¶
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class AutopkgSource(*, recipes: list[AutopkgRecipeEntry])[source]¶
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- Parameters:
recipes (list[AutopkgRecipeEntry])
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class AutopkgRecipeEntry(*, identifier: str, name: str, shortname: str, repo: str, path: str, parent_identifier: str | None = None, inferred_type: str | None = None, recipe_url: HttpUrl | None = None)[source]¶
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- Parameters:
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class MasSource(*, bundle_id: str, store_url: HttpUrl | None = None, raw: dict[str, Any])[source]¶
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class JamfAppInstallerSource(*, title: str, source: str, host: str | None = None)[source]¶
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class GeneratedLabel(*, label_name: str, content: dict[str, Any], sources_used: list[str], warnings: list[str])[source]¶
Response from
POST /apps/{slug}/generate-label.Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'extra': 'ignore'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class DriftResponse(*, total_scanned: int, total_with_drift: int, entries: list[DriftEntry])[source]¶
Paginated drift results from
GET /apps/drift.total_scannedcounts apps with at least two versioned sources;total_with_driftis the unpaged count of disagreements that matched the request’s filters.Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- Parameters:
total_scanned (int)
total_with_drift (int)
entries (list[DriftEntry])
- model_config = {'extra': 'ignore'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class DriftEntry(*, slug: str, name: str, vendor: str | None = None, versions: list[SourceVersion], leader: str | None = None, laggard: str | None = None)[source]¶
Drift detected on a single app.
leaderandlaggardare the source names with the highest and lowest parsed versions; both areNonewhen at least one version string couldn’t be parsed (e.g. Cask date-style versions).Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- Parameters:
- model_config = {'extra': 'ignore'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class SourceVersion(*, source: str, version: str, parsed_ok: bool)[source]¶
One source’s reported version for an app.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'extra': 'ignore'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].