schemas

Pydantic models the API uses for response serialization (and a few request bodies). One module per source’s payload shape, plus shared schemas for apps, drift, and labels.

App

class InstallMethod(*values)[source]

Mirrors Installomator’s type variable.

See https://github.com/Installomator/Installomator/wiki/Label-Variables-Reference for the upstream definition.

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]

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:

Sources (composite payload)

class InstallomatorSource(*, label_name: str, label_url: HttpUrl, raw: dict)[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:
class HomebrewCaskSource(*, token: str, cask_json: dict)[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:
class AutopkgRecipeEntry(*, identifier: str, name: str | None = None, shortname: str | None = None, repo: str, path: str, parent_identifier: str | None = None, inferred_type: str | None = None, recipe_url: HttpUrl | None = None)[source]

Single recipe attached to an app via the AutoPkg index.

name and shortname are optional, mirroring the upstream index (and the AutopkgIndexEntry ingest schema): shared-processor recipes carry name: null and some app recipes have no clean shortname. The response must tolerate the None that stitch faithfully stored, or /apps/{slug}/sources 500s for any app whose matched recipes lack one.

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:
  • identifier (str)

  • name (str | None)

  • shortname (str | None)

  • repo (str)

  • path (str)

  • parent_identifier (str | None)

  • inferred_type (str | None)

  • recipe_url (HttpUrl | None)

class AutopkgSource(*, recipes: list[AutopkgRecipeEntry])[source]

All AutoPkg recipes matched to an app via the recipe index.

AutoPkg coverage is multi-recipe by nature: a single app like Firefox typically has download, munki, pkg, jamf, and intune variants across multiple maintainer repos. Each match is preserved as a separate AutopkgRecipeEntry.

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])

class MasSource(*, bundle_id: str, store_url: HttpUrl | None = None, raw: dict)[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:
class JamfAppInstallerSource(*, title: str, source: str, host: str | None = None, bundle_id: str | None = None, version: str | None = None, jamf_id: str | None = None, download_url: str | None = None, architecture: str | None = None)[source]

Jamf App Installers catalog coverage for an app.

title/source/host come from the public HTML catalog; the rest is enrichment from the App Installers titles API (absent on HTML-only rows, hence optional).

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:
  • title (str)

  • source (str)

  • host (str | None)

  • bundle_id (str | None)

  • version (str | None)

  • jamf_id (str | None)

  • download_url (str | None)

  • architecture (str | None)

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]

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:

Drift

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.

Parameters:
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.

leader and laggard are the source names with the highest and lowest parsed versions; both are None when at least one version string couldn’t be parsed (e.g. Cask’s 2025-04-15 date-style versions). In that case versions is still complete and consumers can render the disagreement without ordering it.

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:
class DriftResponse(*, total_scanned: int, total_with_drift: int, entries: list[DriftEntry])[source]

Paginated drift results across the catalog.

total_scanned is the number of apps inspected (those with ≥2 versioned sources); total_with_drift is the subset where the sources disagreed. entries is the page of disagreements.

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:

Labels

