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.
This is a Python 3 library for installing & managing packages with a variety of package managers.
pip install pydantic-pkgr
✨ Built with
pydanticv2 for strong static typing guarantees and json import/export compatibility
📦 Provides consistent cross-platform interfaces for dependency resolution & installation at runtime
🌈 Supportsdjango>= 4.0,django-ninja, and OpenAPI +django-jsonformout-of-the-box
🦄 Plays well withpyinfraandansiblefor 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/ packages on updating/removingLinux/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$PATHat 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)->Binaryinstall(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()->Binaryprovider: BinProviderName(BinProviderName == str)abspath: Pathversion: 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-admin-data-viewsdjango-pydantic-fielddjango-jsonform
pip install pydantic-pkgr django-admin-data-views django-pydantic-field django-jsonform
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, andpip - Provide editability and actions via Django Admin UI using
django-pydantic-fieldanddjango-jsonform - Implement
updateandremoveactions on BinProviders - Add
preinstallandpostinstallhooks for things like addingaptsources 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)
| File | Size | Uploaded | |
|---|---|---|---|
| pydantic_pkgr-0.1.1.tar.gz | 22.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|