topkit 0.2.0a3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- TopKit/__init__.py +82 -0
- TopKit/access.py +357 -0
- TopKit/contracts.py +360 -0
- TopKit/declarations.py +1010 -0
- TopKit/errors.py +138 -0
- TopKit/fields.py +165 -0
- TopKit/geometry.py +120 -0
- TopKit/lifecycle.py +220 -0
- TopKit/overlay.py +791 -0
- TopKit/queries.py +90 -0
- TopKit/state.py +862 -0
- TopKit/tags.py +251 -0
- TopKit/transactions.py +474 -0
- topkit-0.2.0a3.dist-info/METADATA +136 -0
- topkit-0.2.0a3.dist-info/RECORD +18 -0
- topkit-0.2.0a3.dist-info/WHEEL +5 -0
- topkit-0.2.0a3.dist-info/licenses/LICENSE +202 -0
- topkit-0.2.0a3.dist-info/top_level.txt +1 -0
TopKit/tags.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""The public TOP types: Tag and its metaclass.
|
|
2
|
+
|
|
3
|
+
TOP borrows the language's own syntax for Tag-level acts and leaves the
|
|
4
|
+
Tag's dotted namespace to the program:
|
|
5
|
+
|
|
6
|
+
Wizard(charlie) apply (Bases first)
|
|
7
|
+
charlie in Wizard active membership, sound or defective
|
|
8
|
+
"Deprecated" in Wizard a keyword: the Tag carries the Flag Pin Deprecated
|
|
9
|
+
Wizard in charlie the same, from the Agent's side
|
|
10
|
+
"Wizard" in charlie the same, by name
|
|
11
|
+
isinstance(charlie, Wizard) ever a member ("ever a Wizard, always a Wizard")
|
|
12
|
+
for w in Wizard the sound population
|
|
13
|
+
if Wizard is there a sound Wizard at all?
|
|
14
|
+
for w in ~Wizard the defective population
|
|
15
|
+
Wizard[:] everyone: the whole Field
|
|
16
|
+
Wizard[charlie] the Agent-bound view
|
|
17
|
+
del Wizard[charlie] leave the Field (Rip)
|
|
18
|
+
Form(Wizard) the Base-first closure, as Tags
|
|
19
|
+
f"{Wizard:form}" the same, as text
|
|
20
|
+
|
|
21
|
+
A Tag marked @Pin applies to Tags (STEP-SPEC-9); the pinned Tag is then
|
|
22
|
+
an Agent in every spelling above: Rare(Wizard), Wizard in Rare,
|
|
23
|
+
for tag in Rare, Rare[Wizard], del Rare[Wizard], f"{Wizard:pins}".
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any
|
|
29
|
+
from typing import Iterator
|
|
30
|
+
|
|
31
|
+
from .access import _view_of
|
|
32
|
+
from .contracts import _holds
|
|
33
|
+
from .declarations import _MISSING
|
|
34
|
+
from .declarations import _check_pin_bases
|
|
35
|
+
from .declarations import _is_pin
|
|
36
|
+
from .declarations import _name_checks
|
|
37
|
+
from .errors import TagCompositionError
|
|
38
|
+
from .fields import _Field
|
|
39
|
+
from .fields import _Partition
|
|
40
|
+
from .geometry import _form_of
|
|
41
|
+
from .geometry import _is_tag
|
|
42
|
+
from .lifecycle import _rip
|
|
43
|
+
from .state import Tagged
|
|
44
|
+
from .state import _name_of
|
|
45
|
+
from .state import _state_of
|
|
46
|
+
from .transactions import _apply
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class MetaTag(type):
|
|
50
|
+
"""Metaclass of every Tag: the Tag-level acts, in language syntax."""
|
|
51
|
+
|
|
52
|
+
def __new__(
|
|
53
|
+
meta,
|
|
54
|
+
name: str,
|
|
55
|
+
bases: tuple[type, ...],
|
|
56
|
+
namespace: dict[str, Any],
|
|
57
|
+
**kwargs: Any,
|
|
58
|
+
) -> "MetaTag":
|
|
59
|
+
namespace.setdefault(
|
|
60
|
+
"_topkit_field",
|
|
61
|
+
_Field(),
|
|
62
|
+
)
|
|
63
|
+
_name_checks(namespace)
|
|
64
|
+
|
|
65
|
+
tag = super().__new__(
|
|
66
|
+
meta,
|
|
67
|
+
name,
|
|
68
|
+
bases,
|
|
69
|
+
namespace,
|
|
70
|
+
**kwargs,
|
|
71
|
+
)
|
|
72
|
+
_check_pin_bases(tag)
|
|
73
|
+
|
|
74
|
+
return tag
|
|
75
|
+
|
|
76
|
+
def __call__(
|
|
77
|
+
tag,
|
|
78
|
+
target: object = _MISSING,
|
|
79
|
+
/,
|
|
80
|
+
**inputs: Any,
|
|
81
|
+
) -> object:
|
|
82
|
+
if target is _MISSING:
|
|
83
|
+
raise TypeError(
|
|
84
|
+
f"{tag.__name__} is a Tag: apply it to a Target,"
|
|
85
|
+
f" {tag.__name__}(target)"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if _is_pin(tag):
|
|
89
|
+
_check_pin_target(
|
|
90
|
+
tag,
|
|
91
|
+
target,
|
|
92
|
+
)
|
|
93
|
+
elif isinstance(target, type):
|
|
94
|
+
raise TypeError(
|
|
95
|
+
f"{tag.__name__} is applied to objects, not classes"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return _apply(
|
|
99
|
+
target,
|
|
100
|
+
tag,
|
|
101
|
+
inputs,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def __contains__(
|
|
105
|
+
tag,
|
|
106
|
+
candidate: object,
|
|
107
|
+
) -> bool:
|
|
108
|
+
"""``agent in Wizard``: membership. ``"Deprecated" in Wizard``: a
|
|
109
|
+
keyword among the Tag's Flag Pins; a string is never a member."""
|
|
110
|
+
|
|
111
|
+
if isinstance(candidate, str):
|
|
112
|
+
from .access import _keyword
|
|
113
|
+
|
|
114
|
+
return _keyword(
|
|
115
|
+
tag,
|
|
116
|
+
candidate,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
state = _state_of(candidate)
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
state is not None
|
|
123
|
+
and tag in state.active
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def __instancecheck__(
|
|
127
|
+
tag,
|
|
128
|
+
candidate: object,
|
|
129
|
+
) -> bool:
|
|
130
|
+
state = _state_of(candidate)
|
|
131
|
+
|
|
132
|
+
if state is not None and tag in state.ever:
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
return super().__instancecheck__(candidate)
|
|
136
|
+
|
|
137
|
+
def _sound(
|
|
138
|
+
tag,
|
|
139
|
+
) -> _Partition:
|
|
140
|
+
return _Partition(
|
|
141
|
+
tag._topkit_field,
|
|
142
|
+
_holds,
|
|
143
|
+
"sound",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def __iter__(
|
|
147
|
+
tag,
|
|
148
|
+
) -> Iterator[object]:
|
|
149
|
+
return iter(tag._sound())
|
|
150
|
+
|
|
151
|
+
def __len__(
|
|
152
|
+
tag,
|
|
153
|
+
) -> int:
|
|
154
|
+
return len(tag._sound())
|
|
155
|
+
|
|
156
|
+
def __bool__(
|
|
157
|
+
tag,
|
|
158
|
+
) -> bool:
|
|
159
|
+
return bool(tag._sound())
|
|
160
|
+
|
|
161
|
+
def __invert__(
|
|
162
|
+
tag,
|
|
163
|
+
) -> _Partition:
|
|
164
|
+
return ~tag._sound()
|
|
165
|
+
|
|
166
|
+
def __getitem__(
|
|
167
|
+
tag,
|
|
168
|
+
key: Any,
|
|
169
|
+
) -> Any:
|
|
170
|
+
if isinstance(key, slice):
|
|
171
|
+
if key != slice(None):
|
|
172
|
+
raise TypeError(
|
|
173
|
+
"Tag[:] is the whole Field; positional slices have"
|
|
174
|
+
" no meaning for a population"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return tag._topkit_field
|
|
178
|
+
|
|
179
|
+
return _view_of(
|
|
180
|
+
key,
|
|
181
|
+
tag,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def __delitem__(
|
|
185
|
+
tag,
|
|
186
|
+
agent: object,
|
|
187
|
+
) -> None:
|
|
188
|
+
_rip(
|
|
189
|
+
agent,
|
|
190
|
+
tag,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def __format__(
|
|
194
|
+
tag,
|
|
195
|
+
spec: str,
|
|
196
|
+
) -> str:
|
|
197
|
+
if spec == "":
|
|
198
|
+
return str(tag)
|
|
199
|
+
|
|
200
|
+
if spec == "form":
|
|
201
|
+
return " → ".join(
|
|
202
|
+
member.__name__
|
|
203
|
+
for member in _form_of(tag)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if spec == "pins":
|
|
207
|
+
from .queries import Tags
|
|
208
|
+
|
|
209
|
+
return ", ".join(
|
|
210
|
+
pin.__name__
|
|
211
|
+
for pin in Tags(tag)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if spec == "contract":
|
|
215
|
+
from .contracts import Contract
|
|
216
|
+
|
|
217
|
+
return Contract.Display(tag)
|
|
218
|
+
|
|
219
|
+
raise ValueError(
|
|
220
|
+
f"unknown format spec {spec!r} for a Tag; use 'form',"
|
|
221
|
+
" 'pins', or 'contract'"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _check_pin_target(
|
|
226
|
+
pin: type,
|
|
227
|
+
target: object,
|
|
228
|
+
) -> None:
|
|
229
|
+
if not _is_tag(target):
|
|
230
|
+
raise TagCompositionError(
|
|
231
|
+
f"{pin.__name__} is a Pin: apply it to a Tag, not to"
|
|
232
|
+
f" {_name_of(target)}"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if target in _form_of(pin):
|
|
236
|
+
raise TagCompositionError(
|
|
237
|
+
f"{pin.__name__} cannot pin itself"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class Tag(metaclass=MetaTag):
|
|
242
|
+
"""A semantic category. Subclass it; its members are its contributions."""
|
|
243
|
+
|
|
244
|
+
__slots__ = ()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
__all__ = [
|
|
248
|
+
"MetaTag",
|
|
249
|
+
"Tag",
|
|
250
|
+
"Tagged",
|
|
251
|
+
]
|
TopKit/transactions.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"""Applying a Tag: the tagging sequence and its call boundary.
|
|
2
|
+
|
|
3
|
+
Once for the whole call:
|
|
4
|
+
|
|
5
|
+
1. Gate: the Preconditions visible in the composed Form of this call
|
|
6
|
+
inspect the incoming Agent. A Shape's override wins over its Base's.
|
|
7
|
+
|
|
8
|
+
For each Tag in the Form (Bases first), in order:
|
|
9
|
+
|
|
10
|
+
2. Its Records are built (each may read the value already stored).
|
|
11
|
+
3. Commit: membership, Overlay, runtime type.
|
|
12
|
+
4. Its Imprints run.
|
|
13
|
+
|
|
14
|
+
Once for the whole call:
|
|
15
|
+
|
|
16
|
+
5. Every visible Postcondition is checked.
|
|
17
|
+
|
|
18
|
+
A failure in 1 or 2 rolls the whole call back: the Agent is exactly as it
|
|
19
|
+
was, including Bases pulled in by this call. A failure in 4 or 5 raises
|
|
20
|
+
but the Tags stay: the product left the line, defective, to be repaired
|
|
21
|
+
or Ripped.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import Any
|
|
27
|
+
import warnings
|
|
28
|
+
|
|
29
|
+
from .contracts import _evaluate
|
|
30
|
+
from .declarations import _Declarations
|
|
31
|
+
from .declarations import _declarations_of
|
|
32
|
+
from .declarations import _protocol_inputs
|
|
33
|
+
from .errors import TagCompositionError
|
|
34
|
+
from .errors import TagError
|
|
35
|
+
from .errors import TagImprintError
|
|
36
|
+
from .errors import TagPostconditionError
|
|
37
|
+
from .errors import TagPreconditionError
|
|
38
|
+
from .geometry import _form_of
|
|
39
|
+
from .overlay import _install
|
|
40
|
+
from .overlay import _materialize
|
|
41
|
+
from .state import STATE
|
|
42
|
+
from .state import Tagged
|
|
43
|
+
from .state import _Snapshot
|
|
44
|
+
from .state import _State
|
|
45
|
+
from .state import _bind_to
|
|
46
|
+
from .state import _namespace_of
|
|
47
|
+
from .state import _rebind_all
|
|
48
|
+
from .state import _restore_namespace
|
|
49
|
+
from .state import _runtime_type_for
|
|
50
|
+
from .state import _state_for
|
|
51
|
+
from .state import _state_of
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _apply(
|
|
55
|
+
agent: object,
|
|
56
|
+
tag: type,
|
|
57
|
+
inputs: dict[str, Any],
|
|
58
|
+
) -> object:
|
|
59
|
+
"""Apply ``tag`` and its missing Bases to ``agent`` as one call."""
|
|
60
|
+
|
|
61
|
+
entry_namespace = dict(_namespace_of(agent) or {})
|
|
62
|
+
entry_state = _state_of(agent)
|
|
63
|
+
entry_copy = entry_state.Copy() if entry_state is not None else None
|
|
64
|
+
entry_tags = tuple(entry_state.active) if entry_state is not None else ()
|
|
65
|
+
entry_class = type(agent)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
state = _state_for(agent)
|
|
69
|
+
pending = [
|
|
70
|
+
member
|
|
71
|
+
for member in _form_of(tag)
|
|
72
|
+
if member not in state.active
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
if pending:
|
|
76
|
+
if any(
|
|
77
|
+
_declarations_of(member).preconditions
|
|
78
|
+
for member in pending
|
|
79
|
+
):
|
|
80
|
+
_gate(
|
|
81
|
+
agent,
|
|
82
|
+
state,
|
|
83
|
+
pending,
|
|
84
|
+
inputs,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
for member in pending:
|
|
88
|
+
_apply_one(
|
|
89
|
+
agent,
|
|
90
|
+
member,
|
|
91
|
+
inputs,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
_inspect(agent)
|
|
95
|
+
except (TagImprintError, TagPostconditionError):
|
|
96
|
+
raise
|
|
97
|
+
except BaseException:
|
|
98
|
+
_rollback(
|
|
99
|
+
agent,
|
|
100
|
+
entry_namespace,
|
|
101
|
+
entry_copy,
|
|
102
|
+
entry_tags,
|
|
103
|
+
entry_class,
|
|
104
|
+
)
|
|
105
|
+
raise
|
|
106
|
+
|
|
107
|
+
return agent
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _rollback(
|
|
111
|
+
agent: object,
|
|
112
|
+
entry_namespace: dict[str, Any],
|
|
113
|
+
entry_copy: _State | None,
|
|
114
|
+
entry_tags: tuple[type, ...],
|
|
115
|
+
entry_class: type,
|
|
116
|
+
) -> None:
|
|
117
|
+
current = _state_of(agent)
|
|
118
|
+
|
|
119
|
+
if current is not None:
|
|
120
|
+
for tag in current.active:
|
|
121
|
+
if tag not in entry_tags:
|
|
122
|
+
tag._topkit_field.Remove(agent)
|
|
123
|
+
|
|
124
|
+
_restore_namespace(
|
|
125
|
+
agent,
|
|
126
|
+
entry_namespace,
|
|
127
|
+
)
|
|
128
|
+
namespace = _namespace_of(agent)
|
|
129
|
+
|
|
130
|
+
if namespace is None:
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
if entry_copy is not None:
|
|
134
|
+
namespace[STATE] = entry_copy
|
|
135
|
+
else:
|
|
136
|
+
namespace.pop(STATE, None)
|
|
137
|
+
|
|
138
|
+
if type(agent) is not entry_class:
|
|
139
|
+
agent.__class__ = entry_class
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _gate(
|
|
143
|
+
agent: object,
|
|
144
|
+
state: _State,
|
|
145
|
+
pending: list[type],
|
|
146
|
+
inputs: dict[str, Any],
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Inspect the incoming materials once, against the Form as it will be.
|
|
149
|
+
|
|
150
|
+
Every pending Tag is laid over a scratch copy so that a Shape's
|
|
151
|
+
Precondition overrides (relaxes) its Base's, and so that declaration
|
|
152
|
+
errors and collisions surface before anything changes.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
scratch = state.Copy()
|
|
156
|
+
names: list[str] = []
|
|
157
|
+
|
|
158
|
+
with warnings.catch_warnings():
|
|
159
|
+
warnings.simplefilter("ignore")
|
|
160
|
+
|
|
161
|
+
for tag in pending:
|
|
162
|
+
declarations = _declarations_of(tag)
|
|
163
|
+
|
|
164
|
+
_install(
|
|
165
|
+
scratch,
|
|
166
|
+
tag,
|
|
167
|
+
declarations,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
for name, _function in declarations.preconditions:
|
|
171
|
+
if name not in names:
|
|
172
|
+
names.append(name)
|
|
173
|
+
|
|
174
|
+
state.composing += 1
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
_evaluate(
|
|
178
|
+
(
|
|
179
|
+
(name, scratch.preconditions[name])
|
|
180
|
+
for name in names
|
|
181
|
+
if name in scratch.preconditions
|
|
182
|
+
),
|
|
183
|
+
agent,
|
|
184
|
+
inputs,
|
|
185
|
+
TagPreconditionError,
|
|
186
|
+
"Precondition",
|
|
187
|
+
)
|
|
188
|
+
finally:
|
|
189
|
+
state.composing -= 1
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _apply_one(
|
|
193
|
+
agent: object,
|
|
194
|
+
tag: type,
|
|
195
|
+
inputs: dict[str, Any],
|
|
196
|
+
) -> None:
|
|
197
|
+
# The state is laid over in place: the call boundary (_apply) holds the
|
|
198
|
+
# entry copy that a Record failure rolls back to, and nothing reads the
|
|
199
|
+
# new Overlay before commit binds it on the Agent.
|
|
200
|
+
state = _state_for(agent)
|
|
201
|
+
declarations = _declarations_of(tag)
|
|
202
|
+
deleted_before = set(state.deleted)
|
|
203
|
+
state.composing += 1
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
_install(
|
|
207
|
+
state,
|
|
208
|
+
tag,
|
|
209
|
+
declarations,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
_materialize(
|
|
213
|
+
agent,
|
|
214
|
+
declarations,
|
|
215
|
+
deleted_before,
|
|
216
|
+
inputs,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
_commit(
|
|
220
|
+
agent,
|
|
221
|
+
state,
|
|
222
|
+
deleted_before,
|
|
223
|
+
tag,
|
|
224
|
+
declarations,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
_imprint(
|
|
229
|
+
agent,
|
|
230
|
+
declarations,
|
|
231
|
+
inputs,
|
|
232
|
+
)
|
|
233
|
+
finally:
|
|
234
|
+
state.snapshots[tag] = _snapshot(
|
|
235
|
+
agent,
|
|
236
|
+
state,
|
|
237
|
+
)
|
|
238
|
+
finally:
|
|
239
|
+
state.composing -= 1
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _inspect(
|
|
243
|
+
agent: object,
|
|
244
|
+
) -> None:
|
|
245
|
+
"""Quality check of the finished product: every visible Postcondition."""
|
|
246
|
+
|
|
247
|
+
state = _state_of(agent)
|
|
248
|
+
state.composing += 1
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
_evaluate(
|
|
252
|
+
state.postconditions.items(),
|
|
253
|
+
agent,
|
|
254
|
+
{},
|
|
255
|
+
TagPostconditionError,
|
|
256
|
+
"Postcondition",
|
|
257
|
+
)
|
|
258
|
+
finally:
|
|
259
|
+
state.composing -= 1
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _commit(
|
|
263
|
+
agent: object,
|
|
264
|
+
state: _State,
|
|
265
|
+
deleted_before: set[str],
|
|
266
|
+
tag: type,
|
|
267
|
+
declarations: _Declarations,
|
|
268
|
+
) -> None:
|
|
269
|
+
namespace = _namespace_of(agent)
|
|
270
|
+
|
|
271
|
+
for name in state.deleted - deleted_before:
|
|
272
|
+
namespace.pop(name, None)
|
|
273
|
+
|
|
274
|
+
state.active.append(tag)
|
|
275
|
+
state.ever.add(tag)
|
|
276
|
+
|
|
277
|
+
if declarations.secrets:
|
|
278
|
+
# A first secret turns every bound Action into a composing one.
|
|
279
|
+
_rebind_all(
|
|
280
|
+
agent,
|
|
281
|
+
state,
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
for name, _function in declarations.actions:
|
|
285
|
+
if name in state.actions:
|
|
286
|
+
_bind_to(
|
|
287
|
+
agent,
|
|
288
|
+
state,
|
|
289
|
+
name,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
for name, _operation, public in declarations.operations:
|
|
293
|
+
if public and name in state.actions:
|
|
294
|
+
_bind_to(
|
|
295
|
+
agent,
|
|
296
|
+
state,
|
|
297
|
+
name,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
if _needs_new_type(
|
|
301
|
+
agent,
|
|
302
|
+
declarations,
|
|
303
|
+
):
|
|
304
|
+
next_type = _runtime_type_for(state)
|
|
305
|
+
else:
|
|
306
|
+
next_type = type(agent)
|
|
307
|
+
|
|
308
|
+
if type(agent) is not next_type:
|
|
309
|
+
try:
|
|
310
|
+
agent.__class__ = next_type
|
|
311
|
+
except TypeError as error:
|
|
312
|
+
raise TagCompositionError(
|
|
313
|
+
f"{type(agent).__name__} cannot be actualized in place"
|
|
314
|
+
) from error
|
|
315
|
+
|
|
316
|
+
tag._topkit_field.Add(agent)
|
|
317
|
+
|
|
318
|
+
if state.pinned is not None and declarations.published:
|
|
319
|
+
_publish_to_field(
|
|
320
|
+
state.pinned,
|
|
321
|
+
declarations.published,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _publish_to_field(
|
|
326
|
+
pinned: type,
|
|
327
|
+
names: frozenset[str],
|
|
328
|
+
) -> None:
|
|
329
|
+
"""A Pin's @Public members reach the pinned Tag's Field: every present
|
|
330
|
+
Agent receives them now as the Tag's own published Reports and
|
|
331
|
+
Operations; every future Agent through the Tag's declarations. The
|
|
332
|
+
whole Field is checked on copies first, so no Agent is touched unless
|
|
333
|
+
all can be."""
|
|
334
|
+
|
|
335
|
+
from .declarations import _scan_cache
|
|
336
|
+
from .overlay import _adapter
|
|
337
|
+
|
|
338
|
+
state = _state_of(pinned)
|
|
339
|
+
_scan_cache.pop(pinned, None) # the Tag's declarations grew
|
|
340
|
+
agents = list(pinned[:])
|
|
341
|
+
plans: list[tuple[object, _State]] = []
|
|
342
|
+
|
|
343
|
+
with warnings.catch_warnings():
|
|
344
|
+
warnings.simplefilter("ignore") # the real pass warns once
|
|
345
|
+
|
|
346
|
+
for agent in agents:
|
|
347
|
+
candidate = _state_of(agent).Copy()
|
|
348
|
+
_publish_into(
|
|
349
|
+
candidate,
|
|
350
|
+
pinned,
|
|
351
|
+
state,
|
|
352
|
+
names,
|
|
353
|
+
_adapter,
|
|
354
|
+
)
|
|
355
|
+
plans.append(
|
|
356
|
+
(
|
|
357
|
+
agent,
|
|
358
|
+
candidate,
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
for agent, candidate in plans:
|
|
363
|
+
agent_state = _state_of(agent)
|
|
364
|
+
_publish_into(
|
|
365
|
+
agent_state,
|
|
366
|
+
pinned,
|
|
367
|
+
state,
|
|
368
|
+
names,
|
|
369
|
+
_adapter,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
for name in names:
|
|
373
|
+
if name in agent_state.actions:
|
|
374
|
+
_bind_to(
|
|
375
|
+
agent,
|
|
376
|
+
agent_state,
|
|
377
|
+
name,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
next_type = _runtime_type_for(agent_state)
|
|
381
|
+
|
|
382
|
+
if type(agent) is not next_type:
|
|
383
|
+
agent.__class__ = next_type
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _publish_into(
|
|
387
|
+
agent_state: _State,
|
|
388
|
+
pinned: type,
|
|
389
|
+
state: _State,
|
|
390
|
+
names: frozenset[str],
|
|
391
|
+
adapter: Any,
|
|
392
|
+
) -> None:
|
|
393
|
+
from .overlay import _install_action
|
|
394
|
+
|
|
395
|
+
for name in names:
|
|
396
|
+
if name in state.records:
|
|
397
|
+
agent_state.reports[name] = (pinned, None)
|
|
398
|
+
agent_state.published.add(name)
|
|
399
|
+
elif name in state.actions:
|
|
400
|
+
_install_action(
|
|
401
|
+
agent_state,
|
|
402
|
+
pinned,
|
|
403
|
+
name,
|
|
404
|
+
adapter(pinned, name, state.actions[name]),
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _needs_new_type(
|
|
409
|
+
agent: object,
|
|
410
|
+
declarations: _Declarations,
|
|
411
|
+
) -> bool:
|
|
412
|
+
"""Only type-level facts change the runtime type: the first tagging,
|
|
413
|
+
deletions, secrets, published Reports, dunder Actions, a first Post."""
|
|
414
|
+
|
|
415
|
+
if not isinstance(agent, Tagged):
|
|
416
|
+
return True
|
|
417
|
+
|
|
418
|
+
return bool(
|
|
419
|
+
declarations.deletions
|
|
420
|
+
or declarations.secrets
|
|
421
|
+
or declarations.dunders
|
|
422
|
+
or declarations.postconditions
|
|
423
|
+
or any(public for _name, _value, public in declarations.reports)
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _imprint(
|
|
428
|
+
agent: object,
|
|
429
|
+
declarations: _Declarations,
|
|
430
|
+
inputs: dict[str, Any],
|
|
431
|
+
) -> None:
|
|
432
|
+
for name, imprint in declarations.imprints:
|
|
433
|
+
try:
|
|
434
|
+
imprint(
|
|
435
|
+
agent,
|
|
436
|
+
**_protocol_inputs(imprint, inputs, 1),
|
|
437
|
+
)
|
|
438
|
+
except TagError:
|
|
439
|
+
raise
|
|
440
|
+
except Exception as error:
|
|
441
|
+
raise TagImprintError.Named(name)(
|
|
442
|
+
f"Imprint {imprint.__qualname__} failed:"
|
|
443
|
+
f" {type(error).__name__}: {error}"
|
|
444
|
+
) from error
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _snapshot(
|
|
448
|
+
agent: object,
|
|
449
|
+
state: _State,
|
|
450
|
+
) -> _Snapshot:
|
|
451
|
+
namespace = _namespace_of(agent)
|
|
452
|
+
records = {
|
|
453
|
+
name: namespace[name]
|
|
454
|
+
for name in state.records
|
|
455
|
+
if name in namespace
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
for name in state.records:
|
|
459
|
+
if name in state.secret_values:
|
|
460
|
+
records[name] = state.secret_values[name]
|
|
461
|
+
|
|
462
|
+
reports = {
|
|
463
|
+
name: (origin, getattr(origin, name))
|
|
464
|
+
for name, (origin, _report) in state.reports.items()
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return _Snapshot(
|
|
468
|
+
actions=dict(state.actions),
|
|
469
|
+
records=records,
|
|
470
|
+
reports=reports,
|
|
471
|
+
operations=dict(state.operations),
|
|
472
|
+
deleted=frozenset(state.deleted),
|
|
473
|
+
secrets=frozenset(state.secrets),
|
|
474
|
+
)
|