PatcherClient#

class PatcherClient(client_id: str | None = None, client_secret: str | None = None, server: str | None = None, *, config: ConfigManager | None = None, concurrency: int = 5, disable_cache: bool = False, debug: bool = False, enable_matching: bool = True, enable_homebrew: bool = False, integrations: Integrations | None = None, ui_config: dict | None = None, ignored_titles: list[str] | None = None, enable_installomator: bool | None = None)[source]#

Patcher’s top-level library entry point.

Composes the Jamf client, the catalog API client, and the data layer into one object library callers instantiate directly (or build from on-disk state via from_state()). The CLI constructs the same object after its Setup flow. Construction parameters are documented on __init__().

Construct a PatcherClient with all collaborators wired up.

Library callers pass credentials directly:

from patcher import PatcherClient

async with PatcherClient(
    client_id="...",
    client_secret="...",
    server="https://myorg.jamfcloud.com",
) as patcher:
    summaries = await patcher.jamf.get_summaries(
        await patcher.jamf.get_policies()
    )
# connection pool released here

An in-memory ConfigManager is built internally. No keyring backend required, no plist mutation, no disk I/O on construction. PatcherClient is usable as an async context manager (preferred for clean shutdown) or as a regular object (call aclose() manually when done).

Exactly one of (client_id + client_secret + server) or config must be provided.

Note

The config= parameter exists for internal CLI use, where the Setup flow has already populated keyring with credentials. Library callers should use the credentials path above.

