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/state.py
ADDED
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
"""Agent state and the runtime type.
|
|
2
|
+
|
|
3
|
+
An Agent keeps one ``_TOPKIT_STATE`` in its instance dictionary. Actions
|
|
4
|
+
live in that dictionary as bound callables, Records as plain values, so
|
|
5
|
+
ordinary attribute access costs what it costs on a plain object. The
|
|
6
|
+
runtime type is neutral (host first, then the ``Tagged`` marker) and only
|
|
7
|
+
carries what Python requires on a type: special-method Actions and the
|
|
8
|
+
descriptors that gate deleted, secret, and published names.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from dataclasses import field
|
|
15
|
+
from types import MethodType
|
|
16
|
+
from typing import Any
|
|
17
|
+
from typing import Callable
|
|
18
|
+
from typing import Iterator
|
|
19
|
+
from weakref import WeakValueDictionary
|
|
20
|
+
import weakref
|
|
21
|
+
|
|
22
|
+
from .declarations import STATE
|
|
23
|
+
from .declarations import _MISSING
|
|
24
|
+
from .declarations import _is_dunder
|
|
25
|
+
from .declarations import _is_flag
|
|
26
|
+
from .errors import TagCompositionError
|
|
27
|
+
|
|
28
|
+
Function = Callable[..., Any]
|
|
29
|
+
Check = Callable[..., Any]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ------------------------------------------------------------------
|
|
33
|
+
# State
|
|
34
|
+
# ------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class _Snapshot:
|
|
39
|
+
actions: dict[str, Function]
|
|
40
|
+
records: dict[str, Any]
|
|
41
|
+
reports: dict[str, tuple[type, Any]]
|
|
42
|
+
operations: dict[str, tuple[type, Function]]
|
|
43
|
+
deleted: frozenset[str]
|
|
44
|
+
secrets: frozenset[str]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class _State:
|
|
49
|
+
host_type: type
|
|
50
|
+
pinned: type | None = None # the Tag itself, when the Agent is a Tag
|
|
51
|
+
active: list[type] = field(default_factory=list)
|
|
52
|
+
ever: set[type] = field(default_factory=set)
|
|
53
|
+
actions: dict[str, Function] = field(default_factory=dict)
|
|
54
|
+
action_origins: dict[str, type] = field(default_factory=dict)
|
|
55
|
+
records: dict[str, type] = field(default_factory=dict)
|
|
56
|
+
preconditions: dict[str, Check] = field(default_factory=dict)
|
|
57
|
+
postconditions: dict[str, Check] = field(default_factory=dict)
|
|
58
|
+
reports: dict[str, tuple[type, Any]] = field(default_factory=dict)
|
|
59
|
+
operations: dict[str, tuple[type, Function]] = field(default_factory=dict)
|
|
60
|
+
published: set[str] = field(default_factory=set)
|
|
61
|
+
secrets: set[str] = field(default_factory=set)
|
|
62
|
+
deleted: set[str] = field(default_factory=set)
|
|
63
|
+
snapshots: dict[type, _Snapshot] = field(default_factory=dict)
|
|
64
|
+
rips: dict[type, tuple[Function, ...]] = field(default_factory=dict)
|
|
65
|
+
secret_values: dict[str, Any] = field(default_factory=dict) # a pinned Tag's @Secret members
|
|
66
|
+
originals: dict[str, Any] = field(default_factory=dict) # what a Pin patched, as declared
|
|
67
|
+
composing: int = 0
|
|
68
|
+
checking: bool = False
|
|
69
|
+
|
|
70
|
+
def Copy(
|
|
71
|
+
state,
|
|
72
|
+
) -> "_State":
|
|
73
|
+
return _State(
|
|
74
|
+
host_type=state.host_type,
|
|
75
|
+
pinned=state.pinned,
|
|
76
|
+
active=list(state.active),
|
|
77
|
+
ever=set(state.ever),
|
|
78
|
+
actions=dict(state.actions),
|
|
79
|
+
action_origins=dict(state.action_origins),
|
|
80
|
+
records=dict(state.records),
|
|
81
|
+
preconditions=dict(state.preconditions),
|
|
82
|
+
postconditions=dict(state.postconditions),
|
|
83
|
+
reports=dict(state.reports),
|
|
84
|
+
operations=dict(state.operations),
|
|
85
|
+
published=set(state.published),
|
|
86
|
+
secrets=set(state.secrets),
|
|
87
|
+
deleted=set(state.deleted),
|
|
88
|
+
snapshots=dict(state.snapshots),
|
|
89
|
+
rips=dict(state.rips),
|
|
90
|
+
secret_values=dict(state.secret_values),
|
|
91
|
+
originals=dict(state.originals),
|
|
92
|
+
composing=state.composing,
|
|
93
|
+
checking=state.checking,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class _Class_Namespace:
|
|
98
|
+
"""The namespace of a Tag used as a Target (a pinned Tag).
|
|
99
|
+
|
|
100
|
+
A class dictionary is read through a proxy and written through the
|
|
101
|
+
class. This adapter gives the kernel the few dictionary operations it
|
|
102
|
+
uses, so the tagging sequence is one code path for objects and Tags.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
__slots__ = ("_owner",)
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
namespace,
|
|
109
|
+
owner: type,
|
|
110
|
+
) -> None:
|
|
111
|
+
namespace._owner = owner
|
|
112
|
+
|
|
113
|
+
def get(
|
|
114
|
+
namespace,
|
|
115
|
+
name: str,
|
|
116
|
+
default: Any = None,
|
|
117
|
+
) -> Any:
|
|
118
|
+
return namespace._owner.__dict__.get(
|
|
119
|
+
name,
|
|
120
|
+
default,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def __getitem__(
|
|
124
|
+
namespace,
|
|
125
|
+
name: str,
|
|
126
|
+
) -> Any:
|
|
127
|
+
return namespace._owner.__dict__[name]
|
|
128
|
+
|
|
129
|
+
def __setitem__(
|
|
130
|
+
namespace,
|
|
131
|
+
name: str,
|
|
132
|
+
value: Any,
|
|
133
|
+
) -> None:
|
|
134
|
+
setattr(
|
|
135
|
+
namespace._owner,
|
|
136
|
+
name,
|
|
137
|
+
value,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def __contains__(
|
|
141
|
+
namespace,
|
|
142
|
+
name: object,
|
|
143
|
+
) -> bool:
|
|
144
|
+
return name in namespace._owner.__dict__
|
|
145
|
+
|
|
146
|
+
def pop(
|
|
147
|
+
namespace,
|
|
148
|
+
name: str,
|
|
149
|
+
default: Any = _MISSING,
|
|
150
|
+
) -> Any:
|
|
151
|
+
value = namespace._owner.__dict__.get(
|
|
152
|
+
name,
|
|
153
|
+
_MISSING,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if value is _MISSING:
|
|
157
|
+
if default is _MISSING:
|
|
158
|
+
raise KeyError(name)
|
|
159
|
+
|
|
160
|
+
return default
|
|
161
|
+
|
|
162
|
+
delattr(
|
|
163
|
+
namespace._owner,
|
|
164
|
+
name,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return value
|
|
168
|
+
|
|
169
|
+
def keys(
|
|
170
|
+
namespace,
|
|
171
|
+
) -> Any:
|
|
172
|
+
return namespace._owner.__dict__.keys()
|
|
173
|
+
|
|
174
|
+
def items(
|
|
175
|
+
namespace,
|
|
176
|
+
) -> Any:
|
|
177
|
+
return namespace._owner.__dict__.items()
|
|
178
|
+
|
|
179
|
+
def __iter__(
|
|
180
|
+
namespace,
|
|
181
|
+
) -> Iterator[str]:
|
|
182
|
+
return iter(namespace._owner.__dict__)
|
|
183
|
+
|
|
184
|
+
def __len__(
|
|
185
|
+
namespace,
|
|
186
|
+
) -> int:
|
|
187
|
+
return len(namespace._owner.__dict__)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _namespace_of(
|
|
191
|
+
agent: object,
|
|
192
|
+
) -> Any:
|
|
193
|
+
"""The Agent's writable namespace: its dictionary, or the adapter over
|
|
194
|
+
a Tag's class dictionary; None when it has neither."""
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
namespace = object.__getattribute__(
|
|
198
|
+
agent,
|
|
199
|
+
"__dict__",
|
|
200
|
+
)
|
|
201
|
+
except AttributeError:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
if isinstance(namespace, dict):
|
|
205
|
+
return namespace
|
|
206
|
+
|
|
207
|
+
if isinstance(agent, type):
|
|
208
|
+
return _Class_Namespace(agent)
|
|
209
|
+
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _restore_namespace(
|
|
214
|
+
agent: object,
|
|
215
|
+
entry: dict[str, Any],
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Put the namespace back exactly as it was at entry."""
|
|
218
|
+
|
|
219
|
+
namespace = _namespace_of(agent)
|
|
220
|
+
|
|
221
|
+
if namespace is None:
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
if isinstance(namespace, dict):
|
|
225
|
+
namespace.clear()
|
|
226
|
+
namespace.update(entry)
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
for name in list(namespace.keys()):
|
|
230
|
+
if name not in entry:
|
|
231
|
+
namespace.pop(name, None)
|
|
232
|
+
|
|
233
|
+
for name, value in entry.items():
|
|
234
|
+
if namespace.get(name, _MISSING) is not value:
|
|
235
|
+
namespace[name] = value
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _name_of(
|
|
239
|
+
agent: object,
|
|
240
|
+
) -> str:
|
|
241
|
+
"""How an Agent is called in messages: a Tag by its own name, an
|
|
242
|
+
object by its type's."""
|
|
243
|
+
|
|
244
|
+
if isinstance(agent, type):
|
|
245
|
+
return agent.__name__
|
|
246
|
+
|
|
247
|
+
return type(agent).__name__
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _state_of(
|
|
251
|
+
agent: object,
|
|
252
|
+
) -> _State | None:
|
|
253
|
+
"""The Agent's state, or None. Read straight from the dictionary (or a
|
|
254
|
+
Tag's dictionary proxy): this is on the path of ``agent in Tag``."""
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
return object.__getattribute__(
|
|
258
|
+
agent,
|
|
259
|
+
"__dict__",
|
|
260
|
+
).get(STATE)
|
|
261
|
+
except AttributeError:
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _state_for(
|
|
266
|
+
agent: object,
|
|
267
|
+
) -> _State:
|
|
268
|
+
"""The Agent's state, attached on first use."""
|
|
269
|
+
|
|
270
|
+
namespace = _namespace_of(agent)
|
|
271
|
+
|
|
272
|
+
if namespace is None:
|
|
273
|
+
raise TagCompositionError(
|
|
274
|
+
f"{type(agent).__name__} cannot carry TOP state"
|
|
275
|
+
" (no instance dictionary)"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
state = namespace.get(STATE)
|
|
279
|
+
|
|
280
|
+
if state is None:
|
|
281
|
+
state = _State(
|
|
282
|
+
host_type=type(agent),
|
|
283
|
+
pinned=agent if isinstance(agent, type) else None,
|
|
284
|
+
)
|
|
285
|
+
namespace[STATE] = state
|
|
286
|
+
|
|
287
|
+
return state
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _set_state(
|
|
291
|
+
agent: object,
|
|
292
|
+
state: _State,
|
|
293
|
+
) -> None:
|
|
294
|
+
_namespace_of(agent)[STATE] = state
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ------------------------------------------------------------------
|
|
298
|
+
# Bound Actions
|
|
299
|
+
# ------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class _Bound:
|
|
303
|
+
"""An Action bound to one Agent, stored in the Agent's dictionary.
|
|
304
|
+
|
|
305
|
+
Holds the Agent weakly so the Field's weak references stay honest.
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
__slots__ = (
|
|
309
|
+
"_function",
|
|
310
|
+
"_reference",
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
def __init__(
|
|
314
|
+
bound,
|
|
315
|
+
function: Function,
|
|
316
|
+
agent: object,
|
|
317
|
+
) -> None:
|
|
318
|
+
bound._function = function
|
|
319
|
+
bound._reference = weakref.ref(agent)
|
|
320
|
+
|
|
321
|
+
def __call__(
|
|
322
|
+
bound,
|
|
323
|
+
*args: Any,
|
|
324
|
+
**kwargs: Any,
|
|
325
|
+
) -> Any:
|
|
326
|
+
agent = bound._reference()
|
|
327
|
+
|
|
328
|
+
if agent is None:
|
|
329
|
+
raise ReferenceError("the Agent of this Action no longer exists")
|
|
330
|
+
|
|
331
|
+
return bound._function(
|
|
332
|
+
agent,
|
|
333
|
+
*args,
|
|
334
|
+
**kwargs,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def __name__(
|
|
339
|
+
bound,
|
|
340
|
+
) -> str:
|
|
341
|
+
return bound._function.__name__
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def __doc__(
|
|
345
|
+
bound,
|
|
346
|
+
) -> str | None:
|
|
347
|
+
return bound._function.__doc__
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def __func__(
|
|
351
|
+
bound,
|
|
352
|
+
) -> Function:
|
|
353
|
+
return bound._function
|
|
354
|
+
|
|
355
|
+
def __repr__(
|
|
356
|
+
bound,
|
|
357
|
+
) -> str:
|
|
358
|
+
agent = bound._reference()
|
|
359
|
+
owner = _name_of(agent) if agent is not None else "<gone>"
|
|
360
|
+
|
|
361
|
+
return f"<Action {bound._function.__name__} of {owner}>"
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class _Pinned_Operation:
|
|
365
|
+
"""An Action a Pin landed on a Tag: an Operation of that Tag.
|
|
366
|
+
|
|
367
|
+
Read from the pinned Tag or from any of its Shapes, it binds to the
|
|
368
|
+
Tag it was read from, as a classmethod does, so a Shape inherits it
|
|
369
|
+
the way it inherits every Tag-scope member.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
__slots__ = ("_function",)
|
|
373
|
+
|
|
374
|
+
def __init__(
|
|
375
|
+
operation,
|
|
376
|
+
function: Function,
|
|
377
|
+
) -> None:
|
|
378
|
+
operation._function = function
|
|
379
|
+
|
|
380
|
+
def __get__(
|
|
381
|
+
operation,
|
|
382
|
+
instance: object,
|
|
383
|
+
owner: type | None = None,
|
|
384
|
+
) -> Any:
|
|
385
|
+
if owner is None:
|
|
386
|
+
owner = type(instance)
|
|
387
|
+
|
|
388
|
+
return MethodType(
|
|
389
|
+
operation._function,
|
|
390
|
+
owner,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
def __func__(
|
|
395
|
+
operation,
|
|
396
|
+
) -> Function:
|
|
397
|
+
return operation._function
|
|
398
|
+
|
|
399
|
+
def __repr__(
|
|
400
|
+
operation,
|
|
401
|
+
) -> str:
|
|
402
|
+
return f"<pinned Operation {operation._function.__name__}>"
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class _Originals:
|
|
406
|
+
"""The pinned Tag's declarations as they were before its Pins patched
|
|
407
|
+
them, handed to a Pin's @Rip teardown as its second seat, so
|
|
408
|
+
un-patching is one deliberate line: ``tag.Control = original.Control``.
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
__slots__ = ("_declared",)
|
|
412
|
+
|
|
413
|
+
def __init__(
|
|
414
|
+
original,
|
|
415
|
+
declared: dict[str, Any],
|
|
416
|
+
) -> None:
|
|
417
|
+
object.__setattr__(original, "_declared", dict(declared))
|
|
418
|
+
|
|
419
|
+
def __getattr__(
|
|
420
|
+
original,
|
|
421
|
+
name: str,
|
|
422
|
+
) -> Any:
|
|
423
|
+
try:
|
|
424
|
+
return original._declared[name]
|
|
425
|
+
except KeyError:
|
|
426
|
+
raise AttributeError(
|
|
427
|
+
f"no original declaration named {name!r} was patched"
|
|
428
|
+
) from None
|
|
429
|
+
|
|
430
|
+
def __setattr__(
|
|
431
|
+
original,
|
|
432
|
+
name: str,
|
|
433
|
+
value: Any,
|
|
434
|
+
) -> None:
|
|
435
|
+
raise AttributeError("the originals are a record; they are read-only")
|
|
436
|
+
|
|
437
|
+
def __contains__(
|
|
438
|
+
original,
|
|
439
|
+
name: object,
|
|
440
|
+
) -> bool:
|
|
441
|
+
return name in original._declared
|
|
442
|
+
|
|
443
|
+
def __iter__(
|
|
444
|
+
original,
|
|
445
|
+
) -> Iterator[str]:
|
|
446
|
+
return iter(original._declared)
|
|
447
|
+
|
|
448
|
+
def __repr__(
|
|
449
|
+
original,
|
|
450
|
+
) -> str:
|
|
451
|
+
return f"<originals of {', '.join(original._declared) or 'nothing'}>"
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class _Composing_Pinned_Operation(_Pinned_Operation):
|
|
455
|
+
"""A pinned Operation that opens the pinned Tag's composition door
|
|
456
|
+
while it runs, so the Pin's @Secret members resolve inside it."""
|
|
457
|
+
|
|
458
|
+
__slots__ = ("_state",)
|
|
459
|
+
|
|
460
|
+
def __init__(
|
|
461
|
+
operation,
|
|
462
|
+
function: Function,
|
|
463
|
+
state: "_State",
|
|
464
|
+
) -> None:
|
|
465
|
+
super().__init__(function)
|
|
466
|
+
operation._state = state
|
|
467
|
+
|
|
468
|
+
def __get__(
|
|
469
|
+
operation,
|
|
470
|
+
instance: object,
|
|
471
|
+
owner: type | None = None,
|
|
472
|
+
) -> Any:
|
|
473
|
+
if owner is None:
|
|
474
|
+
owner = type(instance)
|
|
475
|
+
|
|
476
|
+
function = operation._function
|
|
477
|
+
state = operation._state
|
|
478
|
+
|
|
479
|
+
def Composing(
|
|
480
|
+
*args: Any,
|
|
481
|
+
**kwargs: Any,
|
|
482
|
+
) -> Any:
|
|
483
|
+
state.composing += 1
|
|
484
|
+
|
|
485
|
+
try:
|
|
486
|
+
return function(
|
|
487
|
+
owner,
|
|
488
|
+
*args,
|
|
489
|
+
**kwargs,
|
|
490
|
+
)
|
|
491
|
+
finally:
|
|
492
|
+
state.composing -= 1
|
|
493
|
+
|
|
494
|
+
Composing.__name__ = function.__name__
|
|
495
|
+
Composing.__doc__ = function.__doc__
|
|
496
|
+
|
|
497
|
+
return Composing
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
class _Composing_Bound(_Bound):
|
|
501
|
+
"""A bound Action that opens the composition door while it runs, so
|
|
502
|
+
@Secret members resolve inside it."""
|
|
503
|
+
|
|
504
|
+
__slots__ = ()
|
|
505
|
+
|
|
506
|
+
def __call__(
|
|
507
|
+
bound,
|
|
508
|
+
*args: Any,
|
|
509
|
+
**kwargs: Any,
|
|
510
|
+
) -> Any:
|
|
511
|
+
agent = bound._reference()
|
|
512
|
+
|
|
513
|
+
if agent is None:
|
|
514
|
+
raise ReferenceError("the Agent of this Action no longer exists")
|
|
515
|
+
|
|
516
|
+
state = _state_of(agent)
|
|
517
|
+
state.composing += 1
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
return bound._function(
|
|
521
|
+
agent,
|
|
522
|
+
*args,
|
|
523
|
+
**kwargs,
|
|
524
|
+
)
|
|
525
|
+
finally:
|
|
526
|
+
state.composing -= 1
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _bind_to(
|
|
530
|
+
agent: object,
|
|
531
|
+
state: _State,
|
|
532
|
+
name: str,
|
|
533
|
+
) -> None:
|
|
534
|
+
"""Store the Agent's visible Action ``name`` in its dictionary."""
|
|
535
|
+
|
|
536
|
+
function = state.actions[name]
|
|
537
|
+
|
|
538
|
+
if state.pinned is not None:
|
|
539
|
+
_bind_pinned(
|
|
540
|
+
agent,
|
|
541
|
+
state,
|
|
542
|
+
name,
|
|
543
|
+
function,
|
|
544
|
+
)
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
if state.secrets:
|
|
548
|
+
bound: _Bound = _Composing_Bound(function, agent)
|
|
549
|
+
else:
|
|
550
|
+
bound = _Bound(function, agent)
|
|
551
|
+
|
|
552
|
+
_namespace_of(agent)[name] = bound
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _bind_pinned(
|
|
556
|
+
tag: type,
|
|
557
|
+
state: _State,
|
|
558
|
+
name: str,
|
|
559
|
+
function: Function,
|
|
560
|
+
) -> None:
|
|
561
|
+
"""A Pin's Action on a Tag: a secret one lives in the state and is
|
|
562
|
+
read through the door; the rest live in the class dictionary."""
|
|
563
|
+
|
|
564
|
+
if state.secrets:
|
|
565
|
+
bound: _Pinned_Operation = _Composing_Pinned_Operation(function, state)
|
|
566
|
+
else:
|
|
567
|
+
bound = _Pinned_Operation(function)
|
|
568
|
+
|
|
569
|
+
if name in state.secrets:
|
|
570
|
+
_namespace_of(tag).pop(name, None)
|
|
571
|
+
state.secret_values[name] = bound
|
|
572
|
+
else:
|
|
573
|
+
_namespace_of(tag)[name] = bound
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _rebind_all(
|
|
577
|
+
agent: object,
|
|
578
|
+
state: _State,
|
|
579
|
+
) -> None:
|
|
580
|
+
for name in state.actions:
|
|
581
|
+
if not _is_dunder(name):
|
|
582
|
+
_bind_to(
|
|
583
|
+
agent,
|
|
584
|
+
state,
|
|
585
|
+
name,
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
# ------------------------------------------------------------------
|
|
590
|
+
# Descriptors installed on the runtime type
|
|
591
|
+
# ------------------------------------------------------------------
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class _Deleted:
|
|
595
|
+
"""A name a Tag deleted. Reads fail until something stores it again."""
|
|
596
|
+
|
|
597
|
+
__slots__ = ("name",)
|
|
598
|
+
|
|
599
|
+
def __init__(
|
|
600
|
+
gate,
|
|
601
|
+
name: str,
|
|
602
|
+
) -> None:
|
|
603
|
+
gate.name = name
|
|
604
|
+
|
|
605
|
+
def __get__(
|
|
606
|
+
gate,
|
|
607
|
+
agent: object,
|
|
608
|
+
owner: type | None = None,
|
|
609
|
+
) -> Any:
|
|
610
|
+
if agent is None:
|
|
611
|
+
return gate
|
|
612
|
+
|
|
613
|
+
value = _namespace_of(agent).get(
|
|
614
|
+
gate.name,
|
|
615
|
+
_MISSING,
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
if value is _MISSING:
|
|
619
|
+
raise AttributeError(
|
|
620
|
+
f"{type(agent).__name__} has no visible member"
|
|
621
|
+
f" {gate.name!r} (deleted by a Tag)"
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
return value
|
|
625
|
+
|
|
626
|
+
def __set__(
|
|
627
|
+
gate,
|
|
628
|
+
agent: object,
|
|
629
|
+
value: Any,
|
|
630
|
+
) -> None:
|
|
631
|
+
_namespace_of(agent)[gate.name] = value
|
|
632
|
+
|
|
633
|
+
def __delete__(
|
|
634
|
+
gate,
|
|
635
|
+
agent: object,
|
|
636
|
+
) -> None:
|
|
637
|
+
_namespace_of(agent).pop(
|
|
638
|
+
gate.name,
|
|
639
|
+
None,
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
class _Secret_Gate:
|
|
644
|
+
"""A @Secret name. Resolves only while the Agent is composing."""
|
|
645
|
+
|
|
646
|
+
__slots__ = ("name",)
|
|
647
|
+
|
|
648
|
+
def __init__(
|
|
649
|
+
gate,
|
|
650
|
+
name: str,
|
|
651
|
+
) -> None:
|
|
652
|
+
gate.name = name
|
|
653
|
+
|
|
654
|
+
def _open(
|
|
655
|
+
gate,
|
|
656
|
+
agent: object,
|
|
657
|
+
) -> dict[str, Any]:
|
|
658
|
+
namespace = _namespace_of(agent)
|
|
659
|
+
|
|
660
|
+
if namespace[STATE].composing == 0:
|
|
661
|
+
raise AttributeError(
|
|
662
|
+
f"{gate.name!r} is a secret member of"
|
|
663
|
+
f" {type(agent).__name__}; it is reachable only from"
|
|
664
|
+
" its Tags' own Actions and protocols"
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
return namespace
|
|
668
|
+
|
|
669
|
+
def __get__(
|
|
670
|
+
gate,
|
|
671
|
+
agent: object,
|
|
672
|
+
owner: type | None = None,
|
|
673
|
+
) -> Any:
|
|
674
|
+
if agent is None:
|
|
675
|
+
return gate
|
|
676
|
+
|
|
677
|
+
namespace = gate._open(agent)
|
|
678
|
+
value = namespace.get(
|
|
679
|
+
gate.name,
|
|
680
|
+
_MISSING,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
if value is _MISSING:
|
|
684
|
+
raise AttributeError(
|
|
685
|
+
f"{type(agent).__name__} has no visible member"
|
|
686
|
+
f" {gate.name!r}"
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
return value
|
|
690
|
+
|
|
691
|
+
def __set__(
|
|
692
|
+
gate,
|
|
693
|
+
agent: object,
|
|
694
|
+
value: Any,
|
|
695
|
+
) -> None:
|
|
696
|
+
gate._open(agent)[gate.name] = value
|
|
697
|
+
|
|
698
|
+
def __delete__(
|
|
699
|
+
gate,
|
|
700
|
+
agent: object,
|
|
701
|
+
) -> None:
|
|
702
|
+
gate._open(agent).pop(
|
|
703
|
+
gate.name,
|
|
704
|
+
None,
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
class _Published:
|
|
709
|
+
"""A published Report: reads the Tag-scope value, read-only on the Agent."""
|
|
710
|
+
|
|
711
|
+
__slots__ = ("name",)
|
|
712
|
+
|
|
713
|
+
def __init__(
|
|
714
|
+
gate,
|
|
715
|
+
name: str,
|
|
716
|
+
) -> None:
|
|
717
|
+
gate.name = name
|
|
718
|
+
|
|
719
|
+
def __get__(
|
|
720
|
+
gate,
|
|
721
|
+
agent: object,
|
|
722
|
+
owner: type | None = None,
|
|
723
|
+
) -> Any:
|
|
724
|
+
if agent is None:
|
|
725
|
+
return gate
|
|
726
|
+
|
|
727
|
+
from .overlay import _require_membership
|
|
728
|
+
|
|
729
|
+
state = _namespace_of(agent)[STATE]
|
|
730
|
+
origin, _declared = state.reports[gate.name]
|
|
731
|
+
_require_membership(
|
|
732
|
+
agent,
|
|
733
|
+
origin,
|
|
734
|
+
gate.name,
|
|
735
|
+
"Report",
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
return getattr(
|
|
739
|
+
origin,
|
|
740
|
+
gate.name,
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
def __set__(
|
|
744
|
+
gate,
|
|
745
|
+
agent: object,
|
|
746
|
+
value: Any,
|
|
747
|
+
) -> None:
|
|
748
|
+
raise AttributeError(
|
|
749
|
+
f"{gate.name!r} is a published Report; it is read-only on"
|
|
750
|
+
" the Agent and lives on the Tag"
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
# ------------------------------------------------------------------
|
|
755
|
+
# Runtime types
|
|
756
|
+
# ------------------------------------------------------------------
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
class Tagged:
|
|
760
|
+
"""Marker base every Agent runtime type ends with."""
|
|
761
|
+
|
|
762
|
+
__slots__ = ()
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
_type_cache: "WeakValueDictionary[tuple, type]" = WeakValueDictionary()
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def _dunder_actions(
|
|
769
|
+
state: _State,
|
|
770
|
+
) -> dict[str, Function]:
|
|
771
|
+
return {
|
|
772
|
+
name: function
|
|
773
|
+
for name, function in state.actions.items()
|
|
774
|
+
if _is_dunder(name)
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def _type_key_of(
|
|
779
|
+
state: _State,
|
|
780
|
+
) -> tuple:
|
|
781
|
+
return (
|
|
782
|
+
state.host_type,
|
|
783
|
+
frozenset(state.deleted),
|
|
784
|
+
frozenset(state.secrets),
|
|
785
|
+
frozenset(state.published),
|
|
786
|
+
bool(state.postconditions),
|
|
787
|
+
any(_is_flag(tag) for tag in state.active),
|
|
788
|
+
tuple(
|
|
789
|
+
sorted(
|
|
790
|
+
(name, id(function))
|
|
791
|
+
for name, function in _dunder_actions(state).items()
|
|
792
|
+
)
|
|
793
|
+
),
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def _runtime_type_for(
|
|
798
|
+
state: _State,
|
|
799
|
+
) -> type:
|
|
800
|
+
"""The runtime type for a composition, shared across Agents that need
|
|
801
|
+
the same type-level behaviour."""
|
|
802
|
+
|
|
803
|
+
from .access import _hooks_for
|
|
804
|
+
|
|
805
|
+
key = _type_key_of(state)
|
|
806
|
+
shared = _type_cache.get(key)
|
|
807
|
+
|
|
808
|
+
if shared is not None:
|
|
809
|
+
return shared
|
|
810
|
+
|
|
811
|
+
host_type = state.host_type
|
|
812
|
+
dunders = _dunder_actions(state)
|
|
813
|
+
deleted = key[1]
|
|
814
|
+
secrets = key[2]
|
|
815
|
+
published = key[3]
|
|
816
|
+
has_posts = key[4]
|
|
817
|
+
has_flags = key[5]
|
|
818
|
+
|
|
819
|
+
namespace: dict[str, Any] = _hooks_for(
|
|
820
|
+
host_type,
|
|
821
|
+
has_posts,
|
|
822
|
+
has_flags,
|
|
823
|
+
)
|
|
824
|
+
namespace.update(dunders)
|
|
825
|
+
|
|
826
|
+
if not issubclass(host_type, type):
|
|
827
|
+
# A Tag's gates are answered on its miss path, never by a data
|
|
828
|
+
# descriptor on its metaclass (which would also intercept the
|
|
829
|
+
# class-attribute writes the kernel makes).
|
|
830
|
+
for name in deleted:
|
|
831
|
+
namespace[name] = _Deleted(name)
|
|
832
|
+
|
|
833
|
+
for name in secrets:
|
|
834
|
+
namespace[name] = _Secret_Gate(name)
|
|
835
|
+
|
|
836
|
+
for name in published:
|
|
837
|
+
namespace[name] = _Published(name)
|
|
838
|
+
|
|
839
|
+
if issubclass(host_type, Tagged):
|
|
840
|
+
bases: tuple[type, ...] = (host_type,)
|
|
841
|
+
else:
|
|
842
|
+
bases = (
|
|
843
|
+
host_type,
|
|
844
|
+
Tagged,
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
try:
|
|
848
|
+
runtime_type = type(
|
|
849
|
+
host_type.__name__,
|
|
850
|
+
bases,
|
|
851
|
+
namespace,
|
|
852
|
+
)
|
|
853
|
+
except TypeError as error:
|
|
854
|
+
raise TagCompositionError(
|
|
855
|
+
f"{host_type.__name__} cannot be actualized as an Agent"
|
|
856
|
+
) from error
|
|
857
|
+
|
|
858
|
+
runtime_type.__qualname__ = host_type.__qualname__
|
|
859
|
+
runtime_type.__module__ = host_type.__module__
|
|
860
|
+
_type_cache[key] = runtime_type
|
|
861
|
+
|
|
862
|
+
return runtime_type
|