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_installomator: bool = True, enable_homebrew: bool = False, ui_config: dict | None = None)[source]

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_installomator (bool) – If False, api is None and Installomator-label matching (now sourced from the Patcher API catalog) is skipped. Kept under the legacy name for backward compatibility with existing CLI flags.

  • enable_homebrew (bool) – Default for whether fetch_patches() also matches titles against the Homebrew Cask source (a second matching dimension), populating homebrew_cask. Has no effect when api is None (i.e. enable_installomator=False), since matching rides on the same catalog client. Defaults to False.

  • 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.

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_installomator / enable_homebrew toggles. 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_installomator=False was passed at construction time.

  • match_homebrew (bool | None) – Whether to also match titles against the Homebrew Cask source, populating homebrew_cask. None (default) falls back to the enable_homebrew value set at construction time. 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) 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).

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) list[PatchTitle][source]

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

Library equivalent of patcherctl analyze --excel-file.

Note

Excel-to-PatchTitle hydration is parked for v3.0.1. Today this method filters the currently cached data.titles and ignores excel_path. Behavior is unchanged from v2 to preserve existing callers; the parking is purely about closing the no-op gap.

Parameters:
  • excel_path (str | pathlib.Path) – Path to a previously-exported Patcher Excel report. Currently accepted but not read (see note).

  • 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.

Returns:

Filtered + sorted titles.

Return type:

list[PatchTitle]

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_installomator; 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) dict[str, str][source]

Export patch titles to one or more report formats. Convenience wrapper around self.data.export.

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.

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