Internal boundaries: dependency inventory¶
This is an architectural assessment of mpylab at commit 9d09c9a
(2026-10-18), before any boundary refactoring. It proposes internal ownership
within the existing repository. It does not introduce packages, move modules,
change import paths, or define new supported APIs.
Scope and method¶
All 229 Python source files under src/mpylab were inspected through an AST
import inventory. Imports inside functions and relative imports were included;
14 files in device/legacy, tools/legacy and the forwarding module
device/driver.py were distinguished from the other 215 files. Generated
files, including _version.py and limits_ui.py, remain in the inventory.
Module counts describe locations, not effort or architectural quality.
An edge means that one module explicitly imports another, including function imports. Repeated imports between the same pair count once in the figures below. Attribute imports were resolved to a source module when possible. Imports do not prove that a branch executes, or that an imported name is a supported public API. Package initialization, re-exports, configuration-driven loading and pickle restoration require additional inspection. In particular, the absence of a static edge does not imply runtime independence.
Tracked Python sources in TEMField, Conducted Emission, PExplorer, Shielding Effectiveness, both active configuration repositories and MpyConfig were also searched. Application/configuration sources, tests and archived/migrated configurations were distinguished. No configuration script or driver was executed. No measurement procedure or formula was assessed.
Current locations and responsibilities¶
Location |
Files |
Present responsibility |
|---|---|---|
|
53 |
Quantities, configuration, graph, numerics, compatibility exports and CLI tools |
|
89 |
Contracts, transports, drivers, virtual devices, UI helpers and windows |
|
19 |
Measurement lifecycle, EUT monitoring, UI contracts, Qt adapter and runners |
|
18 |
TEM workflows, evaluation, history, reports and interactive review |
|
5 |
MSC workflows, protected acquisition and threshold search |
|
3 |
Amplifier-test workflow and name maps |
|
22 |
Limit models, profiles, registry and Qt explorer |
|
4 |
Qt-free identity/availability, branding, macOS helper and launcher |
Package root / legacy |
2 / 14 |
Version/package initialization / historical compatibility |
Main findings¶
TEM and MSC already have a useful separation. No explicit imports from either domain into the other, or into
env/univers, were found. Both use the shared measurement layer and tools. There are five distinct module-pair edges from TEM to sharedenvmodules and three from MSC. There are 23 from TEM to tools, 13 from MSC and six from the amplifier workflow. No non-domain source module explicitly imports these domain modules. External applications and configuration loading still use them.Directory names do not describe Qt ownership. Twenty-six non-legacy source modules directly import PySide6, Shiboken or a Matplotlib Qt backend. They span
device,env,env/tem,guiandlimits. Six of these are concrete drivers with function-local GUI launchers, not drivers requiring Qt for normal imports. Conversely,gui/__init__.pycontains Qt-free metadata and availability checks.device/ui_quantity_display.py,ui_driver_lifecycle.pyandreceiver_scan_worker.pyare also Qt-free; theui_prefix alone is not a reason to move a module.TEM has an explicit UI dependency during interactive review.
TEMCell.pylocally importsemission_sweep_ui._require_displayandreview_sweep. The latter module imports Qt only inside functions, so a normal TEM import remains Qt-free. A stricter domain boundary would pass review/display services through an adapter, while keeping acquisition, review decisions, autosave and resume state owned by the workflow.The limit calculation layer is largely separate from its explorer.
limit.pyuses NumPy and the registry dynamically loads named profiles. Three FCC profile modules use canonical conversions fromtools.uconv; four profiles have local example imports fromtools.spacing. The Qt explorer additionally importsdevice.ui_frequencyand shared branding. TEM prescreening importslimits.registry.resolve_limit. This is a sensible domain-to-calculation dependency, not a dependency on the explorer.The explorer has a small explicit cycle.
gui.launchers.limit_explorer_mainlocally importslimits.limits_ui_main.main. Thatmainimports the privategui.launchers._limit_parser. This is the sole multi-module strongly connected component in the explicitly resolved non-legacy import graph. Both edges are deferred: it is not evidence of an import-time crash. A neutral parser module or an argument-parsing boundary can remove it.Tools mix foundation and orchestration.
tools.config_inspectimportsenv.config_viewandenv.script_cli;tools.nport_checkimportsdevice.nport;tools.visa_probeimports privatecommunication_gpib._resource_manager. These are application/validation tools rather than evidence that all numerical helpers depend on devices. A blanket rule forbidding everytools -> env/deviceedge would be misleading. Separate responsibilities logically before enforcing an import policy.Compatibility exports are heavily used. Fifty-nine distinct non-legacy modules import
tools.util; 40 importtools.uconvand 31 importtools.configuration.utilre-exports existing implementations from focused modules such aslogging_utils,textandsequences. Prefer those implementation modules for new internal imports, while retaining the existing facade for consumers.uconvis already the canonical conversion backend and should not be duplicated in domains or limits.
Observed consumer contracts¶
These are used import paths, not a proposal to make every imported name public. All paths below are relative to the corresponding repository.
Consumer |
Evidence |
Boundary implication |
|---|---|---|
TEMField |
|
A conversion should come from canonical |
Shielding Effectiveness |
|
Shared report metadata currently lives under TEM; a neutral home needs compatible defaults and old-path exports. |
Conducted Emission |
|
Preserve calculation APIs independently of the explorer. Audit re-exports such as units from |
PExplorer |
|
Pickle viewing depends on restoring concrete classes, even when there is no direct TEM/MSC import. |
GTEM configuration |
|
The legacy driver facade is still an active external contract. |
RC configuration |
|
Preserve condition-map semantics, custom-driver loading and virtual driver contracts. |
MpyConfig 0be1460 and the migrated trees contain historical callers.
They were examined separately and are compatibility evidence, not additional
active applications to modernize in this step. TEMField also implements its
own measurement workflows using Measure; it is not simply a TEMCell GUI.
Dependencies beyond ordinary imports¶
tools.mgraph.MGraph.CreateDevicesbuildsmpylab.device.<driver>names from INI configuration and also loads custom driver files. This is a real graph-to-device dependency absent from the static module-pair counts.env.script_cliandenv.qt_runnerload user configuration modules. Device windows select driver modules dynamically. Limit discovery/registry also uses dynamic imports. These extension mechanisms need explicit ownership.env.Measure.MpyLabCompatUnpicklermaps historicalmpypaths, then resolves stored classes. Moving a class or removing an old module can break saved measurements and PExplorer. Preserve old import paths and test real restoration before moving persisted types. No measurement files were opened for this analysis.device.pm_rxattcontains a demonstration that loads an external switch script by absolute path. This is demonstration/configuration coupling, not an automatic dependency of every power-meter import. Do not execute it as an import-graph probe.
Proposed internal ownership¶
Keep one distribution and the current public import paths. Define logical ownership first; existing directory-wide rules would be too coarse.
![digraph proposed_boundaries {
rankdir=LR;
node [shape=box];
"Qt adapters / applications" -> "TEM / MSC / amplifier workflows";
"Qt adapters / applications" -> "Limits calculation";
"TEM / MSC / amplifier workflows" -> "Measurement lifecycle / EUT / UI contracts";
"TEM / MSC / amplifier workflows" -> "Graph / configuration";
"TEM / MSC / amplifier workflows" -> "Limits calculation" [label="where needed"];
"Graph / configuration" -> "Device contracts / drivers / transports";
"Measurement lifecycle / EUT / UI contracts" -> "Graph / configuration";
"Device contracts / drivers / transports" -> "Quantities / conversion / numerics";
"Limits calculation" -> "Quantities / conversion / numerics";
}](../../_images/graphviz-cbf9e245d00d738113eb93cc7c36da7867a701e2.png)
The diagram is a target dependency direction, not a complete current call graph.
Qt-free application identity and shared report metadata are supporting services.
Measurement UI contracts remain Qt-free. Amplifier protection, RF-off and input
limits stay shared safety responsibilities; an amplifier-test workflow does not
own all amplifier safety. TEM-specific calculations in tools may retain
their paths until a separately reviewed ownership decision.
Recommended next increments¶
Remove the launcher/parser cycle without changing CLI help, version handling or optional-Qt behavior. This is the smallest independently reviewable change.
Establish a neutral report-metadata service and canonical imports for shared helpers. Keep compatibility exports and test existing report defaults and application consumers; do not alter report formats while relocating ownership.
Put TEM interactive review behind a Qt-free adapter contract. Retain the existing interactive decisions, interruption and persistence semantics.
Classify mixed driver launchers, CLI tools and facades explicitly, then add import-boundary checks for the agreed logical areas. Static checks must include local imports and be complemented by Qt-free runtime checks and configuration/pickle compatibility tests. Add these checks in a later step.
There is no justification here for splitting repositories or publishing new packages. The concrete follow-up is the launcher/parser boundary; larger moves should follow only after reviewing these findings.
First increment: explorer argument boundary¶
The launcher/parser cycle identified in the baseline above has now been removed.
gui.limit_explorer_cli owns the program name and argument parsing and imports
neither the launcher, the explorer nor Qt. The console launcher validates
arguments once, checks Qt availability, then calls the internal window runner.
The existing limits.limits_ui_main.main(argv=None) validates arguments through
the same CLI module and calls that runner. There is no import back to the launcher.
The console command, direct module entry point, help/version text and Qt exit
code are retained. Console help/version and invalid-argument handling still work
without Qt; an actual GUI request explains the mpylab[gui] extra when absent.
This does not extend Qt-free behavior to the old direct GUI module import.
Window creation, branding and event-loop ownership are unchanged. No domain,
driver, limit calculation or persisted type was moved.
Third increment: interchangeable sweep review¶
TEMCell and the emission script now use the Qt-free sweep-review adapter contract. Adapters return decisions and evaluation proposals from detached data; the workflow validates and commits metadata/selections. Existing Qt dialogs remain behind an adapter. The terminal adapter saves the same curves as PNGs and explicitly asks for repeat/continue/finish/save-exit and evaluation sets. Auto mode falls back to terminal when Qt/display is absent; unavailable input fails before device creation. EOF/Ctrl-C retains pending data for resume. Adapters remain invocation-local and are not added to measurement pickles. The plot helper is Qt-free; historical private plotting imports remain aliases. See TEM emission data for configuration and operator steps.
Enforced direct-import rules¶
The quality job now runs the stdlib-only checker:
python tools/check_import_boundaries.py
Rules are defined explicitly in tools/check_import_boundaries.py. They cover
49 modules at introduction; the count changes as modules are added. No runtime
module is imported or hardware accessed by the checker.
Rule |
Selected sources |
Forbidden direct dependencies |
|---|---|---|
|
|
|
|
|
TEM/MSC/amplifier domains, devices, GUI and concrete review adapters |
|
|
Qt bindings/backends and concrete Qt frontends, including |
|
|
Qt bindings/backends, |
|
|
Launcher, limits/explorer, devices, |
|
Respective |
Either of the other two domain trees |
Qt targets include PySide2/6, PyQt5/6, Shiboken2/6 and the explicitly listed
Matplotlib Qt adapters. Exact source-module selectors do not implicitly include
children; source-package selectors include future child modules. Forbidden
prefixes match complete name components, so temporal does not match tem.
Imports inside functions, classes, conditional/try blocks and TYPE_CHECKING are
checked. Relative imports and from parent import child are resolved
syntactically, without executing Python or looking up installed packages.
Violations report file, line, target, rule ID and reason. Exit status is 0 for
success, 1 for a boundary violation and 2 for unreadable/invalid selected source
or a missing source root. A rule matching no source module also fails with 2,
preventing an empty scan from passing silently. --source-root accepts a
package directory named mpylab for isolated checks.
This is not a full dependency or cycle analysis. It does not follow re-exports,
parent-package side effects, aliases assigned at runtime, dynamic import calls,
or runtime Matplotlib backend selection. Wildcards only expose their named
base, not unknown re-exported children. MGraph/custom-driver/configuration/limit
loading and pickle globals remain runtime contracts. Compatibility facades and
historical module-path strings remain valid; there is no blanket ban on imports
from tools to devices or measurement code and no legacy exception baseline.
Rule changes need an explicit scope/reason and corresponding tests/documentation;
do not add a suppression simply to hide a new dependency. The installed-core
job without Qt and the existing configuration/pickle tests remain required.