Skip to content

Commit 959ff18

Browse files
bokelleyclaude
andcommitted
fix(types): make the domain package roots lazy so a types package imports first in any order
Backport of the SDK 10 fix for #1784 to the 9.x line, where the cycle reproduces identically: ``from adcp.types.domains.formats import Slot`` raised ImportError when it was the first adcp import in a process, because the domain roots imported every schema module eagerly and ``formats/canonical/_base.py`` re-entered ``formats/canonical`` through ``core/__init__`` while half built. Each domain root is now a PEP 562 module: ``__all__`` and a TYPE_CHECKING import block keep the static surface, ``_DECLARED_IN`` names each name's schema module, and ``__getattr__`` imports the declaring module on first read and caches the binding. Nothing is renamed and every name still resolves. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 1fcc331 commit 959ff18

24 files changed

Lines changed: 7479 additions & 3644 deletions

File tree

‎scripts/consolidate_exports.py‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,21 +956,59 @@ def generate_domain_exports(
956956
]
957957
lines.extend(
958958
[
959+
"",
960+
"This root is lazy (PEP 562): importing it, or a schema module under it,",
961+
"does not import every schema in the domain. A name is bound from its",
962+
"declaring module on first access and cached here. That is what keeps a",
963+
"schema module's ``from ...<domain> import <sibling>`` from re-entering a",
964+
"half-initialized module through this package (#1784).",
959965
"",
960966
"Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY.",
961967
f"Generation date: {generation_date}",
962968
'"""',
963969
"# ruff: noqa: E501, I001",
964970
"from __future__ import annotations",
965971
"",
972+
"from typing import TYPE_CHECKING",
973+
"",
974+
"if TYPE_CHECKING:",
966975
]
967976
)
968977
for module_name in sorted(by_module):
969978
lines.append(
970-
f"from adcp.types.domains.{module_name} import "
979+
f" from adcp.types.domains.{module_name} import "
971980
f"{', '.join(sorted(by_module[module_name]))}"
972981
)
973982
lines.extend(_format_all_block(sorted(rows)))
983+
lines.extend(
984+
[
985+
"",
986+
"#: The schema module that declares each name, for the lazy binding below.",
987+
"_DECLARED_IN = {",
988+
]
989+
)
990+
for type_name, module_name in sorted(rows.items()):
991+
lines.append(f' "{type_name}": "adcp.types.domains.{module_name}",')
992+
lines.extend(
993+
[
994+
"}",
995+
"",
996+
"if not TYPE_CHECKING:",
997+
" import importlib as _importlib",
998+
"",
999+
" def __getattr__(name: str) -> object:",
1000+
" module_name = _DECLARED_IN.get(name)",
1001+
" if module_name is None:",
1002+
" # Not a declared name: let the import system try a submodule, or fail.",
1003+
' raise AttributeError(f"module {__name__!r} has no attribute {name!r}")',
1004+
" value = getattr(_importlib.import_module(module_name), name)",
1005+
" globals()[name] = value",
1006+
" return value",
1007+
"",
1008+
" def __dir__() -> list[str]:",
1009+
" return sorted(set(globals()) | set(__all__))",
1010+
]
1011+
)
9741012
modules[domain] = "\n".join(lines)
9751013

