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/contracts.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""Contracts: Preconditions gate the incoming Agent, Postconditions
|
|
2
|
+
promise about the finished one.
|
|
3
|
+
|
|
4
|
+
A condition is strictly boolean: True or a fall-through (None) holds,
|
|
5
|
+
False fails, anything else is rejected. An assert-style body fails by
|
|
6
|
+
raising.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
from typing import Callable
|
|
13
|
+
from typing import Iterable
|
|
14
|
+
|
|
15
|
+
from .declarations import _protocol_inputs
|
|
16
|
+
from .declarations import _takes_underlay
|
|
17
|
+
from .errors import _Named
|
|
18
|
+
from .errors import TagContractError
|
|
19
|
+
from .errors import TagError
|
|
20
|
+
from .errors import TagPostconditionError
|
|
21
|
+
from .errors import TagPreconditionError
|
|
22
|
+
from .geometry import _leaves
|
|
23
|
+
from .state import _State
|
|
24
|
+
from .state import _name_of
|
|
25
|
+
from .state import _state_of
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Check = Callable[[object, dict[str, Any]], Any]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _verdict(
|
|
32
|
+
result: Any,
|
|
33
|
+
label: str,
|
|
34
|
+
) -> bool:
|
|
35
|
+
if result is True or result is None:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
if result is False:
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
raise TagContractError(
|
|
42
|
+
f"{label} returned {result!r} ({type(result).__name__}); a"
|
|
43
|
+
" condition must yield True, False, or None. TOP does not"
|
|
44
|
+
" coerce truthy / falsy values: write the comparison you mean,"
|
|
45
|
+
" such as `x != 0`, `x > 0`, or `x is not None`."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _bind_condition(
|
|
50
|
+
function: Callable[..., Any],
|
|
51
|
+
prior: Check | None,
|
|
52
|
+
with_inputs: bool,
|
|
53
|
+
) -> Check:
|
|
54
|
+
"""Bind one condition, giving it its Underlay when marked.
|
|
55
|
+
|
|
56
|
+
The Underlay is a callable reporting whether the prior condition of the
|
|
57
|
+
same name holds (True / False), so ``assert base()`` and
|
|
58
|
+
``return base() and ...`` both compose.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
uses_underlay = _takes_underlay(function)
|
|
62
|
+
|
|
63
|
+
if uses_underlay and prior is None:
|
|
64
|
+
from .errors import TagResolutionError
|
|
65
|
+
|
|
66
|
+
raise TagResolutionError(
|
|
67
|
+
f"{function.__qualname__} is @Underlay but no prior"
|
|
68
|
+
" condition of that name is visible"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
skip = 2 if uses_underlay else 1
|
|
72
|
+
|
|
73
|
+
def Check(
|
|
74
|
+
agent: object,
|
|
75
|
+
inputs: dict[str, Any],
|
|
76
|
+
) -> Any:
|
|
77
|
+
named = (
|
|
78
|
+
_protocol_inputs(function, inputs, skip)
|
|
79
|
+
if with_inputs
|
|
80
|
+
else {}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if not uses_underlay:
|
|
84
|
+
return function(
|
|
85
|
+
agent,
|
|
86
|
+
**named,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def base() -> bool:
|
|
90
|
+
try:
|
|
91
|
+
return _verdict(
|
|
92
|
+
prior(agent, inputs),
|
|
93
|
+
"underlay",
|
|
94
|
+
)
|
|
95
|
+
except Exception:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
return function(
|
|
99
|
+
agent,
|
|
100
|
+
base,
|
|
101
|
+
**named,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return Check
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _evaluate(
|
|
108
|
+
checks: Iterable[tuple[str, Check]],
|
|
109
|
+
agent: object,
|
|
110
|
+
inputs: dict[str, Any],
|
|
111
|
+
failure: _Named,
|
|
112
|
+
phase: str,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Run conditions; raise ``failure`` naming the first that does not hold."""
|
|
115
|
+
|
|
116
|
+
for name, check in checks:
|
|
117
|
+
try:
|
|
118
|
+
result = check(agent, inputs)
|
|
119
|
+
except TagContractError:
|
|
120
|
+
raise
|
|
121
|
+
except Exception as error:
|
|
122
|
+
raise failure.Named(name)(
|
|
123
|
+
f"{phase} {name!r} raised {type(error).__name__}: {error}"
|
|
124
|
+
) from error
|
|
125
|
+
|
|
126
|
+
if not _verdict(result, f"{phase} {name!r}"):
|
|
127
|
+
raise failure.Named(name)(
|
|
128
|
+
f"{phase} {name!r} failed"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _guarded(
|
|
133
|
+
agent: object,
|
|
134
|
+
scope: str,
|
|
135
|
+
detailed: bool,
|
|
136
|
+
failure: _Named,
|
|
137
|
+
phase: str,
|
|
138
|
+
) -> bool:
|
|
139
|
+
"""Run one scope of the Agent's visible conditions on demand.
|
|
140
|
+
|
|
141
|
+
Re-entrancy guarded: a nested ``bool(agent)`` inside a condition
|
|
142
|
+
answers True instead of recursing.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
state = _state_of(agent)
|
|
146
|
+
|
|
147
|
+
if state is None or state.checking:
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
state.checking = True
|
|
151
|
+
state.composing += 1
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
checks = getattr(state, scope).items()
|
|
155
|
+
|
|
156
|
+
if detailed:
|
|
157
|
+
_evaluate(
|
|
158
|
+
checks,
|
|
159
|
+
agent,
|
|
160
|
+
{},
|
|
161
|
+
failure,
|
|
162
|
+
phase,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
for name, check in checks:
|
|
168
|
+
try:
|
|
169
|
+
if not _verdict(check(agent, {}), name):
|
|
170
|
+
return False
|
|
171
|
+
except Exception:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
return True
|
|
175
|
+
finally:
|
|
176
|
+
state.composing -= 1
|
|
177
|
+
state.checking = False
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _holds(
|
|
181
|
+
agent: object,
|
|
182
|
+
) -> bool:
|
|
183
|
+
"""True exactly when every visible Postcondition holds."""
|
|
184
|
+
|
|
185
|
+
return _guarded(
|
|
186
|
+
agent,
|
|
187
|
+
"postconditions",
|
|
188
|
+
False,
|
|
189
|
+
TagPostconditionError,
|
|
190
|
+
"Postcondition",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _status_of(
|
|
195
|
+
agent: object,
|
|
196
|
+
scope: str,
|
|
197
|
+
) -> dict[str, bool]:
|
|
198
|
+
state = _state_of(agent)
|
|
199
|
+
|
|
200
|
+
if state is None:
|
|
201
|
+
return {}
|
|
202
|
+
|
|
203
|
+
reentrant = state.checking
|
|
204
|
+
state.checking = True
|
|
205
|
+
state.composing += 1
|
|
206
|
+
status: dict[str, bool] = {}
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
for name, check in getattr(state, scope).items():
|
|
210
|
+
try:
|
|
211
|
+
status[name] = _verdict(
|
|
212
|
+
check(agent, {}),
|
|
213
|
+
name,
|
|
214
|
+
)
|
|
215
|
+
except Exception:
|
|
216
|
+
status[name] = False
|
|
217
|
+
finally:
|
|
218
|
+
state.composing -= 1
|
|
219
|
+
state.checking = reentrant
|
|
220
|
+
|
|
221
|
+
return status
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _title_of(
|
|
225
|
+
agent: object,
|
|
226
|
+
) -> str:
|
|
227
|
+
state = _state_of(agent)
|
|
228
|
+
host = _name_of(agent)
|
|
229
|
+
|
|
230
|
+
if state is None or not state.active:
|
|
231
|
+
return host
|
|
232
|
+
|
|
233
|
+
leaves = ", ".join(
|
|
234
|
+
tag.__name__
|
|
235
|
+
for tag in _leaves(state.active)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return f"{host}[{leaves}]"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class Contract:
|
|
242
|
+
"""Named, on-demand contract checks for an Agent.
|
|
243
|
+
|
|
244
|
+
``bool(agent)`` is the boolean form. ``Contract`` names the culprit.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def Holds(
|
|
249
|
+
agent: object,
|
|
250
|
+
) -> bool:
|
|
251
|
+
return _holds(agent)
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def Postconditions(
|
|
255
|
+
agent: object,
|
|
256
|
+
) -> bool:
|
|
257
|
+
return _guarded(
|
|
258
|
+
agent,
|
|
259
|
+
"postconditions",
|
|
260
|
+
True,
|
|
261
|
+
TagPostconditionError,
|
|
262
|
+
"Postcondition",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
@staticmethod
|
|
266
|
+
def Preconditions(
|
|
267
|
+
agent: object,
|
|
268
|
+
) -> bool:
|
|
269
|
+
return _guarded(
|
|
270
|
+
agent,
|
|
271
|
+
"preconditions",
|
|
272
|
+
True,
|
|
273
|
+
TagPreconditionError,
|
|
274
|
+
"Precondition",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def Conditions(
|
|
279
|
+
agent: object,
|
|
280
|
+
) -> bool:
|
|
281
|
+
Contract.Preconditions(agent)
|
|
282
|
+
Contract.Postconditions(agent)
|
|
283
|
+
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
@staticmethod
|
|
287
|
+
def Delete(
|
|
288
|
+
agent: object,
|
|
289
|
+
*names: str,
|
|
290
|
+
) -> None:
|
|
291
|
+
"""End the named conditions on the Agent, explicitly.
|
|
292
|
+
|
|
293
|
+
Conditions are sticky: Rip does not remove them. A Tag whose gate
|
|
294
|
+
or promise should end with its membership deletes it here, from
|
|
295
|
+
its own ``@Rip`` protocol, one deliberate name at a time. A name
|
|
296
|
+
that is not a condition on the Agent is a Resolution Failure: an
|
|
297
|
+
author who ends a promise must be ending a real one.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
from .errors import TagResolutionError
|
|
301
|
+
|
|
302
|
+
state = _state_of(agent)
|
|
303
|
+
|
|
304
|
+
for name in names:
|
|
305
|
+
found = False
|
|
306
|
+
|
|
307
|
+
for scope in (
|
|
308
|
+
state.preconditions if state is not None else {},
|
|
309
|
+
state.postconditions if state is not None else {},
|
|
310
|
+
):
|
|
311
|
+
if name in scope:
|
|
312
|
+
del scope[name]
|
|
313
|
+
found = True
|
|
314
|
+
|
|
315
|
+
if not found:
|
|
316
|
+
raise TagResolutionError(
|
|
317
|
+
f"{name!r} is not a condition on {_name_of(agent)};"
|
|
318
|
+
" Contract.Delete ends a gate or a promise that is"
|
|
319
|
+
" there"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def Status(
|
|
324
|
+
agent: object,
|
|
325
|
+
) -> dict[str, bool]:
|
|
326
|
+
"""``{condition: holds?}`` for every visible condition, never raising."""
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
**_status_of(agent, "preconditions"),
|
|
330
|
+
**_status_of(agent, "postconditions"),
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@staticmethod
|
|
334
|
+
def Display(
|
|
335
|
+
agent: object,
|
|
336
|
+
) -> str:
|
|
337
|
+
title = _title_of(agent)
|
|
338
|
+
pre = _status_of(agent, "preconditions")
|
|
339
|
+
post = _status_of(agent, "postconditions")
|
|
340
|
+
|
|
341
|
+
if not pre and not post:
|
|
342
|
+
return f"{title}: no conditions"
|
|
343
|
+
|
|
344
|
+
lines = [f"{title} contract:"]
|
|
345
|
+
|
|
346
|
+
for heading, scope in (
|
|
347
|
+
("Pre", pre),
|
|
348
|
+
("Post", post),
|
|
349
|
+
):
|
|
350
|
+
if not scope:
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
lines.append(f" {heading}:")
|
|
354
|
+
|
|
355
|
+
for name, holds in scope.items():
|
|
356
|
+
lines.append(
|
|
357
|
+
f" {'OK' if holds else 'XX'} {name}"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
return "\n".join(lines)
|