{"id":18893211,"url":"https://github.com/archivebox/abx-pkg","last_synced_at":"2026-04-01T21:28:34.036Z","repository":{"id":240359845,"uuid":"802422164","full_name":"ArchiveBox/abx-pkg","owner":"ArchiveBox","description":"📦 Modern strongly typed Python library for managing system dependencies with package managers like apt, brew, pip, npm, etc.","archived":false,"fork":false,"pushed_at":"2025-04-07T22:09:38.000Z","size":577,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-11T20:53:05.824Z","etag":null,"topics":["apt","brew","dependencies","dependencies-checking","dependency-manager","django","dpkg","js","npm","package-managers","packages","pip","pydantic","python"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/abx-pkg/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ArchiveBox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["ArchiveBox","pirate"],"custom":["https://donate.archivebox.io/"]}},"created_at":"2024-05-18T08:48:10.000Z","updated_at":"2025-04-09T11:48:41.000Z","dependencies_parsed_at":"2024-05-21T04:42:54.461Z","dependency_job_id":"8d1934df-88f5-404e-bfc3-f378362e7374","html_url":"https://github.com/ArchiveBox/abx-pkg","commit_stats":null,"previous_names":["archivebox/pydantic-pkgr","archivebox/abx-pkg"],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabx-pkg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabx-pkg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabx-pkg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabx-pkg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ArchiveBox","download_url":"https://codeload.github.com/ArchiveBox/abx-pkg/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248984311,"owners_count":21193721,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apt","brew","dependencies","dependencies-checking","dependency-manager","django","dpkg","js","npm","package-managers","packages","pip","pydantic","python"],"created_at":"2024-11-08T08:12:28.484Z","updated_at":"2026-04-01T21:28:34.016Z","avatar_url":"https://github.com/ArchiveBox.png","language":"Python","readme":"\u003ch1\u003e\u003ca href=\"https://github.com/ArchiveBox/abx-pkg\"\u003e\u003ccode\u003eabx-pkg\u003c/code\u003e\u003c/a\u003e \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; 📦  \u003csmall\u003e\u003ccode\u003eapt\u003c/code\u003e\u0026nbsp; \u003ccode\u003ebrew\u003c/code\u003e\u0026nbsp; \u003ccode\u003epip\u003c/code\u003e\u0026nbsp; \u003ccode\u003enpm\u003c/code\u003e\u0026nbsp; \u003ccode\u003ecargo\u003c/code\u003e\u0026nbsp; \u003ccode\u003egem\u003c/code\u003e\u0026nbsp; \u003ccode\u003ego_get\u003c/code\u003e\u0026nbsp; \u003ccode\u003enix\u003c/code\u003e\u0026nbsp; \u003ccode\u003edocker\u003c/code\u003e \u0026nbsp;₊₊₊\u003c/small\u003e\u003cbr/\u003e\u003csub\u003eSimple Python interfaces for package managers + installed binaries.\u003c/sub\u003e\u003c/h1\u003e\n\u003cbr/\u003e\n\n[![PyPI][pypi-badge]][pypi]\n[![Python Version][version-badge]][pypi]\n[![Django Version][django-badge]][pypi]\n[![GitHub][licence-badge]][licence]\n[![GitHub Last Commit][repo-badge]][repo]\n\u003c!--[![Downloads][downloads-badge]][pypi]--\u003e\n\n\u003cbr/\u003e\n\n**It's an ORM for your package managers, providing a nice python types for packages + installers.**  \n  \n**This is a [Python library](https://pypi.org/project/abx-pkg/) for installing \u0026 managing packages locally with a variety of package managers.**  \nIt's designed for when `requirements.txt` isn't enough, and you have to detect or install dependencies at runtime. It's great for installing and managing MCP servers and their dependencies at runtime.\n\n\n```python\npip install abx-pkg\n\npython\n\u003e\u003e\u003e from abx_pkg import Binary, apt, pip, npm\n\n\u003e\u003e\u003e curl = Binary('curl').load()\n\u003e\u003e\u003e print(curl.abspath, curl.version, curl.exec(cmd=['--version']))\n/usr/bin/curl 7.81.0 curl 7.81.0 (x86_64-apple-darwin23.0) libcurl/7.81.0 ...\n\n\u003e\u003e\u003e npm.install('puppeteer')\n```\n\n\u003e 📦 Provides consistent interfaces for runtime dependency resolution \u0026 installation across multiple package managers \u0026 OSs\n\u003e ✨ Built with [`pydantic`](https://pydantic-docs.helpmanual.io/) v2 for strong static typing guarantees and easy conversion to/from json\n\u003e 🌈 Usable with [`django`](https://docs.djangoproject.com/en/5.0/) \u003e= 4.0, [`django-ninja`](https://django-ninja.dev/), and OpenAPI + [`django-jsonform`](https://django-jsonform.readthedocs.io/) to build UIs \u0026 APIs\n\u003e 🦄 Driver layer can be [`pyinfra`](https://github.com/pyinfra-dev/pyinfra) / [`ansible`](https://github.com/ansible/ansible) / or built-in `abx-pkg` engine\n\n\u003csub\u003e\u003ci\u003eBuilt by \u003ca href=\"https://github.com/ArchiveBox\"\u003eArchiveBox\u003c/a\u003e to install \u0026 auto-update our extractor dependencies at runtime (\u003ccode\u003echrome\u003c/code\u003e, \u003ccode\u003ewget\u003c/code\u003e, \u003ccode\u003ecurl\u003c/code\u003e, etc.) on `macOS`/`Linux`/`Docker`.\u003c/i\u003e\u003c/sub\u003e\n\n\u003cbr/\u003e\n\n**Source Code**: [https://github.com/ArchiveBox/abx-pkg/](https://github.com/ArchiveBox/abx-pkg/)\n\n**Documentation**: [https://github.com/ArchiveBox/abx-pkg/blob/main/README.md](https://github.com/ArchiveBox/abx-pkg/blob/main/README.md)\n\n\u003cbr/\u003e\n\n```python\nfrom abx_pkg import Binary, apt, brew, pip, npm, env\n\n# Provider singletons are available as simple imports — no manual instantiation needed\ndependencies = [\n    Binary(name='curl',       binproviders=[env, apt, brew]),\n    Binary(name='wget',       binproviders=[env, apt, brew]),\n    Binary(name='yt-dlp',     binproviders=[env, pip, apt, brew]),\n    Binary(name='playwright', binproviders=[env, pip, npm]),\n    Binary(name='puppeteer',  binproviders=[env, npm]),\n]\nfor binary in dependencies:\n    binary = binary.load_or_install()\n\n    print(binary.abspath, binary.version, binary.binprovider, binary.is_valid, binary.sha256)\n    # Path('/usr/bin/curl') SemVer('7.81.0') AptProvider() True abc134...\n\n    binary.exec(cmd=['--version'])   # curl 7.81.0 (x86_64-apple-darwin23.0) libcurl/7.81.0 ...\n```\n\n`Binary.min_version` is optional. Leave it as `None` when any discovered version is acceptable, or set it to a `SemVer`/string to enforce a minimum version after load/install.\n\n```python\nfrom abx_pkg import Binary, apt, pip\n\n# Use providers directly for package manager operations\napt.install('wget')\nprint(apt.PATH, apt.get_abspaths('wget'), apt.get_version('wget'))\n\n# our Binary API provides a nice type-checkable, validated, serializable handle\nffmpeg = Binary(name='ffmpeg').load()\nprint(ffmpeg)                       # name=ffmpeg abspath=/usr/bin/ffmpeg version=3.3.0 is_valid=True binprovider=apt ...\nprint(ffmpeg.abspaths)              # show all the ffmpeg binaries found in $PATH (in case there's more than one available)\nprint(ffmpeg.model_dump())          # ... everything can also be dumped/loaded as json-ready dict\nprint(ffmpeg.model_json_schema())   # ... OpenAPI-ready JSON schema showing all available fields\n```\n\n```python\nfrom pydantic import InstanceOf\nfrom abx_pkg import Binary, BinProvider, BrewProvider, EnvProvider\n\n# You can also instantiate provider classes manually for custom configuration,\n# or define binaries as classes for type checking\nclass CurlBinary(Binary):\n    name: str = 'curl'\n    binproviders: list[InstanceOf[BinProvider]] = [BrewProvider(), EnvProvider()]\n\ncurl = CurlBinary().install()\nassert isinstance(curl, CurlBinary)                                 # CurlBinary is a unique type you can use in annotations now\nprint(curl.abspath, curl.version, curl.binprovider, curl.is_valid)  # Path('/opt/homebrew/bin/curl') SemVer('8.4.0') BrewProvider() True\ncurl.exec(cmd=['--version'])                                        # curl 8.4.0 (x86_64-apple-darwin23.0) libcurl/8.4.0 ...\n```\n\n### Supported Package Managers\n\n**So far it supports `installing`/`finding installed`/`updating`/`removing` packages on `Linux`/`macOS` with:**\n\n- `apt` (Ubuntu/Debian/etc.)\n- `brew` (macOS/Linux)\n- `pip` (Linux/macOS/Windows)\n- `npm` (Linux/macOS/Windows)\n- `cargo` (Linux/macOS)\n- `gem` (Linux/macOS)\n- `go get` / `go install` (`GoGetProvider`) (Linux/macOS)\n- `nix` (Linux/macOS)\n- `docker` (Linux/macOS, using local wrapper scripts that run `docker run`)\n- `env` (looks for existing version of binary in user's `$PATH` at runtime)\n- `vendor` (you can bundle vendored copies of packages you depend on within your source)\n\n*Planned:* `apk`, `pkg`, *and more using `ansible`/[`pyinfra`](https://github.com/pyinfra-dev/pyinfra)...*\n\n`DockerProvider` expects image refs as install args, typically via overrides on a `Binary`. It writes a local wrapper script for the binary and executes it via `docker run ...`; the binary version is parsed from the image tag, so semver-like tags work best.\n\n`NpmProvider` prefers a real `npm` executable when both `npm` and `pnpm` are installed. If `npm` is unavailable, it can still drive installs and metadata lookups through `pnpm` using the same provider API.\n\n---\n\n\n## Usage\n\n```bash\npip install abx-pkg\n```\n\n### Lazy Provider Singletons\n\nAll built-in providers are available as lazy singletons — just import them by name:\n\n```python\nfrom abx_pkg import apt, brew, pip, npm, env\n\napt.install('curl')\nenv.load('wget')\n```\n\nThese are instantiated on first access and cached for reuse. If you need custom configuration, you can still instantiate provider classes directly:\n\n```python\nfrom abx_pkg import PipProvider\n\ncustom_pip = PipProvider(abspath_handler={...})\n```\n\n### Version Floors\n\n`Binary.min_version` is enforced after a provider resolves or installs a binary. Provider discovery can still succeed, but the final `Binary` will be rejected if the loaded version is below your required floor.\n\n```python\nfrom abx_pkg import Binary, SemVer, env, brew\n\ncurl = Binary(\n    name=\"curl\",\n    min_version=SemVer(\"8.0.0\"),\n    binproviders=[env, brew],\n).load_or_install()\n```\n\nUse `min_version=None` to explicitly disable version floor checks.\n\n### [`BinProvider`](https://github.com/ArchiveBox/abx-pkg/blob/main/abx_pkg/binprovider.py#:~:text=class%20BinProvider)\n\n**Implementations: `EnvProvider`, `AptProvider`, `BrewProvider`, `PipProvider`, `NpmProvider`**\n\nThis type represents a \"provider of binaries\", e.g. a package manager like `apt`/`pip`/`npm`, or `env` (which finds binaries in your `$PATH`).\n\n`BinProvider`s implement the following interface:\n* `.INSTALLER_BIN -\u003e /opt/homebrew/bin/brew`  provider's pkg manager location\n* `.PATH -\u003e PATHStr('/opt/homebrew/bin:/usr/local/bin:...')`  where provider stores bins\n* `get_install_args(bin_name: str) -\u003e InstallArgs(['curl', 'libcurl4', '...])` find installer args for a bin\n- `install(bin_name: str)` install a bin using binprovider to install needed packages\n- `update(bin_name: str)` update a bin using the binprovider's package manager\n- `uninstall(bin_name: str)` remove a bin using the binprovider's package manager\n- `load(bin_name: str)`  find an existing installed binary\n- `load_or_install(bin_name: str)` `-\u003e` `Binary` find existing / install if needed\n- `get_version(bin_name: str) -\u003e SemVer('1.0.0')`  get currently installed version\n- `get_abspath(bin_name: str) -\u003e Path('/absolute/path/to/bin')` get installed bin abspath\n* `get_abspaths(bin_name: str) -\u003e [Path('/opt/homebrew/bin/curl'), Path('/other/paths/to/curl'), ...]` get all matching bins found\n* `get_sha256(bin_name: str) -\u003e str` get sha256 hash hexdigest of the binary\n\n`install()` and `update()` return a loaded binary with fresh `abspath` / `version` / `sha256` metadata.\n`uninstall()` removes the package and returns `True` when the provider action succeeded.\n\n\n```python\nfrom abx_pkg import env, apt, pip\n\n### Example: Finding an existing install of bash using the system $PATH environment\nbash = env.load(bin_name='bash')      # Binary('bash', provider=env)\nprint(bash.abspath)                   # Path('/opt/homebrew/bin/bash')\nprint(bash.version)                   # SemVer('5.2.26')\nbash.exec(['-c', 'echo hi'])          # hi\n\n### Example: Installing curl using the apt package manager\ncurl = apt.install(bin_name='curl')   # Binary('curl', provider=apt)\nprint(curl.abspath)                   # Path('/usr/bin/curl')\nprint(curl.version)                   # SemVer('8.4.0')\nprint(curl.sha256)                    # 9fd780521c97365f94c90724d80a889097ae1eeb2ffce67b87869cb7e79688ec\ncurl.exec(['--version'])              # curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 ...\n\n# Example: Updating and removing curl with the same provider\ncurl = apt.update(bin_name='curl')\nassert curl.is_valid\nassert apt.uninstall(bin_name='curl') is True\n\n### Example: Finding/Installing django with pip (w/ customized binpath resolution behavior)\nfrom abx_pkg import PipProvider\ncustom_pip = PipProvider(\n    abspath_handler={'*': lambda self, bin_name, **context: inspect.getfile(bin_name)},  # use python inspect to get path instead of os.which\n)\ndjango_bin = custom_pip.load_or_install('django') # Binary('django', provider=custom_pip)\nprint(django_bin.abspath)             # Path('/usr/lib/python3.10/site-packages/django/__init__.py')\nprint(django_bin.version)             # SemVer('5.0.2')\n```\n\n### [`Binary`](https://github.com/ArchiveBox/abx-pkg/blob/main/abx_pkg/binary.py#:~:text=class%20Binary)\n\nThis type represents a single binary dependency aka a package (e.g. `wget`, `curl`, `ffmpeg`, etc.).  \nIt can define one or more `BinProvider`s that it supports, along with overrides to customize the behavior for each.\n\n`Binary`s implement the following interface:\n- `load()`, `install()`, `update()`, `uninstall()`, `load_or_install()` `-\u003e` `Binary`\n- `binprovider: InstanceOf[BinProvider]`\n- `abspath: Path`\n- `abspaths: list[Path]`\n- `version: SemVer`\n- `sha256: str`\n\n`Binary.install()` and `Binary.update()` return a fresh loaded `Binary`.\n`Binary.uninstall()` returns a `Binary` with `binprovider`, `abspath`, `version`, and `sha256` cleared after removal.\n`Binary.load()`, `Binary.install()`, `Binary.load_or_install()`, and `Binary.update()` all enforce `min_version` consistently.\n\n```python\nfrom abx_pkg import BinProvider, Binary, BinProviderName, BinName, ProviderLookupDict, SemVer, BrewProvider\nfrom abx_pkg import env, pip, apt\n\nclass CustomBrewProvider(BrewProvider):\n    name: str = 'custom_brew'\n\n    def get_macos_packages(self, bin_name: str, **context) -\u003e list[str]:\n        extra_packages_lookup_table = json.load(Path('macos_packages.json'))\n        return extra_packages_lookup_table.get(platform.machine(), [bin_name])\n\n\n### Example: Create a reusable class defining a binary and its providers\nclass YtdlpBinary(Binary):\n    name: BinName = 'ytdlp'\n    description: str = 'YT-DLP (Replacement for YouTube-DL) Media Downloader'\n\n    binproviders_supported: list[BinProvider] = [env, pip, apt, CustomBrewProvider()]\n    \n    # customize installed package names for specific package managers\n    overrides: dict[BinProviderName, ProviderLookupDict] = {\n        'pip': {'install_args': ['yt-dlp[default,curl-cffi]']}, # can use literal values (install_args -\u003e list[str], version -\u003e SemVer, abspath -\u003e Path, install -\u003e str log)\n        'apt': {'install_args': lambda: ['yt-dlp', 'ffmpeg']},  # also accepts any pure Callable that returns a list of packages\n        'brew': {'install_args': 'self.get_macos_packages'},    # also accepts string reference to function on self (where self is the BinProvider)\n    }\n\n\nytdlp = YtdlpBinary().load_or_install()\nprint(ytdlp.binprovider)                  # BrewProvider(...)\nprint(ytdlp.abspath)                      # Path('/opt/homebrew/bin/yt-dlp')\nprint(ytdlp.abspaths)                     # [Path('/opt/homebrew/bin/yt-dlp'), Path('/usr/local/bin/yt-dlp')]\nprint(ytdlp.version)                      # SemVer('2024.4.9')\nprint(ytdlp.sha256)                       # 46c3518cfa788090c42e379971485f56d007a6ce366dafb0556134ca724d6a36\nprint(ytdlp.is_valid)                     # True\n\n# Lifecycle actions preserve the Binary type and refresh/clear loaded metadata as needed\nytdlp = ytdlp.update()\nassert ytdlp.is_valid\nytdlp = ytdlp.uninstall()\nassert ytdlp.abspath is None and ytdlp.version is None\n```\n\n```python\nfrom abx_pkg import BinProvider, Binary, BinProviderName, BinName, ProviderLookupDict, SemVer\nfrom abx_pkg import env, apt\n\n#### Example: Create a binary that uses Podman if available, or Docker otherwise\nclass DockerBinary(Binary):\n    name: BinName = 'docker'\n\n    binproviders_supported: list[BinProvider] = [env, apt]\n    \n    overrides: dict[BinProviderName, ProviderLookupDict] = {\n        'env': {\n            # example: prefer podman if installed (falling back to docker)\n            'abspath': lambda: os.which('podman') or os.which('docker') or os.which('docker-ce'),\n        },\n        'apt': {\n            # example: vary installed package name based on your CPU architecture\n            'install_args': {\n                'amd64': ['docker'],\n                'armv7l': ['docker-ce'],\n                'arm64': ['docker-ce'],\n            }.get(platform.machine(), 'docker'),\n        },\n    }\n\ndocker = DockerBinary().load_or_install()\nprint(docker.binprovider)                 # EnvProvider()\nprint(docker.abspath)                     # Path('/usr/local/bin/podman')\nprint(docker.abspaths)                    # [Path('/usr/local/bin/podman'), Path('/opt/homebrew/bin/podman')]\nprint(docker.version)                     # SemVer('6.0.2')\nprint(docker.is_valid)                    # True\n\n# You can also pass **kwargs to override properties at runtime,\n# e.g. if you want to force the abspath to be at a specific path:\ncustom_docker = DockerBinary(abspath='~/custom/bin/podman').load()\nprint(custom_docker.name)                 # 'docker'\nprint(custom_docker.binprovider)          # EnvProvider()\nprint(custom_docker.abspath)              # Path('/Users/example/custom/bin/podman')\nprint(custom_docker.version)              # SemVer('5.0.2')\nprint(custom_docker.is_valid)             # True\n```\n\n### [`SemVer`](https://github.com/ArchiveBox/abx-pkg/blob/main/abx_pkg/semver.py#:~:text=class%20SemVer)\n\n```python\nfrom abx_pkg import SemVer\n\n### Example: Use the SemVer type directly for parsing \u0026 verifying version strings\nSemVer.parse('Google Chrome 124.0.6367.208+beta_234. 234.234.123')  # SemVer(124, 0, 6367')\nSemVer.parse('2024.04.05)                                           # SemVer(2024, 4, 5)\nSemVer.parse('1.9+beta')                                            # SemVer(1, 9, 0)\nstr(SemVer(1, 9, 0))                                                # '1.9.0'\n```\n\u003cbr/\u003e\n\n\u003e These types are all meant to be used library-style to make writing your own apps easier.  \n\u003e e.g. you can use it to build things like: [`playwright install --with-deps`](https://playwright.dev/docs/browsers#install-system-dependencies))\n\n\n\u003cbr/\u003e\n\n---\n---\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n## Django Usage\n\nWith a few more packages, you get type-checked Django fields \u0026 forms that support `BinProvider` and `Binary`.\n\n\u003e [!TIP]\n\u003e For the full Django experience, we recommend installing these 3 excellent packages:\n\u003e - [`django-admin-data-views`](https://github.com/MrThearMan/django-admin-data-views)\n\u003e - [`django-pydantic-field`](https://github.com/surenkov/django-pydantic-field)\n\u003e - [`django-jsonform`](https://django-jsonform.readthedocs.io/)  \n\u003e `pip install abx-pkg django-admin-data-views django-pydantic-field django-jsonform`\n\n\u003cbr/\u003e\n\n### Django Model Usage: Store `BinProvider` and `Binary` entries in your model fields\n\n```bash\npip install django-pydantic-field\n```\n\n*Fore more info see the [`django-pydantic-field`](https://github.com/surenkov/django-pydantic-field) docs...*\n\nExample Django `models.py` showing how to store `Binary` and `BinProvider` instances in DB fields:\n```python\nfrom typing import List\nfrom django.db import models\nfrom pydantic import InstanceOf\nfrom abx_pkg import BinProvider, Binary, SemVer\nfrom django_pydantic_field import SchemaField\n\nclass Binary(models.Model):\n    name = models.CharField(max_length=63)\n    binary: Binary = SchemaField()\n    binproviders: list[InstanceOf[BinProvider]] = SchemaField(default=[])\n    version: SemVer = SchemaField(default=(0,0,1))\n```\n\nAnd here's how to save a `Binary` using the example model:\n```python\nfrom abx_pkg import Binary, SemVer, env\n\n# find existing curl Binary in $PATH\ncurl = Binary(name='curl').load()\n\n# save it to the DB using our new model\nobj = Binary(\n    name='curl',\n    binary=curl,                                  # store Binary/BinProvider/SemVer values directly in fields\n    binproviders=[env],                           # no need for manual JSON serialization / schema checking\n    min_version=SemVer('6.5.0'),\n)\nobj.save()\n```\n\nWhen fetching it back from the DB, the `Binary` field is auto-deserialized / immediately usable:\n```\nobj = Binary.objects.get(name='curl')    # everything is transparently serialized to/from the DB,\n                                                  # and is ready to go immediately after querying:\nassert obj.binary.abspath == curl.abspath\nprint(obj.binary.abspath)                         #   Path('/usr/local/bin/curl')\nobj.binary.exec(['--version'])                    #   curl 7.81.0 (x86_64-apple-darwin23.0) libcurl/7.81.0 ...\n```\n*For a full example see our provided [`django_example_project/`](https://github.com/ArchiveBox/abx-pkg/tree/main/django_example_project)...*\n\n\u003cbr/\u003e\n\n### Django Admin Usage: Display `Binary` objects nicely in the Admin UI\n\n\u003cimg height=\"220\" alt=\"Django Admin binaries list view\" src=\"https://github.com/ArchiveBox/abx-pkg/assets/511499/a9980217-f39e-434e-b266-20cd6feb17c3\" align=\"top\"\u003e\u003cimg height=\"220\" alt=\"Django Admin binaries detail view\" src=\"https://github.com/ArchiveBox/abx-pkg/assets/511499/d4d9086e-c8f4-4b6e-8ee8-8c8a864715b0\" align=\"top\"\u003e\n\n```bash\npip install abx-pkg django-admin-data-views\n```\n*For more info see the [`django-admin-data-views`](https://github.com/MrThearMan/django-admin-data-views) docs...*\n\nThen add this to your `settings.py`:\n```python\nINSTALLED_APPS = [\n    # ...\n    'admin_data_views'\n    'abx_pkg'\n    # ...\n]\n\n# point these to a function that gets the list of all binaries / a single binary\nABX_PKG_GET_ALL_BINARIES = 'abx_pkg.views.get_all_binaries'\nABX_PKG_GET_BINARY = 'abx_pkg.views.get_binary'\n\nADMIN_DATA_VIEWS = {\n    \"NAME\": \"Environment\",\n    \"URLS\": [\n        {\n            \"route\": \"binaries/\",\n            \"view\": \"abx_pkg.views.binaries_list_view\",\n            \"name\": \"binaries\",\n            \"items\": {\n                \"route\": \"\u003cstr:key\u003e/\",\n                \"view\": \"abx_pkg.views.binary_detail_view\",\n                \"name\": \"binary\",\n            },\n        },\n        # Coming soon: binprovider_list_view + binprovider_detail_view ...\n    ],\n}\n```\n*For a full example see our provided [`django_example_project/`](https://github.com/ArchiveBox/abx-pkg/tree/main/django_example_project)...*\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ci\u003eNote: If you override the default site admin, you must register the views manually...\u003c/i\u003e\u003c/summary\u003e\n\u003cbr/\u003e\u003cbr/\u003e\n\u003cb\u003e\u003ccode\u003eadmin.py\u003c/code\u003e:\u003c/b\u003e\n\u003cbr/\u003e\n\u003cpre\u003e\u003ccode\u003e\nclass YourSiteAdmin(admin.AdminSite):\n    \"\"\"Your customized version of admin.AdminSite\"\"\"\n    ...\n\u003cbr/\u003e\ncustom_admin = YourSiteAdmin()\ncustom_admin.register(get_user_model())\n...\nfrom abx_pkg.admin import register_admin_views\nregister_admin_views(custom_admin)\n\u003c/code\u003e\u003c/pre\u003e\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n### ~~Django Admin Usage: JSONFormWidget for editing `BinProvider` and `Binary` data~~\n\n\u003cdetails\u003e\u003csummary\u003eExpand to see more...\u003c/summary\u003e\n\n\u003cimg src=\"https://github.com/ArchiveBox/abx-pkg/assets/511499/63705a57-4f62-4dbe-9f3a-0515323d8b5e\" width=\"600px\"/\u003e\n\n\u003e [!IMPORTANT]\n\u003e This feature is coming soon but is blocked on a few issues being fixed first:  \n\u003e - https://github.com/surenkov/django-pydantic-field/issues/64\n\u003e - https://github.com/surenkov/django-pydantic-field/issues/65\n\u003e - https://github.com/surenkov/django-pydantic-field/issues/66\n\n~~Install `django-jsonform` to get auto-generated Forms for editing BinProvider, Binary, etc. data~~\n```bash\npip install django-pydantic-field django-jsonform\n```\n*For more info see the [`django-jsonform`](https://django-jsonform.readthedocs.io/) docs...*\n\n`admin.py`:\n```python\nfrom django.contrib import admin\nfrom django_jsonform.widgets import JSONFormWidget\nfrom django_pydantic_field.v2.fields import PydanticSchemaField\n\nclass MyModelAdmin(admin.ModelAdmin):\n    formfield_overrides = {PydanticSchemaField: {\"widget\": JSONFormWidget}}\n\nadmin.site.register(MyModel, MyModelAdmin)\n```\n\n\u003c/details\u003e\n\n*For a full example see our provided [`django_example_project/`](https://github.com/ArchiveBox/abx-pkg/tree/main/django_example_project)...*\n\n\u003cbr/\u003e\n\n---\n\n\u003cbr/\u003e\n\n\n## Logging\n\n`abx-pkg` uses the standard Python `logging` module. By default it stays quiet unless your application configures logging explicitly.\n\n```python\nimport logging\nfrom abx_pkg import Binary, env, configure_logging\n\nconfigure_logging(logging.INFO)\n\npython = Binary(name='python', binproviders=[env]).load()\n```\n\nTo enable Rich logging:\n\n```bash\npip install \"abx-pkg[rich]\"\n```\n\n```python\nimport logging\nfrom abx_pkg import Binary, EnvProvider, configure_rich_logging\n\nconfigure_rich_logging(logging.DEBUG)\n\npython = Binary(name='python', binproviders=[EnvProvider()]).load()\n```\n\nDebug logging is hardened so logging itself does not become the failure. If a provider/model object has a broken or overly-expensive `repr()`, `abx-pkg` falls back to a short `ClassName(...)` summary instead of raising while formatting log output.\n\n`configure_rich_logging(...)` uses `rich.logging.RichHandler` under the hood, so log levels, paths, arguments, and command lines render with terminal colors when supported.\n\nYou can also manage it with standard logging primitives:\n\n```python\nimport logging\n\nlogging.basicConfig(level=logging.INFO)\nlogging.getLogger(\"abx_pkg\").setLevel(logging.DEBUG)\n```\n\n## Examples\n\n### Advanced: Implement your own package manager behavior by subclassing BinProvider\n\n```python\nfrom subprocess import run, PIPE\n\nfrom abx_pkg import BinProvider, BinProviderName, BinName, SemVer\n\nclass CargoProvider(BinProvider):\n    name: BinProviderName = 'cargo'\n    \n    def on_setup_paths(self):\n        if '~/.cargo/bin' not in sys.path:\n            sys.path.append('~/.cargo/bin')\n\n    def on_install(self, bin_name: BinName, **context):\n        install_args = self.on_get_install_args(bin_name)\n        installer_process = run(['cargo', 'install', *install_args.split(' ')], capture_output = True, text=True)\n        assert installer_process.returncode == 0\n\n    def on_get_install_args(self, bin_name: BinName, **context) -\u003e InstallArgs:\n        # optionally remap bin_names to strings passed to installer \n        # e.g. 'yt-dlp' -\u003e ['yt-dlp, 'ffmpeg', 'libcffi', 'libaac']\n        return [bin_name]\n\n    def on_get_abspath(self, bin_name: BinName, **context) -\u003e Path | None:\n        self.on_setup_paths()\n        return Path(os.which(bin_name))\n\n    def on_get_version(self, bin_name: BinName, **context) -\u003e SemVer | None:\n        self.on_setup_paths()\n        return SemVer(run([bin_name, '--version'], stdout=PIPE).stdout.decode())\n\ncargo = CargoProvider()\nrg = cargo.install(bin_name='ripgrep')\nprint(rg.binprovider)                   # CargoProvider()\nprint(rg.version)                       # SemVer(14, 1, 0)\n```\n\n\n\u003cbr/\u003e\n\n---\n\n\u003cbr/\u003e\n\n*Note:* this package used to be called `pydantic-pkgr`, it was renamed to `abx-pkg` on 2024-11-12.\n\n### TODO\n\n- [x] Implement initial basic support for `apt`, `brew`, and `pip`\n- [x] Provide editability and actions via Django Admin UI using [`django-pydantic-field`](https://github.com/surenkov/django-pydantic-field) and [`django-jsonform`](https://django-jsonform.readthedocs.io/en/latest/)\n- [ ] Add `preinstall` and `postinstall` hooks for things like adding `apt` sources and running cleanup scripts\n- [ ] Implement more package managers (`apk`, `ppm`, `pkg`, etc.)\n\n\n### Other Packages We Like\n\n- https://github.com/MrThearMan/django-signal-webhooks\n- https://github.com/MrThearMan/django-admin-data-views\n- https://github.com/lazybird/django-solo\n- https://github.com/joshourisman/django-pydantic-settings\n- https://github.com/surenkov/django-pydantic-field\n- https://github.com/jordaneremieff/djantic\n\n[coverage-badge]: https://coveralls.io/repos/github/ArchiveBox/abx-pkg/badge.svg?branch=main\n[status-badge]: https://img.shields.io/github/actions/workflow/status/ArchiveBox/abx-pkg/test.yml?branch=main\n[pypi-badge]: https://img.shields.io/pypi/v/abx-pkg?v=1\n[licence-badge]: https://img.shields.io/github/license/ArchiveBox/abx-pkg?v=1\n[repo-badge]: https://img.shields.io/github/last-commit/ArchiveBox/abx-pkg?v=1\n[issues-badge]: https://img.shields.io/github/issues-raw/ArchiveBox/abx-pkg?v=1\n[version-badge]: https://img.shields.io/pypi/pyversions/abx-pkg?v=1\n[downloads-badge]: https://img.shields.io/pypi/dm/abx-pkg?v=1\n[django-badge]: https://img.shields.io/pypi/djversions/abx-pkg?v=1\n\n[coverage]: https://coveralls.io/github/ArchiveBox/abx-pkg?branch=main\n[status]: https://github.com/ArchiveBox/abx-pkg/actions/workflows/test.yml\n[pypi]: https://pypi.org/project/abx-pkg\n[licence]: https://github.com/ArchiveBox/abx-pkg/blob/main/LICENSE\n[repo]: https://github.com/ArchiveBox/abx-pkg/commits/main\n[issues]: https://github.com/ArchiveBox/abx-pkg/issues\n","funding_links":["https://github.com/sponsors/ArchiveBox","https://github.com/sponsors/pirate","https://donate.archivebox.io/"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchivebox%2Fabx-pkg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farchivebox%2Fabx-pkg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchivebox%2Fabx-pkg/lists"}