9761014
root = [

‎src/adcp/types/domains/a2ui/__init__.py‎

Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,57 @@
99
it from its own schema's module, ``adcp.types.domains.a2ui.<schema>``.
1010
Nothing here is renamed.
1111
12+
This root is lazy (PEP 562): importing it, or a schema module under it,
13+
does not import every schema in the domain. A name is bound from its
14+
declaring module on first access and cached here. That is what keeps a
15+
schema module's ``from ...<domain> import <sibling>`` from re-entering a
16+
half-initialized module through this package (#1784).
17+
1218
Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY.
13-
Generation date: 2026-10-04 18:45:11 UTC
19+
Generation date: 2026-11-01 23:49:37 UTC
1420
"""
1521

1622
# ruff: noqa: E501, I001
1723
from __future__ import annotations
1824

19-
from adcp.types.domains.a2ui.bound_value import (
20-
A2UiBoundValue,
21-
A2UiBoundValue1,
22-
A2UiBoundValue2,
23-
A2UiBoundValue3,
24-
A2UiBoundValue4,
25-
A2UiBoundValue5,
26-
)
27-
from adcp.types.domains.a2ui.component import A2UiComponent
28-
from adcp.types.domains.a2ui.si_catalog import (
29-
Action23,
30-
Align,
31-
AppHandoff,
32-
Apps,
33-
Button,
34-
Card,
35-
Column,
36-
Component,
37-
Image,
38-
IntegrationAction,
39-
Justify,
40-
Layout,
41-
Link,
42-
List,
43-
ProductCard,
44-
Row,
45-
SiComponentCatalog,
46-
Template,
47-
Text,
48-
Type,
49-
Variant,
50-
Variant4,
51-
)
52-
from adcp.types.domains.a2ui.surface import A2UiSurface
53-
from adcp.types.domains.a2ui.user_action import A2UiUserAction
25+
from typing import TYPE_CHECKING
26+
27+
if TYPE_CHECKING:
28+
from adcp.types.domains.a2ui.bound_value import (
29+
A2UiBoundValue,
30+
A2UiBoundValue1,
31+
A2UiBoundValue2,
32+
A2UiBoundValue3,
33+
A2UiBoundValue4,
34+
A2UiBoundValue5,
35+
)
36+
from adcp.types.domains.a2ui.component import A2UiComponent
37+
from adcp.types.domains.a2ui.si_catalog import (
38+
Action23,
39+
Align,
40+
AppHandoff,
41+
Apps,
42+
Button,
43+
Card,
44+
Column,
45+
Component,
46+
Image,
47+
IntegrationAction,
48+
Justify,
49+
Layout,
50+
Link,
51+
List,
52+
ProductCard,
53+
Row,
54+
SiComponentCatalog,
55+
Template,
56+
Text,
57+
Type,
58+
Variant,
59+
Variant4,
60+
)
61+
from adcp.types.domains.a2ui.surface import A2UiSurface
62+
from adcp.types.domains.a2ui.user_action import A2UiUserAction
5463

5564
# Explicit exports
5665
__all__ = [
@@ -86,3 +95,54 @@
8695
"Variant",
8796
"Variant4",
8897
]
98+
99+
100+
#: The schema module that declares each name, for the lazy binding below.
101+
_DECLARED_IN = {
102+
"A2UiBoundValue": "adcp.types.domains.a2ui.bound_value",
103+
"A2UiBoundValue1": "adcp.types.domains.a2ui.bound_value",
104+
"A2UiBoundValue2": "adcp.types.domains.a2ui.bound_value",
105+
"A2UiBoundValue3": "adcp.types.domains.a2ui.bound_value",
106+
"A2UiBoundValue4": "adcp.types.domains.a2ui.bound_value",
107+
"A2UiBoundValue5": "adcp.types.domains.a2ui.bound_value",
108+
"A2UiComponent": "adcp.types.domains.a2ui.component",
109+
"A2UiSurface": "adcp.types.domains.a2ui.surface",
110+
"A2UiUserAction": "adcp.types.domains.a2ui.user_action",
111+
"Action23": "adcp.types.domains.a2ui.si_catalog",
112+
"Align": "adcp.types.domains.a2ui.si_catalog",
113+
"AppHandoff": "adcp.types.domains.a2ui.si_catalog",
114+
"Apps": "adcp.types.domains.a2ui.si_catalog",
115+
"Button": "adcp.types.domains.a2ui.si_catalog",
116+
"Card": "adcp.types.domains.a2ui.si_catalog",
117+
"Column": "adcp.types.domains.a2ui.si_catalog",
118+
"Component": "adcp.types.domains.a2ui.si_catalog",
119+
"Image": "adcp.types.domains.a2ui.si_catalog",
120+
"IntegrationAction": "adcp.types.domains.a2ui.si_catalog",
121+
"Justify": "adcp.types.domains.a2ui.si_catalog",
122+
"Layout": "adcp.types.domains.a2ui.si_catalog",
123+
"Link": "adcp.types.domains.a2ui.si_catalog",
124+
"List": "adcp.types.domains.a2ui.si_catalog",
125+
"ProductCard": "adcp.types.domains.a2ui.si_catalog",
126+
"Row": "adcp.types.domains.a2ui.si_catalog",
127+
"SiComponentCatalog": "adcp.types.domains.a2ui.si_catalog",
128+
"Template": "adcp.types.domains.a2ui.si_catalog",
129+
"Text": "adcp.types.domains.a2ui.si_catalog",
130+
"Type": "adcp.types.domains.a2ui.si_catalog",
131+
"Variant": "adcp.types.domains.a2ui.si_catalog",
132+
"Variant4": "adcp.types.domains.a2ui.si_catalog",
133+
}
134+
135+
if not TYPE_CHECKING:
136+
import importlib as _importlib
137+
138+
def __getattr__(name: str) -> object:
139+
module_name = _DECLARED_IN.get(name)
140+
if module_name is None:
141+
# Not a declared name: let the import system try a submodule, or fail.
142+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
143+
value = getattr(_importlib.import_module(module_name), name)
144+
globals()[name] = value
145+
return value
146+
147+
def __dir__() -> list[str]:
148+
return sorted(set(globals()) | set(__all__))

‎src/adcp/types/domains/aao/__init__.py‎

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,28 @@
99
it from its own schema's module, ``adcp.types.domains.aao.<schema>``.
1010
Nothing here is renamed.
1111
12+
This root is lazy (PEP 562): importing it, or a schema module under it,
13+
does not import every schema in the domain. A name is bound from its
14+
declaring module on first access and cached here. That is what keeps a
15+
schema module's ``from ...<domain> import <sibling>`` from re-entering a
16+
half-initialized module through this package (#1784).
17+
1218
Auto-generated from the generated domain tree. DO NOT EDIT MANUALLY.
13-
Generation date: 2026-10-04 18:45:11 UTC
19+
Generation date: 2026-11-01 23:49:37 UTC
1420
"""
1521

1622
# ruff: noqa: E501, I001
1723
from __future__ import annotations
1824

19-
from adcp.types.domains.aao.agent_publishers import (
20-
AaoDirectoryAgentPublishersInverseLookupResponse,
21-
DiscoveryMethod,
22-
PublisherEntry,
23-
Status,
24-
)
25+
from typing import TYPE_CHECKING
26+
27+
if TYPE_CHECKING:
28+
from adcp.types.domains.aao.agent_publishers import (
29+
AaoDirectoryAgentPublishersInverseLookupResponse,
30+
DiscoveryMethod,
31+
PublisherEntry,
32+
Status,
33+
)
2534

2635
# Explicit exports
2736
__all__ = [
@@ -30,3 +39,27 @@
3039
"PublisherEntry",
3140
"Status",
3241
]
42+
43+
44+
#: The schema module that declares each name, for the lazy binding below.
45+
_DECLARED_IN = {
46+
"AaoDirectoryAgentPublishersInverseLookupResponse": "adcp.types.domains.aao.agent_publishers",
47+
"DiscoveryMethod": "adcp.types.domains.aao.agent_publishers",
48+
"PublisherEntry": "adcp.types.domains.aao.agent_publishers",
49+
"Status": "adcp.types.domains.aao.agent_publishers",
50+
}
51+
52+
if not TYPE_CHECKING:
53+
import importlib as _importlib
54+
55+
def __getattr__(name: str) -> object:
56+
module_name = _DECLARED_IN.get(name)
57+
if module_name is None:
58+
# Not a declared name: let the import system try a submodule, or fail.
59+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
60+
value = getattr(_importlib.import_module(module_name), name)
61+
globals()[name] = value
62+
return value
63+
64+
def __dir__() -> list[str]:
65+
return sorted(set(globals()) | set(__all__))

0 commit comments

Comments
 (0)