Skip to content

Fix extension loading across CLI, Python API, and multiprocessing - #1119

Open
jpn-- wants to merge 4 commits into
ActivitySim:mainfrom
driftlesslabs:fix-extension-imports
Open

jpn-- wants to merge 4 commits into
ActivitySim:mainfrom
driftlesslabs:fix-extension-imports

Conversation

@jpn--

@jpn-- jpn-- commented Sep 23, 2026

Copy link
Copy Markdown
Member

Problem and fix

activitysim run --ext model/extensions imports the package in the parent but fails in multiprocessing because the worker passes the filesystem path to importlib.import_module. State.import_extensions() records the same path and has the same worker failure. Absolute paths do not help.

Use a shared loader and save normalized absolute import locations when extensions are registered. Workers use the same API to import the module basename from its saved parent directory. This supports relative and absolute paths, ./, trailing separators, spaces, path-like API arguments, and dotted module names while restoring sys.path after successful or failed imports.

The CLI now delegates to State.import_extensions(), resolves a relative -w before changing directory, and discovers extension settings checkers by the imported module's name. The Python API runner carries the state's working directory into workers and tolerates absent optional CLI injectables, allowing state.run.all() to work when the process CWD differs from the model directory. imported_extensions remains a list of importable module names for existing consumers such as SANDAG settings-checker discovery. Absolute locations are stored separately in private _extension_locations worker metadata. The documentation explains this and Python's module-name caching behavior.

Closes #1118.

Validation

Tests are integrated into the existing Core Testing workflow: the Linux foundation and macOS/Windows jobs discover the regressions through pytest --pyargs activitysim.core and pytest --pyargs activitysim.cli. The unchanged external SANDAG job validates compatibility with the example repository.

  • Coverage includes real single-process and multiprocessing CLI/API runs; relative, absolute, bare, dotted and trailing-separator inputs; explicit working directories; relative package imports; settings-checker discovery; and import-error cleanup.
  • API tests consume imported_extensions exactly as downstream settings checkers do, passing each module name to importlib.import_module. Private worker locations do not alter that public registry.
  • Worker tests change CWD, place a conflicting module there, and exercise every available start method. They cover both registered location metadata and legacy callers supplying only module names. API end-to-end tests explicitly use spawn.
  • All 32 settings-checker tests from the unchanged SANDAG example passed locally against the compatibility fix.

The original CLI multiprocessing regression was verified to fail on unfixed upstream.

Final verification on 38ba3c307: the full upstream Core Testing workflow passed, with all 16 test jobs successful (the branch-only documentation job was intentionally skipped). This includes Linux, macOS, Windows, every regional model, and the unchanged external SANDAG example. The SANDAG job passed all 34 tests, including the settings-checker tests that failed before the compatibility correction. Locally, all 39 extension regression cases and all 32 unchanged SANDAG settings-checker tests also passed. No changes were made to the SANDAG example repository.

@jpn--
jpn-- requested review from i-am-sijia and a balanced review from Copilot September 23, 2026 21:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The shared loading path addresses the reported multiprocessing failures with comprehensive regression coverage.

Review effort: Balanced
Findings: None

What changed in this PR

Adds consistent extension loading across CLI, Python API, and multiprocessing workers.

Changes:

  • Normalizes extension locations and uses a shared loader.
  • Propagates working-directory context to workers.
  • Adds cross-platform regression tests and documentation.
File Description
docs/​dev-guide/​core-workflow-api.md Documents extension loading semantics.
activitysim/​core/​extensions.py Implements shared resolution and import helpers.
activitysim/​core/​workflow/​state.py Registers normalized extension locations.
activitysim/​core/​workflow/​runner.py Safely forwards available worker injectables.
activitysim/​core/​mp_tasks.py Reloads extensions through the shared state API.
activitysim/​cli/​run.py Delegates extension loading and preserves working directories.
activitysim/​core/​test/​test_extensions.py Tests loader and worker behavior.
activitysim/​cli/​test/​test_extensions.py Tests CLI and API execution end to end.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiprocessing treats --ext filesystem paths as module names, breaking nested and absolute extension paths

2 participants