Build-time class-inheritance flattener. Write thin diff-only child classes against a base file; get back a single self-contained module with the inheritance inlined.
Generalizes HuggingFace transformers'
modular
codegen into a framework-agnostic library. Status: alpha.
When all three apply:
- Many sibling files share a base (10+).
- They are read and forked, not just called.
- Edits to the base must not silently ripple into already-released siblings.
For five subclasses in a normal app, plain Python inheritance is the right answer.
pip install pycodelift # or: uv add pycodeliftmypkg/
├── base/modeling_base.py # parent
└── child/modular_child.py # diff
mypkg/child/modular_child.py:
from ..base.modeling_base import BaseModel
class ChildModel(BaseModel):
def __init__(self, config):
super().__init__(config)
self.y = 2
__all__ = ["ChildModel"]Convert:
pycodelift convert mypkg/child/modular_child.py --package mypkgOr:
from pycodelift import Flattener
files = Flattener(package_name="mypkg").convert("mypkg/child/modular_child.py")
print(files["modeling"])The output modeling_child.py has BaseModel's body inlined and no
inheritance edge.
Eight CST passes: parse → walk → resolve parent classes →
inline-flatten one level → case-preserving rename → pull in
transitive helpers → split outputs by class-suffix → render. Each pass
with code examples in docs/how-it-works.md.
Flattener(...) keyword args (all optional):
| Argument | Purpose | Default |
|---|---|---|
rename |
irregular casing overrides | snake↔Pascal derivation |
file_types |
suffix → output stem (Config → configuration_<x>.py) |
everything → modeling_<x>.py |
pre_body_vars |
top-level vars that come first | ("logger",) |
skip_imports |
imports to drop | () |
post_class_hooks |
e.g. TrailingCallHook("post_init") for trailing-call rules |
() |
header_template |
banner with {relative_path} / {short_name} slots |
default banner |
excluded_external_files |
per-package files to skip when walking imports | None |
prepend_namespace_parent |
transformers' optimum.habana-style prefix |
False |
flatten_levels=2 and non-empty special_bases raise
NotImplementedError in v0.1.
from pycodelift.adapters.transformers import make_transformers_flattener
flattener = make_transformers_flattener()
files = flattener.convert("src/transformers/models/olmo2/modular_olmo2.py")The adapter pins every transformers convention so output matches
upstream utils/modular_model_converter.py. CLI: --transformers.
Combine with --package optimum for transformers forks.
pycodelift convert <file>... # one or many
pycodelift convert-tree <root> # recursive
pycodelift describe # dump effective config as JSON
Flags: --package, --transformers, --write, --target-dir.
- One-level flatten only.
Foo2(Foo)works;Foo's own parent is not inlined. - Not thread-safe (v0.1 mutates module-level vendor state). Use a process pool for parallelism.
del self.xis line-level — removes the assignment, not later references.
Three runnable mini-projects under examples/:
01_minimal, 02_split_outputs, 03_transformers_compat.
Derived from huggingface/transformers's
utils/modular_model_converter.py
(Apache-2.0). The CST passes are theirs (battle-tested across 200+
models); pycodelift extracts the policy slots they hard-coded.
See NOTICE.
Apache-2.0.