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 ADDED
@@ -0,0 +1,82 @@
1
+ """TopKit: the Python reference implementation of Tag-Oriented Programming."""
2
+
3
+ from .contracts import Contract
4
+ from .declarations import Action
5
+ from .declarations import Delete
6
+ from .declarations import Flag
7
+ from .declarations import Imprint
8
+ from .declarations import Operation
9
+ from .declarations import Pin
10
+ from .declarations import Post
11
+ from .declarations import Postcondition
12
+ from .declarations import Pre
13
+ from .declarations import Precondition
14
+ from .declarations import Public
15
+ from .declarations import Record
16
+ from .declarations import Report
17
+ from .declarations import Requirement
18
+ from .declarations import Rip
19
+ from .declarations import Secret
20
+ from .declarations import Underlay
21
+ from .errors import TagCompositionError
22
+ from .errors import TagContractError
23
+ from .errors import TagContractWarning
24
+ from .errors import TagDeclarationError
25
+ from .errors import TagError
26
+ from .errors import TagImprintError
27
+ from .errors import TagOverwriteWarning
28
+ from .errors import TagPostconditionError
29
+ from .errors import TagPreconditionError
30
+ from .errors import TagResolutionError
31
+ from .errors import TagRogueAccessError
32
+ from .lifecycle import At_Exit
33
+ from .lifecycle import Scope
34
+ from .queries import Apply
35
+ from .queries import Form
36
+ from .queries import Keyword
37
+ from .queries import Outline
38
+ from .queries import Tags
39
+ from .tags import Tag
40
+ from .tags import Tagged
41
+
42
+
43
+ __all__ = [
44
+ "Action",
45
+ "Apply",
46
+ "At_Exit",
47
+ "Contract",
48
+ "Delete",
49
+ "Flag",
50
+ "Form",
51
+ "Keyword",
52
+ "Imprint",
53
+ "Operation",
54
+ "Outline",
55
+ "Pin",
56
+ "Post",
57
+ "Postcondition",
58
+ "Pre",
59
+ "Precondition",
60
+ "Public",
61
+ "Record",
62
+ "Report",
63
+ "Requirement",
64
+ "Rip",
65
+ "Scope",
66
+ "Secret",
67
+ "Tag",
68
+ "TagCompositionError",
69
+ "TagContractError",
70
+ "TagContractWarning",
71
+ "TagDeclarationError",
72
+ "TagError",
73
+ "TagImprintError",
74
+ "TagOverwriteWarning",
75
+ "TagPostconditionError",
76
+ "TagPreconditionError",
77
+ "TagResolutionError",
78
+ "TagRogueAccessError",
79
+ "Tagged",
80
+ "Tags",
81
+ "Underlay",
82
+ ]
TopKit/access.py ADDED
@@ -0,0 +1,357 @@
1
+ """Access: the hooks on the runtime type and the Agent-bound Tag views.
2
+
3
+ Three access forms, three meanings:
4
+
5
+ agent.name the current visible Overlay (Agent scope)
6
+ agent.Wizard.name the Overlay as it was right after Wizard applied
7
+ Wizard.name the Tag itself (Tag scope)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from functools import partial
13
+ from typing import Any
14
+
15
+ from .errors import TagCompositionError
16
+ from .errors import TagResolutionError
17
+ from .declarations import _MISSING
18
+ from .state import _Bound
19
+ from .state import _Pinned_Operation
20
+ from .state import _Snapshot
21
+ from .state import _State
22
+ from .state import _name_of
23
+ from .state import _state_of
24
+
25
+
26
+ # ------------------------------------------------------------------
27
+ # Hooks placed on every runtime type
28
+ # ------------------------------------------------------------------
29
+
30
+
31
+ def _host_member(
32
+ host_type: type,
33
+ name: str,
34
+ ) -> Any:
35
+ """A special method the host itself defines, or None."""
36
+
37
+ for klass in host_type.__mro__:
38
+ if klass is object:
39
+ break
40
+
41
+ member = klass.__dict__.get(name)
42
+
43
+ if member is not None:
44
+ return member
45
+
46
+ return None
47
+
48
+
49
+ def _hooks_for(
50
+ host_type: type,
51
+ has_posts: bool,
52
+ has_flags: bool,
53
+ ) -> dict[str, Any]:
54
+ hooks: dict[str, Any] = {
55
+ "__getattr__": _agent_getattr,
56
+ "__del__": _agent_del,
57
+ "_TOPKIT_HOST_TYPE": host_type,
58
+ "_TOPKIT_HOST_GETATTR": _host_member(host_type, "__getattr__"),
59
+ "_TOPKIT_HOST_DEL": _host_member(host_type, "__del__"),
60
+ }
61
+
62
+ if has_posts and _host_member(host_type, "__bool__") is None:
63
+ hooks["__bool__"] = _agent_bool
64
+
65
+ if _host_member(host_type, "__format__") is None:
66
+ hooks["__format__"] = _agent_format
67
+
68
+ if has_flags and _host_member(host_type, "__contains__") is None:
69
+ hooks["__contains__"] = _agent_contains
70
+
71
+ if _host_member(host_type, "__copy__") is None:
72
+ hooks["__copy__"] = _agent_copy
73
+
74
+ if _host_member(host_type, "__deepcopy__") is None:
75
+ hooks["__deepcopy__"] = _agent_deepcopy
76
+
77
+ return hooks
78
+
79
+
80
+ def _agent_getattr(
81
+ agent: object,
82
+ name: str,
83
+ ) -> Any:
84
+ """Miss path only: Tag views by name, then the host's own __getattr__."""
85
+
86
+ state = _state_of(agent)
87
+
88
+ if state is not None:
89
+ if name in state.secrets and state.pinned is not None:
90
+ return _secret_of_tag(
91
+ agent,
92
+ state,
93
+ name,
94
+ )
95
+
96
+ for tag in reversed(state.active):
97
+ if tag.__name__ == name:
98
+ return _view_of(
99
+ agent,
100
+ tag,
101
+ state,
102
+ )
103
+
104
+ host_getattr = type(agent).__dict__.get("_TOPKIT_HOST_GETATTR")
105
+
106
+ if host_getattr is not None:
107
+ return host_getattr(
108
+ agent,
109
+ name,
110
+ )
111
+
112
+ raise AttributeError(
113
+ f"{_name_of(agent)} has no member {name!r}"
114
+ )
115
+
116
+
117
+ def _secret_of_tag(
118
+ tag: type,
119
+ state: _State,
120
+ name: str,
121
+ ) -> Any:
122
+ """A Pin's @Secret member on a Tag: held in the state, resolved only
123
+ while the Tag's own protocols or pinned Operations run."""
124
+
125
+ if state.composing == 0:
126
+ raise AttributeError(
127
+ f"{name!r} is a secret member of {tag.__name__}; it is"
128
+ " reachable only from its Pins' own Actions and protocols"
129
+ )
130
+
131
+ value = state.secret_values.get(
132
+ name,
133
+ _MISSING,
134
+ )
135
+
136
+ if value is _MISSING:
137
+ raise AttributeError(
138
+ f"{tag.__name__} has no visible member {name!r}"
139
+ )
140
+
141
+ if isinstance(value, _Pinned_Operation):
142
+ return value.__get__(
143
+ None,
144
+ tag,
145
+ )
146
+
147
+ return value
148
+
149
+
150
+ def _agent_format(
151
+ agent: object,
152
+ spec: str,
153
+ ) -> str:
154
+ """``f"{agent:tags}"``, ``f"{agent:outline}"``, ``f"{agent:contract}"``."""
155
+
156
+ if spec == "":
157
+ return str(agent)
158
+
159
+ if spec == "tags":
160
+ from .queries import Tags
161
+
162
+ return ", ".join(
163
+ tag.__name__
164
+ for tag in Tags(agent)
165
+ )
166
+
167
+ if spec == "outline":
168
+ from .queries import Outline
169
+
170
+ return Outline(agent)
171
+
172
+ if spec == "contract":
173
+ from .contracts import Contract
174
+
175
+ return Contract.Display(agent)
176
+
177
+ raise ValueError(
178
+ f"unknown format spec {spec!r} for an Agent; use 'tags',"
179
+ " 'outline', or 'contract'"
180
+ )
181
+
182
+
183
+ def _agent_contains(
184
+ agent: object,
185
+ probe: object,
186
+ ) -> bool:
187
+ """``"Undead" in ghoul`` and ``Undead in ghoul``: an active Flag, by
188
+ name or by class."""
189
+
190
+ return _keyword(
191
+ agent,
192
+ probe,
193
+ )
194
+
195
+
196
+ def _keyword(
197
+ agent: object,
198
+ probe: object,
199
+ ) -> bool:
200
+ from .declarations import _is_flag
201
+
202
+ state = _state_of(agent)
203
+
204
+ if state is None:
205
+ return False
206
+
207
+ if isinstance(probe, str):
208
+ return any(
209
+ _is_flag(tag) and tag.__name__ == probe
210
+ for tag in state.active
211
+ )
212
+
213
+ return (
214
+ probe in state.active
215
+ and _is_flag(probe)
216
+ )
217
+
218
+
219
+ def _agent_bool(
220
+ agent: object,
221
+ ) -> bool:
222
+ from .contracts import _holds
223
+
224
+ return _holds(agent)
225
+
226
+
227
+ def _agent_del(
228
+ agent: object,
229
+ ) -> None:
230
+ # Best effort: run remaining teardowns, then the host's finalizer.
231
+ # Python does not promise finalizers at shutdown or inside cycles;
232
+ # Scope() is the guaranteed path.
233
+ try:
234
+ from .lifecycle import _teardown_all
235
+
236
+ _teardown_all(agent)
237
+
238
+ host_del = type(agent).__dict__.get("_TOPKIT_HOST_DEL")
239
+
240
+ if host_del is not None:
241
+ host_del(agent)
242
+ except Exception:
243
+ pass
244
+
245
+
246
+ def _agent_copy(
247
+ agent: object,
248
+ ) -> object:
249
+ raise TagCompositionError(
250
+ "copying an Agent is domain work: build a new Target and apply"
251
+ " its Tags again (Tags(agent) lists them)"
252
+ )
253
+
254
+
255
+ def _agent_deepcopy(
256
+ agent: object,
257
+ memo: dict[int, Any],
258
+ ) -> object:
259
+ return _agent_copy(agent)
260
+
261
+
262
+ # ------------------------------------------------------------------
263
+ # Agent-bound Tag views
264
+ # ------------------------------------------------------------------
265
+
266
+
267
+ class _Tag_View:
268
+ """The Overlay snapshot captured right after one Tag applied."""
269
+
270
+ __slots__ = (
271
+ "_agent",
272
+ "_tag",
273
+ "_snapshot",
274
+ )
275
+
276
+ def __init__(
277
+ view,
278
+ agent: object,
279
+ tag: type,
280
+ snapshot: _Snapshot,
281
+ ) -> None:
282
+ object.__setattr__(view, "_agent", agent)
283
+ object.__setattr__(view, "_tag", tag)
284
+ object.__setattr__(view, "_snapshot", snapshot)
285
+
286
+ def __getattr__(
287
+ view,
288
+ name: str,
289
+ ) -> Any:
290
+ snapshot: _Snapshot = view._snapshot
291
+ agent = view._agent
292
+
293
+ if name in snapshot.deleted:
294
+ raise AttributeError(
295
+ f"{view._tag.__name__} deleted {name!r}"
296
+ )
297
+
298
+ if name in snapshot.secrets and _state_of(agent).composing == 0:
299
+ raise AttributeError(
300
+ f"{name!r} is a secret member of {view._tag.__name__}"
301
+ )
302
+
303
+ if name in snapshot.actions:
304
+ return _Bound(
305
+ snapshot.actions[name],
306
+ agent,
307
+ )
308
+
309
+ if name in snapshot.records:
310
+ return snapshot.records[name]
311
+
312
+ if name in snapshot.reports:
313
+ return snapshot.reports[name][1]
314
+
315
+ if name in snapshot.operations:
316
+ origin, operation = snapshot.operations[name]
317
+
318
+ return partial(
319
+ operation,
320
+ origin,
321
+ )
322
+
323
+ raise AttributeError(
324
+ f"{view._tag.__name__} view has no member {name!r}"
325
+ )
326
+
327
+ def __setattr__(
328
+ view,
329
+ name: str,
330
+ value: Any,
331
+ ) -> None:
332
+ raise AttributeError("a Tag view is a snapshot; it is read-only")
333
+
334
+ def __repr__(
335
+ view,
336
+ ) -> str:
337
+ return f"<{view._tag.__name__} view of {_name_of(view._agent)}>"
338
+
339
+
340
+ def _view_of(
341
+ agent: object,
342
+ tag: type,
343
+ state: _State | None = None,
344
+ ) -> _Tag_View:
345
+ if state is None:
346
+ state = _state_of(agent)
347
+
348
+ if state is None or tag not in state.active:
349
+ raise TagResolutionError(
350
+ f"{tag.__name__} is not active on this Agent"
351
+ )
352
+
353
+ return _Tag_View(
354
+ agent,
355
+ tag,
356
+ state.snapshots[tag],
357
+ )