Core package layout (implementation map)
This page summarizes how the planframe package is organized internally. It is aimed at contributors and adapter authors who need to navigate the codebase; the public API remains from planframe import Frame, execute_plan, etc.
Frame lives under planframe/frame/
The user-facing type Frame is a single class composed from mixins (single responsibility per module, easier navigation):
| Module | Role |
|---|---|
packages/planframe/planframe/frame/_class.py |
Declares class Frame(...) and __slots__. |
packages/planframe/planframe/frame/_mixin_core.py |
Construction (source), schema / plan, optimize, expression compilation helpers, and delegation to execute_plan. |
packages/planframe/planframe/frame/_mixin_ops.py |
Lazy transforms from selection through pivot (the bulk of the fluent API). |
packages/planframe/planframe/frame/_mixin_io.py |
Materialization (collect, async helpers), row/column export, and write_* sinks. |
packages/planframe/planframe/frame/_utils.py |
Small helpers shared by mixins (e.g. sort flag coercion). |
Imports stay stable: from planframe.frame import Frame re-exports from packages/planframe/planframe/frame/__init__.py.
Shared compilation: PlanCompileContext
planframe.compile_context.PlanCompileContext (packages/planframe/planframe/compile_context.py) holds (adapter, schema) and centralizes:
- compiling expression IR via
adapter.compile_expr - lowering join keys, named aggregations, sort keys, and
Projectitems into adapter-facing structures
Both Frame (when building or validating plans) and execute_plan use the same type so compilation behavior does not drift between “plan building” and “plan execution”.
Plan execution: execute_plan and node dispatch
planframe.execution.execute_plan (packages/planframe/planframe/execution.py) is the supported interpreter for PlanNode trees (packages/planframe/planframe/plan/nodes.py). It:
- Builds a
PlanCompileContextfor the given adapter and schema. - Walks the plan using a registry of handlers keyed by concrete node type (instead of one giant
if/elifchain), so new node kinds can be added in a localized way.
Frame materialization calls into this interpreter; adapters still implement the per-operation primitives (select, filter, join, …).
execute_plan_async: does not run a separate async plan interpreter. It schedules the same synchronous execute_plan implementation on a worker thread via asyncio.to_thread, so callers can await plan evaluation without blocking the event loop. Async engine I/O remains on BaseAdapter async materialization methods (acollect, ato_dict, …). See Migrating since v1.1.0 (async public API) and Creating an adapter — Async execution contract.
Typing stubs
Generated Pyright stubs for Frame are emitted to planframe/frame/__init__.pyi. Regenerate with:
python scripts/generate_typing_stubs.py
See also: Typing design, Creating an adapter.