class GenerateLabelResponse(*, label_name: str, content: dict[str, Any], sources_used: list[str], warnings: list[str])[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:

Shared base

UpstreamModel is the camelCase base every upstream payload schema inherits from (it is not Installomator-specific; Installomator’s raw payload is stored as a dict rather than a typed model).

class UpstreamModel[source]

Base for models that mirror a third-party source’s camelCase payload.

Field names stay snake_case; to_camel auto-generates the camelCase wire alias (bundle_idbundleId), so most fields need no explicit Field(alias=...). populate_by_name lets our code construct by either name, and unknown fields are ignored (Pydantic’s default) so an upstream addition never breaks ingest.

Deliberately a small twin of patcher.core.models.UpstreamModel rather than an import of it: importing the client package pulls pandas + keyring + the Jamf clients into the API process (a real cost on the 1 GB host), and the API is a separate, lightweight deployable.

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.

Homebrew Cask

class HomebrewCaskRecord(*, token: str, name: list[str], desc: str | None = None, homepage: str | None = None, url: str | None = None, version: str | None = None, sha256: str | None = None, auto_updates: bool | None = None, depends_on: dict | None = None, artifacts: list[dict] = [])[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:

AutoPkg

class AutopkgIndexEntry(*, name: str | None = None, description: str | None = None, repo: str, path: str, parent: str | None = None, shortname: str | None = None, inferred_type: str | None = None, children: list[str] = [])[source]

A single recipe entry as it appears in the value of an identifiers map key in upstream index.json. The map’s key (the reverse-DNS identifier like com.github.autopkg.download.Firefox) is passed separately when ingesting; it is not part of the entry value.

name and shortname are intentionally optional because the upstream index has substantial inconsistency on these fields. Shared- processor utility recipes typically have name: null; some app recipes have unusual shortname values (often containing special characters like . or whitespace) that the index doesn’t capture cleanly. Preserving these rows keeps the catalog complete; the stitch matching logic already gates on a non-empty normalized name, so recipes without one naturally never attach to apps.

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:
  • name (str | None)

  • description (str | None)

  • repo (str)

  • path (str)

  • parent (str | None)

  • shortname (str | None)

  • inferred_type (str | None)

  • children (list[str])

Jamf App Installers

class JaiMediaSource(*, url: str, hash: str | None = None, hashType: str | None = None)[source]

One download source for a title — titles often carry one per architecture.

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

  • hash (str | None)

  • hashType (str | None)

class JaiTitle(*, id: str, bundleId: str | None = None, titleName: str, publisher: str | None = None, iconUrl: str | None = None, version: str | None = None, shortVersion: str | None = None, architecture: str | None = None, minimumOsVersion: str | None = None, language: str | None = None, availabilityDate: datetime | None = None, mediaSourceType: str | None = None, originalMediaSources: list[JaiMediaSource] = <factory>, sizeInBytes: int | None = None, installationPathShared: bool | None = None, packageSigningIdentity: str | None = None, installerPackageHash: str | None = None, installerPackageHashType: str | None = None, launchDaemonIncluded: bool | None = None, notificationAvailable: bool | None = None, suppressAutoUpdate: bool | None = None, originalTermsAndConditions: list[Any] = <factory>)[source]

A Jamf App Installers catalog title.

The list endpoint returns the leading identity fields; the per-title detail endpoint adds the rest. Everything past title_name is optional, so the same model parses both shapes. Aliases are auto-generated camelCase except the two original* fields, whose wire names don’t follow from the snake_case field name.

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:
  • id (str)

  • bundleId (str | None)

  • titleName (str)

  • publisher (str | None)

  • iconUrl (str | None)

  • version (str | None)

  • shortVersion (str | None)

  • architecture (str | None)

  • minimumOsVersion (str | None)

  • language (str | None)

  • availabilityDate (datetime | None)

  • mediaSourceType (str | None)

  • originalMediaSources (list[JaiMediaSource])

  • sizeInBytes (int | None)

  • installationPathShared (bool | None)

  • packageSigningIdentity (str | None)

  • installerPackageHash (str | None)

  • installerPackageHashType (str | None)

  • launchDaemonIncluded (bool | None)

  • notificationAvailable (bool | None)

  • suppressAutoUpdate (bool | None)

  • originalTermsAndConditions (list[Any])

class JaiTitlePage(*, totalCount: int, results: list[JaiTitle])[source]

One page of GET /api/v1/app-installers/titles (totalCount + results).

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:

Mac App Store

class MasLookupRecord(*, bundleId: str, trackName: str, version: str | None = None, releaseDate: str | None = None, releaseNotes: str | None = None, trackViewUrl: str | None = None, minimumOsVersion: str | None = None, price: float | None = None, kind: str | None = None)[source]

Single result from the lookup endpoint.

Apple returns the fields camelCase as documented. We accept them verbatim here and project to snake_case at the model boundary, matching the pattern used for Homebrew Cask and Installomator records.

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:
  • bundleId (str)

  • trackName (str)

  • version (str | None)

  • releaseDate (str | None)

  • releaseNotes (str | None)

  • trackViewUrl (str | None)

  • minimumOsVersion (str | None)

  • price (float | None)

  • kind (str | None)