Loading
Split tango.utils into a package
TL;DR
tango/utils.py(2216 lines, 130 top-level symbols) becomes atango/utils/package of twelve private submodules grouped by concern, with__init__.pyre-exporting the public surface.- No definition is edited.
__all__is unchanged, every documented function keeps its import path, and no caller in the package or the tests needed a change.
Follow-up to #789. Draft: opened for agreement on the grouping before review of the detail.
Key Facts
- The concerns are not entangled, which is what makes this worth doing. Treating each group as a module leaves three dependency edges —
_typeinfo -> _types,_convert -> _types,_db -> _types, _convert— and no cycles. Nine of the twelve groups depend on nothing else in the file. - The move is verbatim and provable. All 128 definitions compare equal by AST dump against
tango/utils.pyat the merge base. Each one is moved with the comments that preceded it. - The import cycle is unchanged.
utilspulls 26 names out of the partially-initialisedtangopackage at module level. Submodules execute whiletango/utils/__init__.pyis being imported, which is the same momenttango/__init__.pyimported.utilsbefore, so they see the same partially-initialised package. wheel.packagesneededtango.utilsadded. Subpackages are listed explicitly there —tango.databasedsalready is — so without it the new package would not ship. This is the one change that a reader could easily miss and that would only fail at install time.LICENSE.txtattributed PSF-licensed code totango/utils.py. The path is updated totango/utils/_concurrency.py, where_adjust_thread_countnow lives, and the attribution comment travels with the code.tango/utils/__init__.pycarries a file-level# ruff: noqa: F401. A re-export facade is all unused imports by construction; the alternative was a shared per-file-ignore inpyproject.toml, which would have relaxed the rule for every__init__.pyin the tree.
Behaviour change
One, and it is deliberate. Names that tango/utils.py merely imported were incidentally reachable as tango.utils.<name> — CmdArgType, np, sys, StdStringVector, Release and 53 others. They are not re-exported. Nothing in the package or the test suite imported any of them from tango.utils, and the tango names are canonically available from tango itself.
Two carve-outs:
__auto_die_callbacks_holderis re-exported, becausetests/test_device_proxy.pyimports it fromtango.utils. It is a container mutated in place, so re-exporting the object is correct.__device_classesand__TEST_CONTEXT_HOST_TRLare not re-exported. Functions rebind them at runtime, and a facade binds values at import time, so a copy here would silently go stale. They stay in the submodule that owns them, where theglobalstatements still work.
Objects now report a __module__ of tango.utils._<group> rather than tango.utils. The submodules are importable, so pickling and autofunction by explicit path are unaffected.
How this was checked
- Surface comparison against a snapshot of
dir(tango.utils)and__all__taken before the split: every defined name still resolves,__all__is identical and all 44 names resolve, every object has the same kind, every callable the same signature. The only additions are__path__and the twelve submodules. - AST equality of all 128 definitions, old versus new.
- Full suite: 1514 passed, 98 skipped, 53 xfailed, 1 xpassed, 0 failures — the same numbers as the merge base. The
xpassedistest_dev_encoded_memory_usage, which the project marks "too fragile"; it is the only@pytest.mark.xfailin the suite and flips independently of this change.
Caveats
- Git records this as one delete and thirteen adds, not a rename: at 450 of 2216 lines the largest new file is below any sane similarity threshold.
git log --follow -- tango/utils.pyreaches the pre-split history andgit blame -C -Cattributes the moved lines, since the content is unchanged. Flagged because it is the usual objection to a split of this shape. - The
__all__inconsistencies reported in #789 are untouched here on purpose:get_enum_labelsandis_boolare documented but missing from__all__, and five names that their own docstrings call "internal use only" are listed in it. Resolving those changes the public surface, so it does not belong in a move that changes nothing. - Moving the genuinely-internal helpers out of
tango.utilsinto something liketango/_internal/is the natural next step, and needs the__all__question settled first.