Repository navigation
Find a class's lazy counterpart through the base/lazy registry - #42
Merged
Merged
Conversation
The lazy class of a config class was found by building its name from a prefix and reading that name off the module. The registry that already maps lazy to base now answers both directions, and nothing looks a lazy class up by name. A class's group abbreviation is declared once, on the class, and read on demand; a lazy class answers with its base's declaration. The @abbreviation decorator and the abbreviation= parameter write the same declaration, so decorator order no longer matters and two different declarations for one class raise. GROUP_ABBREVIATIONS_REGISTRY and LAZY_CONFIG_PREFIX are removed. The factory alone names a lazy class. Fixes #35 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #35
What changed
abbreviation.__call__no longer buildsf"{LAZY_CONFIG_PREFIX}{cls.__name__}"and reads it offsys.modules[cls.__module__]. Nothing in the library looks a lazy class up by name any more.register_lazy_type_mapping(the existing owner; it already wrote both directions and already raised on a second counterpart in either direction). It is called once, when_create_lazy_dataclass_unifiedcreates the lazy class.get_base_type_for_lazyandget_lazy_type_for_baseare the two reads.declare_group_abbreviation), and read on demand byget_group_abbreviation, which maps a lazy class to its base through the registry and then walks the base's MRO for the nearest own declaration.@abbreviation(...)andabbreviation=on the global-default decorator write that same declaration, so the order of the two decorators does not matter.LAZY_CLASS_NAME_PREFIX, which existed but was unused). The three other spellings (f"Lazy{...}"for nested fields, themake_lazy_simpledefault,LAZY_CONFIG_PREFIXin the decorator) are gone, and the name is no longer carried in_pending_injections.make_lazy_simple(base)with no name returns the lazy class the base already owns, whatever it is called.Inventory before creation: searched for
_lazy_type_registry,_base_to_lazy_registry,register_lazy_type_mapping,get_lazy_type_for_base,LAZY_CONFIG_PREFIX,LAZY_CLASS_NAME_PREFIX,sys.modules,GROUP_ABBREVIATIONS_REGISTRY,_abbreviation. No new registry was added; one was removed.Acceptance criteria
getattr(sys.modules[...], ...)lookup of a lazy class remainstest_no_lazy_class_is_looked_up_by_constructed_namepins it. The four remainingsys.modules[...]uses inlazy_factory.pyare writes that export a generated class or decorator to its module.test_class_inside_function_gets_abbreviation_on_base_and_lazy, parametrised over both orders.Further cases from the task: two same-named classes in one module, a renamed prefix (both in both decorator orders), the explicit
abbreviation=parameter, a subclass with and without its own declaration (including through theinherit_as_nonerebuild), two different abbreviations for one class raising, and the registry refusing a second counterpart in either direction.Observed on
origin/main(1fcdf12) with the new test file: 5 fail (same-named classes with@abbreviationinnermost, renamed prefix in both orders, conflicting declarations, the source check). The function-local case passes onmainwhen the class's module is importable, because the lazy class is still exported to that module; it failed only once a second class reused the name.Paper results
Behaviour changes for callers
objectstate.lazy_factory.GROUP_ABBREVIATIONS_REGISTRYis removed. Read withget_group_abbreviation(config_type); declare with@abbreviation/abbreviation=.formbridge/strategies/preview_formatting.py:26-34imports it and walks it by hand; it must callobjectstate.lazy_factory.get_group_abbreviationat release time.pyqt-reactive/tests/test_preview_formatters.py:10,229,230,279monkeypatches it; those tests must declare with@abbreviationinstead.objectstate.lazy_factory.LAZY_CONFIG_PREFIXis removed. No use found in OpenHCS, pyqt-reactive, pycodify or formbridge.@abbreviation("x")together withabbreviation="y"on the same class now raisesValueError. Before, which one won depended on decorator order. OpenHCS usesabbreviation=atopenhcs/core/config.py:596and:987; neither class also carries@abbreviationas far as the grep shows.@abbreviationapplied to a lazy class declares on its base class.make_lazy_simple(base)without a name no longer raises when the base already owns a lazy class with a non-default name; it returns that class. An explicit different name still raises.Sites outside the library that build the lazy name
grep -rn 'LAZY_CONFIG_PREFIX\|f"Lazy{'overopenhcs/,external/pyqt-reactive/src,external/pycodify/src: no hits. No caller builds the lazy name itself.pycodify-generated
.pypipelinesNot affected. pycodify spells a class by its
__module__and__qualname__(pycodify/core.py:61,formatters.py) and has no knowledge of theLazyprefix. Lazy classes keep the same name, the same__module__, and are still exported to that module under that name, so existing generated source imports the same objects.Found outside this issue (not changed)
lazy_factory.py,_inject_all_pending_fields: the global config class is recreated withmake_dataclassfrom an almost empty namespace, so an@abbreviationon the global class itself (OpenHCScore/config.py:210,"gpc") does not reach the recreated class. Onmainthe entry was likewise keyed by the discarded class.PREVIEW_LABEL_REGISTRY,FIELD_ABBREVIATIONS_REGISTRYandALWAYS_VIEWABLE_FIELDS_REGISTRYstill copy each base entry to the lazy class at decoration time, the same restated relation this PR removes for group abbreviations.Tests
pytest -n 2: 251 passed.ruff check src/ --select E9,F63,F7,F82: clean.scripts/check_ruff_added_lines.py --base origin/main: no diagnostics on added lines. OpenHCS and formbridge were not run against this branch.🤖 Generated with Claude Code