Parameters:
  • client_id (str | None) – Jamf Pro API client ID.

  • client_secret (str | None) – Jamf Pro API client secret.

  • server (str | None) – Jamf Pro instance URL (e.g. https://myorg.jamfcloud.com).

  • config (ConfigManager | None) – Existing ConfigManager instance, mutually exclusive with the credentials arguments.

  • concurrency (int) – Maximum concurrent API requests. Defaults to 5, the recommended ceiling per the Jamf Developer Guide.

  • disable_cache (bool) – If True, DataManager skips on-disk patch-data caching.

  • debug (bool) – Enables debug-mode handling in collaborators (notably disables the spinner animation when set in the CLI path).

  • enable_matching (bool) – If False, api is None and all catalog matching (Installomator labels and Homebrew Cask) is skipped. Defaults to True.

  • enable_homebrew (bool) – Deprecated force-on override for the Homebrew Cask integration; prefer integrations. When True, enables the homebrew source regardless of integrations. Defaults to False (no override).

  • integrations (Integrations | None) – Per-source matching toggles (Integrations). Gates which sources are paged as match candidates and recorded on each title’s sources map. Defaults to all sources enabled.

  • ui_config (dict | None) – Optional dict of UI settings (header text, footer, font paths, header color, etc.) for PDF/HTML report styling. Defaults to UIDefaults values.

  • ignored_titles (list[str] | None) – Extra Jamf-title skip patterns merged with the built-in IGNORED_TITLES during matching. Defaults to none.

  • enable_installomator (bool | None) – Deprecated alias for enable_matching, kept for backward compatibility. Emits a DeprecationWarning when passed; remove in favor of enable_matching.

Raises:

PatcherError – If neither credentials nor config are provided.

classmethod from_state(**overrides: Any) PatcherClient[source]#

Construct a PatcherClient using state already persisted on this Mac.

Reads Jamf credentials from the macOS keychain, UI customization from the property list, and the enable_matching toggle plus the per-source integrations. Equivalent to what the patcherctl CLI does on startup; useful for library callers running on a workstation that has already been through the setup wizard.

Any keyword argument accepted by __init__ can be passed as an override (commonly concurrency or debug).

Parameters:

overrides (Any) – Optional PatcherClient constructor kwargs that take precedence over what’s read from on-disk state.

Returns:

A configured PatcherClient ready to call.

Return type:

PatcherClient

Raises:

PatcherError – If keychain credentials are missing (i.e. patcherctl setup hasn’t completed on this machine).

async fetch_patches(*, match_installomator: bool = True, match_homebrew: bool | None = None, include_ios: bool = False, sort_by: str | None = None, omit_recent_hours: int | None = None) list[PatchTitle][source]#

Fetch patch summaries in one call. The library equivalent of what the CLI’s export flow gathers before writing a report.

Composes the granular pipeline: policies β†’ summaries β†’ (optional Installomator match) β†’ (optional iOS append) β†’ (optional sort/filter). Equivalent to manually chaining self.jamf.get_policies, self.jamf.get_summaries, match_titles(), append_ios_status(), omit_recent(), and sort_titles().

Parameters:
  • match_installomator (bool) – If True (default), match each title to its Installomator label via the Patcher API catalog (match_titles()). No-op when enable_matching=False was passed at construction time.

  • match_homebrew (bool | None) – Per-call override for the Homebrew Cask source. True forces it on, False forces it off, None (default) uses the integrations configured at construction. Rides on the same match pass as Installomator, so it is a no-op when match_installomator is False or api is None.

  • include_ios (bool) – If True, append per-iOS-version summaries to the returned list. Costs additional Jamf API calls.

  • sort_by (str | None) – Optional attribute name to sort titles by (e.g. "released", "completion_percent"). Normalized to lowercase + underscores.

  • omit_recent_hours (int | None) – If provided, drop titles released within the past N hours. Mirrors the CLI’s --omit flag.

Returns:

List of PatchTitle objects, optionally enriched and filtered.

Return type:

list[PatchTitle]

Raises:

PatcherError – If the Jamf API calls fail or sort_by names an attribute that doesn’t exist on PatchTitle.

async analyze(titles: list[PatchTitle], criteria: str, *, threshold: float | None = 70.0, top_n: int | None = None, where: dict | None = None) list[PatchTitle][source]#

Filter and sort patch titles by a named criterion. The library equivalent of the CLI’s analyze subcommand.

Accepts a CLI-style criterion string (e.g. "most-installed", "below-threshold"). Library callers who want type-checked, autocomplete-friendly access should construct TitleFilter directly and invoke the matching method.

Changed in version 3.0: The criteria parameter no longer accepts FilterCriteria enum values; the enum was removed in favor of TitleFilter methods. Pass the kebab-case string form, or use TitleFilter directly.

Parameters:
  • titles (list[PatchTitle]) – Patch titles to analyze. Typically the output of fetch_patches().

  • criteria (str) – CLI-style criterion (e.g. "most-installed"). See TitleFilter for the full list.

  • threshold (float | None) – Completion-percent threshold for below-threshold criterion. Ignored by other criteria. Defaults to 70.0.

  • top_n (int | None) – If provided, return at most top_n results. The below-threshold and zero-completion criteria ignore this (they return all matching titles).

  • where (dict | None) – Optional pre-filter applied before the criterion runs. Keys are min_compliance / min_hosts / released_after; unknown keys raise PatcherError.

Returns:

Filtered + sorted list of PatchTitle objects.

Return type:

list[PatchTitle]

Raises:

PatcherError – If criteria is not a recognized value.

async analyze_excel(excel_path: str | Path, criteria: str, *, threshold: float | None = 70.0, top_n: int | None = None, where: dict | None = None) list[PatchTitle][source]#

Filter and sort patch titles loaded from a saved Excel report.

Hydrates PatchTitle objects from excel_path (a previously-exported Patcher report) and analyzes those instead of the cached snapshot β€” the library equivalent of patcherctl analyze --excel-file. Useful for re-analyzing a shared or historical export without re-fetching from Jamf.

Parameters:
  • excel_path (str | pathlib.Path) – Path to a previously-exported Patcher Excel report.

  • criteria (str) – CLI-style filter criterion.

  • threshold (float | None) – Completion-percent cutoff for below-threshold.

  • top_n (int | None) – Optional result cap. Ignored by below-threshold and zero-completion.

  • where (dict | None) – Optional pre-filter (min_compliance / min_hosts / released_after), same as analyze().

Returns:

Filtered + sorted titles.

Return type:

list[PatchTitle]

Raises:

PatcherError – If the Excel file can’t be read or yields no titles.

async analyze_trend(criteria: str, *, save_to: str | Path | None = None)[source]#

Compute trend analysis across every cached patch dataset.

Library equivalent of patcherctl analyze --all-time --criteria. Reads every cached snapshot in the data cache and builds a trend DataFrame keyed on the requested criterion.

Changed in version 3.0: The criteria parameter no longer accepts TrendCriteria enum values; the enum was removed in favor of TrendAnalysis methods. Pass the kebab-case string form, or use TrendAnalysis directly.

Parameters:
  • criteria (str) – CLI-style trend criterion (e.g. "patch-adoption", "release-frequency", "completion-trends").

  • save_to (str | Path | None) – Optional path. When provided, the trend DataFrame is also written to disk as HTML. Parent directories are created if needed.

Returns:

Trend results as a ~pandas.DataFrame.

Return type:

DataFrame

Raises:

PatcherError – If fewer than two cached snapshots exist or criteria is unrecognized.

async diff(*, since: timedelta | None = None, all_time: bool = False, between: tuple[date, date] | None = None, no_fetch: bool = False) DiffResult[source]#

Pairwise comparison between two patch-state snapshots.

Default (no flags): live fetch via fetch_patches() compared against the most-recent cached snapshot. Override behavior with one of the keyword arguments below.

Added in version 3.1.

Parameters:
  • since (timedelta | None) – When set, compare against the earliest cached snapshot in the trailing window (e.g. timedelta(days=30) for β€œwhat changed in the last 30 days”).

  • all_time (bool) – When True, compare against the earliest cached snapshot ever recorded. Mutually exclusive with since.

  • between (tuple[date, date] | None) – Two-date pair selecting cached snapshots closest to each date. Implies cache-only (no live fetch). Cannot be combined with since or all_time.

  • no_fetch (bool) – When True, skip the live fetch and compare two cached snapshots only. Defaults to the second-most-recent and most-recent unless since or all_time is also passed.

Returns:

Structured delta covering added, removed, and changed titles.

Return type:

DiffResult

Raises:

PatcherError – On invalid flag combinations, or when no cached snapshots are available for the requested mode.

async detect_drift(*, slug: str | None = None, vendor: str | None = None, source: str | None = None, limit: int = 100, offset: int = 0) DriftResponse | DriftEntry | None[source]#

Cross-source version drift detection via the Patcher catalog API.

Without slug: returns a DriftResponse listing apps whose upstream sources disagree on the current version. With slug: returns a DriftEntry for that single app, or None if the app doesn’t exist or has no drift.

Works without enable_matching; the catalog API is constructed on demand when needed.

Added in version 3.1.

Parameters:
  • slug (str | None) – When set, narrow to a single app. Filters below are ignored. None returns the paginated list.

  • vendor (str | None) – Case-insensitive exact vendor match for list mode.

  • source (str | None) – Drop list entries where this source did not participate in the disagreement.

  • limit (int) – Max entries per list page.

  • offset (int) – Entries to skip before the list page.

Returns:

Drift result. Shape depends on whether slug was set.

Return type:

DriftResponse | DriftEntry | None

Raises:

PatcherError – If list-mode filters are passed with a slug.

async export(titles: list[PatchTitle], *, output_dir: str | Path, formats: set[str] | None = None, report_title: str | None = None, date_format: str = '%B %d %Y', header_color: str | None = None, analysis: bool = False, device_reports: dict[str, list] | None = None, coverage: list[str] | None = None) dict[str, str][source]#

Export patch titles to one or more report formats. Builds and caches the canonical snapshot via DataManager, then renders it through Exporter.

Parameters:
  • titles (list[PatchTitle]) – Patch titles to include in the report.

  • output_dir (str | Path) – Directory to write report file(s) into.

  • formats (set[str] | None) – Set of format strings to emit. Defaults to all four: {"excel", "html", "pdf", "json"}.

  • report_title (str | None) – Title used in PDF/HTML headers. Defaults to the header_text value from this client’s ui_config.

  • date_format (str) – Date format for PDF/HTML headers (strftime). Defaults to "%B %d %Y".

  • header_color (str | None) – Hex color for the HTML report header background. Falls back to header_color when None.

  • analysis (bool) – If True, treats this as an analysis report (affects HTML output path naming).

  • device_reports (dict[str, list] | None) – Optional per-title device detail data for Excel’s per-title sheets.

  • coverage (list[str] | None) – Source tokens to render as opt-in Y/N coverage columns. None (default) renders no integration columns.

Returns:

Mapping of format β†’ output path for every report written.

Return type:

dict[str, str]

async reset(kind: Literal['full', 'UI', 'creds', 'cache'], *, credential: Literal['url', 'client_id', 'client_secret'] | None = None) None[source]#

Reset persisted state on this Mac. Library equivalent of patcherctl reset <kind>.

Unlike the CLI, reset() does not re-launch the setup wizard after a full reset β€” library callers can re-construct a PatcherClient themselves once they’ve supplied new credentials.

Kinds:

  • "cache" β€” empty the on-disk patch-data cache. Works in any mode.

  • "creds" β€” delete Jamf credentials from the keychain. Pass credential= to scope to a single key. Requires keychain-backed mode (raises in in-memory mode).

  • "UI" β€” clear UI customization from the property list. Requires keychain-backed mode.

  • "full" β€” every reset above, plus clears the setup_completed flag so the next patcherctl invocation re-runs the wizard.

Parameters:
  • kind (str) – One of "full", "UI", "creds", "cache".

  • credential (str | None) – When kind="creds", restrict deletion to this single credential. One of "url", "client_id", "client_secret".

Raises:

PatcherError – If kind is not "cache" and this client was constructed with in-memory credentials (nothing on disk to reset).

Return type:

None

async aclose() None[source]#

Release the underlying httpx connection pools.

Idempotent. Safe to call multiple times. Closes both the JamfClient and (when present) the PatcherAPIClient.

Return type:

None