Skip to content

Packaging Guide — Module Discovery in Frozen Environments

Cullinan v0.93a11.post2+ introduces a unified strategy pipeline for module discovery across all packaging tools (Nuitka, PyInstaller) and development environments.

Quick Start

The most reliable approach for --onefile builds:

from cullinan import configure

configure(
    explicit_modules=["myapp.services", "myapp.controllers"],
    user_packages=["myapp"],
)

explicit_modules replaces the deprecated nuitka_modules (removed in v0.93a11.post2). It works with all packaging tools — Nuitka, PyInstaller, and any future freezer.

For --standalone / --onedir builds where filesystem access is available:

from cullinan import configure

configure(
    user_packages=["myapp"],
    auto_scan=True,  # default
)

Strategy Pipeline

Cullinan now uses a composable pipeline of six independent strategies. Each pipeline is selected automatically based on get_packaging_mode():

Nuitka

Mode Pipeline Notes
--standalone S0 → S1 → S2 → S3 → S4 Full filesystem scan
--onefile S0 → S1 → S2 → S3 → S5 Skip S4 (no source files), use recursive dir(pkg) + sys.modules fallback

PyInstaller

Mode Pipeline Notes
--onedir S0 → S1 → S4 _MEIPASS + executable dir scan
--onefile S0 → S1 → S4 _MEIPASS temp extraction dir

Development

Mode Pipeline Notes
development S0 → S1 → S2 → S3 get_caller_package() + fallback

Strategy Reference

Strategy Description Runs When
S0: explicit_modules User-provided explicit module list explicit_modules is set
S1: user_packages Configured packages via pkgutil.walk_packages user_packages is set
S2: sys_modules_scan Auto-scan sys.modules with path heuristics auto_scan=True
S3: main_module_inference Infer package from __main__.__file__ Always
S4: directory_scanning Walk filesystem for .py/.pyc/.pyd/.so Standalone/onedir modes
S5: onefile_dir_fallback Recursive dir(pkg) + sys.modules prefix scan One-file modes (last resort)

Performance Improvements (v0.93a11.post2+)

  1. Global type hints cachetyping.get_type_hints() results are cached at module level (shared across ApplicationContext instances). Previously each context instance had its own cache, causing duplicate resolution.

  2. Lazy source lineinspect.getsourcelines() is no longer called during decorator registration. Source file paths are still captured; line numbers are deferred to on-demand diagnostics.

Migration from v0.93a11

Old API New API
nuitka_modules=["myapp"] explicit_modules=["myapp"]
scan_modules_nuitka() Pipeline auto-selected
scan_modules_pyinstaller() Pipeline auto-selected

The old functions have been removed. No backward-compatibility aliases are provided — update your configuration at the same time you upgrade the framework.

Troubleshooting

No modules discovered in one-file mode

configure(explicit_modules=["your_top_level_package"])

Type hints resolution error after class reload (tests)

PendingRegistry.reset() now also invalidates the global type hints cache:

from cullinan.core.pending import PendingRegistry
PendingRegistry.reset()  # also clears type hints cache

Strategy override (advanced)

from cullinan.runtime.scan_pipelines import execute_pipeline
from cullinan.support.config import get_config

modules = execute_pipeline(
    get_config(),
    packaging_mode="nuitka-onefile",
    base_dirs=["/custom/path"],
    early_termination=False,  # run ALL strategies
)