Skip to main content
Archived

This project has been archived by its maintainers, and is no longer receiving any updates.

pydantic-pkgr         📦 apt  brew  pip  npm  ++
A typed Python API wrapper for common system package managers.


PyPI Python Version Django Version GitHub GitHub Last Commit


This is a Python 3 library for installing & managing packages with a variety of package managers.

pip install pydantic-pkgr

✨ Built with pydantic v2 for strong static typing guarantees and json import/export compatibility
📦 Provides consistent cross-platform interfaces for dependency resolution & installation at runtime
🌈 Supports django >= 4.0, django-ninja, and OpenAPI + django-jsonform out-of-the-box
🦄 Plays well with pyinfra and ansible for advanced orchestration use-cases

Built by ArchiveBox to install & auto-update our extractor dependencies at runtime (chrome, wget, curl, etc.) on macOS/Linux/Docker.


Source Code: https://github.com/ArchiveBox/pydantic-pkgr/

Documentation: https://github.com/ArchiveBox/pydantic-pkgr/blob/main/README.md


from pydantic_pkgr import AptProvider

# Example: Install curl using the apt provider
apt = AptProvider()
curl = apt.install('curl')

print(curl.provider)                       # 'apt'
print(curl.abspath)                        # Path('/usr/bin/curl')
print(curl.version)                        # SemVer('7.81.0')
curl.exec(['--version'])                   # curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 ...
from pydantic_pkgr import Binary, BinName, BinProvider

# Example: Create a re-usable curl Binary object that defines its install methods
curl = Binary(name='curl', providers=[BrewProvider(), EnvProvider()])
curl = curl.install()

print(curl.provider)                       # 'brew'
print(curl.abspath)                        # Path('/opt/homebrew/bin/curl')
print(curl.version)                        # SemVer('8.4.0')
curl.exec(['--version'])                   # curl 8.4.0 (x86_64-apple-darwin23.0) libcurl/8.4.0 ...
print(curl.model_dump_json())              # ... everything can also be dumped/loaded as json
print(curl.model_json_schema())            # ... all types provide OpenAPI-ready JSON schemas

Supported Package Managers

So far it supports installing/finding installed/updating/removing packages on Linux/macOS with:

  • apt (Ubuntu/Debian/etc.)
  • brew (macOS/Linux)
  • pip (Linux/macOS/Windows)
  • npm (Linux/macOS/Windows)
  • env (looks for existing version of binary in user's $PATH at runtime)
  • vendor (you can bundle vendored copies of packages you depend on within your source)

Planned: docker, cargo, nix, apk, go get, gem, pkg, and more using ansible/pyinfra...


Usage

pip install pydantic-pkgr

BinProvider

Implementations: EnvProvider, AptProvider, BrewProvider, PipProvider, NpmProvider

This type represents a "provider of binaries", e.g. a package manager like apt/pip/npm, or env (which finds binaries in your $PATH).

BinProviders implement the following interface:

  • load(bin_name: str), install(bin_name: str), load_or_install(bin_name: str) -> Binary
  • install(bin_name: str)
  • get_abspath(bin_name: str) -> Path('/absolute/path/to/bin')
  • get_version(bin_name: str) -> SemVer('1.0.0')
  • get_subdeps(bin_name: str) -> InstallStr('somepackage some-extras')
import platform
from typing import List
from pydantic_pkgr import EnvProvider, PipProvider, AptProvider, BrewProvider

### Example: Finding an existing install of bash using the system $PATH environment
env = EnvProvider()
bash = env.load(bin_name='bash')
print(bash.abspath)                   # Path('/opt/homebrew/bin/bash')
print(bash.version)                   # SemVer('5.2.26')
bash.exec(['-c', 'echo hi'])          # hi

### Example: Installing curl using the apt package manager
apt = AptProvider()
curl = apt.install(bin_name='curl')
print(curl.version)                   # Path('/usr/bin/curl')
print(curl.version)                   # SemVer('8.4.0')
curl.exec(['--version'])              # curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 ...

### Example: Finding/Installing django with pip (w/ customized binpath resolution behavior)
pip = PipProvider(
    abspath_provider={'*': lambda bin_name, **context: inspect.getfile(bin_name)},  # use python inspect to get path instead of os.which
)
django_bin = pip.load_or_install(bin_name='django')
print(django_bin.abspath)             # Path('/usr/lib/python3.10/site-packages/django/__init__.py')
print(django_bin.version)             # SemVer('5.0.2')

Binary

This type represents a single binary dependency aka a package (e.g. wget, curl, ffmpeg, etc.).
It can define one or more BinProviders that it supports, along with overrides to customize the behavior for each.

Binarys implement the following interface:

  • load(), install(), load_or_install() -> Binary
  • provider: BinProviderName (BinProviderName == str)
  • abspath: Path
  • version: SemVer
from pydantic_pkgr import BinProvider, Binary, BinProviderName, BinName, ProviderLookupDict, SemVer

### Example: Create a re-usable class defining a binary and its providers
class YtdlpBinary(Binary):
    name: BinName = 'ytdlp'
    description: str = 'YT-DLP (Replacement for YouTube-DL) Media Downloader'

    providers_supported: List[BinProvider] = [EnvProvider(), PipProvider(), AptProvider(), BrewProvider()]
    
    # customize installed package names for specific package managers
    provider_overrides: Dict[BinProviderName, ProviderLookupDict] = {
        'pip': {'subdeps': lambda: 'yt-dlp[default,curl-cffi]'}},
        'apt': {'subdeps': lambda: 'yt-dlp ffmpeg'}},
        'brew': {'subdeps': 'some.other.module.get_brew_subdeps'}},  # also accepts dotted import path to function
    }

