Extract Python models and dependencies into standalone packages
Project description
pydistill
Extract Python models and their transitive dependencies into standalone, self-contained packages.
Why?
You have a large Python project and want to share some Pydantic models (or any Python classes) with another service without:
- Publishing your entire codebase
- Manually copying files and fixing imports
- Maintaining a separate package by hand
PyDistill automates this: point it at your entry points, and it extracts exactly what's needed into an importable package.
Installation
# With uv
uv add pydistill
# With pip
pip install pydistill
Quick Start
# Extract a model and its dependencies
pydistill \
--entry myapp.models:User \
--entry myapp.models:Order \
--base-package myapp \
--output-package extracted_models \
--output-dir ./dist/extracted_models
This will:
- Discover all local imports transitively from
UserandOrder - Copy the relevant source files to
./dist/extracted_models/ - Rewrite imports (
myapp.*→extracted_models.*) - Generate
__init__.pyfiles so the package is importable - Generate
pyproject.tomlso the output is installable withpip
Install the extracted output directly:
pip install ./dist/extracted_models
Configuration
CLI Options
pydistill [OPTIONS]
Options:
-e, --entry MODULE:NAME Entry point (can be repeated)
-b, --base-package PACKAGE Base package to extract from
-p, --output-package PACKAGE Output package name
-o, --output-dir DIR Output directory
-s, --source-root DIR Additional source roots (can be repeated)
-c, --config FILE Path to config file
-n, --dry-run Show what would be extracted
--clean Remove output directory first
-q, --quiet Suppress output
-f, --filesystem-only Skip importlib, use only filesystem resolution
--dist-name NAME Distribution name in generated pyproject.toml
--dist-version VERSION Distribution version in generated pyproject.toml
--dependency SPEC Dependency specifier (can be repeated)
--format Format extracted files (default: ruff format)
--formatter CMD Custom formatter command (implies --format)
--version Show version
-h, --help Show help
Configuration File
Create a pydistill.toml in your project root:
[pydistill]
entries = [
"myapp.users.models:User",
"myapp.orders.models:Order",
]
base_package = "myapp"
output_package = "extracted_models"
output_dir = "./dist/extracted_models"
clean = true
dist_name = "extracted-models"
dist_version = "0.1.0"
dependencies = ["pydantic>=2.0", "email-validator>=2.0"]
# filesystem_only = true # Enable for uninstallable projects
# format = true # Format extracted files
# formatter = "ruff format" # Custom formatter command
Then just run:
pydistill
PyDistill automatically searches for pydistill.toml in the current directory and parent directories.
CLI arguments override config file values.
Example
Given this project structure:
myapp/
├── __init__.py
├── common/
│ ├── __init__.py
│ └── types.py # Status enum, Address model
├── users/
│ ├── __init__.py
│ └── models.py # User model (imports common.types)
└── orders/
├── __init__.py
└── models.py # Order model (imports users.models, common.types)
Running:
pydistill -e myapp.orders.models:Order -b myapp -p extracted -o ./dist/extracted
Produces:
dist/extracted/
├── pyproject.toml
├── __init__.py
├── common/
│ ├── __init__.py
│ └── types.py # Status, Address (imports rewritten)
├── users/
│ ├── __init__.py
│ └── models.py # User (imports rewritten)
└── orders/
├── __init__.py
└── models.py # Order (imports rewritten)
All imports like from myapp.common.types import Status become from extracted.common.types import Status.
Extracting from Uninstallable Projects
Need to extract models from a project that can't be installed in your current environment (e.g., has Windows-only dependencies on macOS)? Use --filesystem-only:
pydistill \
-e their_app.models:SomeModel \
-b their_app \
-p extracted \
-o ./dist/extracted \
-s /path/to/their/project \
--filesystem-only
This skips Python's importlib and resolves modules purely via filesystem search in the specified source roots.
Formatting Extracted Code
The AST-based import rewriting can produce code that's not perfectly formatted. Use --format to automatically format extracted files:
# Use ruff (default)
pydistill -e myapp:Model -b myapp -p out -o ./dist --format
# Use black instead
pydistill ... --format --formatter black
# Custom ruff config
pydistill ... --format --formatter "ruff format --line-length 120"
Formatting failures are non-fatal—extraction will succeed even if the formatter is unavailable.
How It Works
- Parse entry points - Resolve
module:ClassNameto source files - Discover imports - Use Python's
astmodule to find allimportandfrom ... importstatements - BFS traversal - Follow imports transitively within the base package (ignores third-party like
pydantic,datetime, etc.) - Rewrite imports - Use
ast.NodeTransformerto rewrite module paths - Generate package - Copy files preserving structure, create
__init__.pyfiles, and writepyproject.toml
Use Cases
- Microservices: Share domain models between services without a monorepo
- API clients: Generate a lightweight client package with just the request/response models
- CI/CD: Automatically generate model packages when source changes
- Cross-platform extraction: Extract from projects with platform-specific dependencies using
--filesystem-only
Limitations
- Only follows imports within
--base-package(by design) - Extracts entire modules, not individual classes (a module containing
UserandAdminwill include both) - Relative imports are preserved as-is (they work in the output package since structure is maintained)
- Comments are not preserved: The AST-based rewriting uses
ast.parse()andast.unparse(), which strips comments from source files. This is acceptable for artifact/tarball use cases but means the extracted code loses inline documentation. Use--formatto ensure consistent formatting of the output. - Dynamic imports are not traced: Imports using
importlib.import_module()or__import__()cannot be statically analyzed - Star imports (
from x import *): The module path is rewritten correctly, but transitive dependencies imported via__all__re-exports may not be discovered
Development
# Clone and install
git clone https://github.com/yourorg/pydistill
cd pydistill
uv sync
# Run tests
uv run pytest
# Run pydistill locally
uv run pydistill --help
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pydistill-0.4.0.tar.gz.
File metadata
- Download URL: pydistill-0.4.0.tar.gz
- Upload date:
- Size: 55.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab929223edd50e46789cb1042a1cd019415b48743a60308e334331de65362962
|
|
| MD5 |
d6845d764373830b177b303016e11c09
|
|
| BLAKE2b-256 |
a54a882795fd51a264532f27972e8030f9c61b0503061da8e7da057444842a6d
|
File details
Details for the file pydistill-0.4.0-py3-none-any.whl.
File metadata
- Download URL: pydistill-0.4.0-py3-none-any.whl
- Upload date:
- Size: 16.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04e063c95005ba4ebdca77031cf8dbde039608d6bd2df1c1f9ecafe5d1ba2a03
|
|
| MD5 |
95694534159a20f5c0e39ecf1b229fe2
|
|
| BLAKE2b-256 |
12fec950c034d837ae4770d84111646568678ad302655db25bf2ef647e316cde
|