ytdlp = YtdlpBinary().load_or_install()
print(ytdlp.provider)                     # 'brew'
print(ytdlp.abspath)                      # Path('/opt/homebrew/bin/yt-dlp')
print(ytdlp.version)                      # SemVer('2024.4.9')
print(ytdlp.is_valid)                     # True
from pydantic_pkgr import BinProvider, Binary, BinProviderName, BinName, ProviderLookupDict, SemVer

#### Example: Create a binary that uses Podman if available, or Docker otherwise
class DockerBinary(Binary):
    name: BinName = 'docker'

    providers_supported: List[BinProvider] = [EnvProvider(), AptProvider()]
    
    provider_overrides: Dict[BinProviderName, ProviderLookupDict] = {
        'env': {
            # prefer podman if installed (or fall back to docker)
            'abspath': lambda: os.which('podman') or os.which('docker') or os.which('docker-ce'),
        },
        'apt': {
            # install docker OR docker-ce (varies based on CPU architecture)
            'subdeps': lambda: {
                'amd64': 'docker',
                'armv7l': 'docker-ce',
                'arm64': 'docker-ce',
            }.get(platform.machine()) or 'docker',
        },
    }

docker = DockerBinary().load_or_install()
print(docker.provider)                    # 'env'
print(docker.abspath)                     # Path('/usr/local/bin/podman')
print(docker.version)                     # SemVer('6.0.2')
print(docker.is_valid)                    # True

# You can also pass **kwargs to override properties at runtime,
# e.g. if you want to force the abspath to be at a specific path:
custom_docker = DockerBinary(abspath='~/custom/bin/podman').load()
print(custom_docker.name)                 # 'docker'
print(custom_docker.provider)             # 'env'
print(custom_docker.abspath)              # Path('/Users/example/custom/bin/podman')
print(custom_docker.version)              # SemVer('5.0.2')
print(custom_docker.is_valid)             # True

SemVer

from pydantic_pkgr import SemVer

### Example: Use the SemVer type directly for parsing & verifying version strings
SemVer.parse('Google Chrome 124.0.6367.208+beta_234. 234.234.123')  # SemVer(124, 0, 6367')
SemVer.parse('2024.04.05)                                           # SemVer(2024, 4, 5)
SemVer.parse('1.9+beta')                                            # SemVer(1, 9, 0)
str(SemVer(1, 9, 0))                                                # '1.9.0'



Django Usage

The pydantic ecosystem help us get auto-generated, type-checked Django fields & forms that support BinProvider and Binary.

[!TIP] For the full experience, we recommend installing these 3 excellent packages:


Django Model Usage: Store BinProvider and Binary entries in your model fields

pip install django-pydantic-field

Fore more info see the django-pydantic-field docs...

Usage in your models.py:

from django.db import models
from django_pydantic_field import SchemaField

from pydantic_pkgr import BinProvider, EnvProvider, Binary

DEFAULT_PROVIDER = EnvProvider()

class MyModel(models.Model):
    ...

    # SchemaField supports storing a single BinProvider/Binary in a field...
    favorite_binprovider: BinProvider = SchemaField(default=DEFAULT_PROVIDER)

    # ... or inside a collection type like list[...] dict[...]
    optional_binaries: list[Binary] = SchemaField(default=[])

curl = Binary(name='curl', providers=[DEFAULT_PROVIDER]).load()

obj = MyModel(optional_binaries=[curl])
obj.save()

assert obj.favorite_binprovider == DEFAULT_PROVIDER
assert obj.optional_binaries[0].provider == DEFAULT_PROVIDER

Django Admin Usage: Show read-only list of Binaries in Admin UI

pip install pydantic-pkgr django-admin-data-views

For more info see the django-admin-data-views docs...

Then add this to your settings.py:

INSTALLED_APPS = [
    # ...
    'admin_data_views'
    'pydantic_pkgr'
    # ...
]

ADMIN_DATA_VIEWS = {
    "NAME": "Environment",
    "URLS": [
        {
            "route": "binaries/",
            "view": "pydantic_pkgr.views.binaries_list_view",
            "name": "binaries",
            "items": {
                "route": "<str:key>/",
                "view": "pydantic_pkgr.views.binary_detail_view",
                "name": "binary",
            },
        },
        # Coming soon: binprovider_list_view + binprovider_detail_view ...
    ],
}
Note: If you override the default site admin, you must register the views manually...

admin.py:

class YourSiteAdmin(admin.AdminSite):
    """Your customized version of admin.AdminSite"""
    ...

custom_admin = YourSiteAdmin() custom_admin.register(get_user_model()) ... from pydantic_pkgr.admin import register_admin_views register_admin_views(custom_admin)

Django Admin Usage: JSONFormWidget for editing BinProvider and Binary data

Install django-jsonform to get auto-generated Forms for editing BinProvider, Binary, etc. data

pip install django-pydantic-field django-jsonform

For more info see the django-jsonform docs...

admin.py:

from django.contrib import admin
from django_jsonform.widgets import JSONFormWidget
from django_pydantic_field.v2.fields import PydanticSchemaField

class MyModelAdmin(admin.ModelAdmin):
    formfield_overrides = {PydanticSchemaField: {"widget": JSONFormWidget}}

admin.site.register(MyModel, MyModelAdmin)



Examples

Advanced: Implement your own package manager behavior by subclassing BinProvider

from subprocess import run, PIPE

from pydantic_pkgr import BinProvider, BinProviderName, BinName, SemVer

class CargoProvider(BinProvider):
    name: BinProviderName = 'cargo'
    
    def on_setup_paths(self):
        if '~/.cargo/bin' not in sys.path:
            sys.path.append('~/.cargo/bin')

    def on_install(self, bin_name: BinName, **context):
        subdeps = self.on_get_subdeps(bin_name)
        installer_process = run(['cargo', 'install', *subdeps.split(' ')], stdout=PIPE, stderr=PIPE)
        assert installer_process.returncode == 0

    def on_get_subdeps(self, bin_name: BinName, **context) -> InstallStr:
        # optionally remap bin_names to strings passed to installer 
        # e.g. 'yt-dlp' -> 'yt-dlp ffmpeg libcffi libaac'
        return bin_name

    def on_get_abspath(self, bin_name: BinName, **context) -> Path | None:
        self.on_setup_paths()
        return Path(os.which(bin_name))

    def on_get_version(self, bin_name: BinName, **context) -> SemVer | None:
        self.on_setup_paths()
        return SemVer(run([bin_name, '--version'], stdout=PIPE).stdout.decode())

cargo = CargoProvider()
rg = cargo.install(bin_name='ripgrep')
print(rg.provider)                      # 'cargo'
print(rg.version)                       # SemVer(14, 1, 0)



TODO

  • Implement initial basic support for apt, brew, and pip
  • Provide editability and actions via Django Admin UI using django-pydantic-field and django-jsonform
  • Implement update and remove actions on BinProviders
  • Add preinstall and postinstall hooks for things like adding apt sources and running cleanup scripts
  • Implement more package managers

Other Packages We Like

Release files for pydantic-pkgr 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pydantic-pkgr 0.1.1
File Size Uploaded
pydantic_pkgr-0.1.1.tar.gz 22.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pydantic-pkgr 0.1.1
File Interpreter ABI Platform
pydantic_pkgr-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 46.7 kB

Release files / pydantic_pkgr-0.1.1.tar.gz

Download URL pydantic_pkgr-0.1.1.tar.gz
Size 22.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7354861a687cad408b655770a73ff5473693ea82a38977044d326af5154481ed
BLAKE2b-256 checksum
How to use checksums
3d4c8fba534202f3a19dfe65062e67a54394b4e55745c4cb5be8a91997e04fab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.15.1 CPython/3.12.3 Darwin/23.4.0

Release files / pydantic_pkgr-0.1.1-py3-none-any.whl

Download URL pydantic_pkgr-0.1.1-py3-none-any.whl
Size 23.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
793cc1c5023bf94b988835c9c8e71e8422418572935e7b83d2d448ddcd80f17f
BLAKE2b-256 checksum
How to use checksums
484c00116baf04bc79778d2b8bcfe75cdad1aa090a6fa43c0e118b5d7532c2b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.15.1 CPython/3.12.3 Darwin/23.4.0

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page