topkit 0.2.0a3__tar.gz → 0.2.0a4__tar.gz
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-0.2.0a3/topkit.egg-info → topkit-0.2.0a4}/PKG-INFO +33 -21
- topkit-0.2.0a4/README.md +127 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/access.py +166 -23
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/contracts.py +68 -7
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/declarations.py +108 -10
- topkit-0.2.0a4/TopKit/fields.py +386 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/geometry.py +15 -9
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/lifecycle.py +66 -29
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/overlay.py +277 -22
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/queries.py +4 -4
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/state.py +53 -10
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/tags.py +48 -2
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/transactions.py +88 -17
- {topkit-0.2.0a3 → topkit-0.2.0a4}/pyproject.toml +2 -4
- topkit-0.2.0a4/tests/test_differential_fuzz.py +52 -0
- topkit-0.2.0a4/tests/test_documents.py +71 -0
- topkit-0.2.0a4/tests/test_oracle.py +34 -0
- topkit-0.2.0a4/tests/test_performance.py +263 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/tests/test_topkit.py +2179 -5
- {topkit-0.2.0a3 → topkit-0.2.0a4/topkit.egg-info}/PKG-INFO +33 -21
- {topkit-0.2.0a3 → topkit-0.2.0a4}/topkit.egg-info/SOURCES.txt +4 -0
- topkit-0.2.0a3/README.md +0 -113
- topkit-0.2.0a3/TopKit/fields.py +0 -165
- {topkit-0.2.0a3 → topkit-0.2.0a4}/LICENSE +0 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/__init__.py +0 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/TopKit/errors.py +0 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/setup.cfg +0 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/tests/test_examples.py +0 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/topkit.egg-info/dependency_links.txt +0 -0
- {topkit-0.2.0a3 → topkit-0.2.0a4}/topkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topkit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.0a4
|
|
4
4
|
Summary: TopKit: the Python reference implementation of Tag-Oriented Programming.
|
|
5
5
|
Author: Julio Toboso
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -11,12 +11,10 @@ Keywords: tag-oriented-programming,top,topkit,tags
|
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.12
|
|
20
18
|
Description-Content-Type: text/markdown
|
|
21
19
|
License-File: LICENSE
|
|
22
20
|
Dynamic: license-file
|
|
@@ -76,38 +74,52 @@ assert ari.Attack() == "Ari casts Magic Missile while holding a shield"
|
|
|
76
74
|
assert ari.Wizard.Attack() == "Ari casts Magic Missile" # the view after Wizard
|
|
77
75
|
```
|
|
78
76
|
|
|
79
|
-
Read [`TopKit/GUIDE.md`](TopKit/GUIDE.md) for the patterns, and run
|
|
77
|
+
Read [`TopKit/GUIDE.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TopKit/GUIDE.md) for the patterns, and run
|
|
80
78
|
`examples/dnd_character.py` and `examples/biome.py` for the long form;
|
|
81
79
|
`examples/fleet_patching.py` and `examples/crew_access.py` for the design
|
|
82
80
|
patterns Pins and published members give you.
|
|
83
81
|
|
|
82
|
+
## Installing
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
pip install topkit
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
TopKit is in alpha. While no final release exists, a plain
|
|
89
|
+
`pip install topkit` installs the newest alpha. Once a final release is
|
|
90
|
+
out, it installs that, and `pip install --pre topkit` asks for the newest
|
|
91
|
+
alpha.
|
|
92
|
+
|
|
84
93
|
## This repository
|
|
85
94
|
|
|
86
95
|
| Path | What | License |
|
|
87
96
|
| --- | --- | --- |
|
|
88
|
-
| [`spec/SPECIFICATION.md`](spec/SPECIFICATION.md) | **The Specification**, written in rings from the kernel outward. The source of truth. | CC-BY-4.0 |
|
|
89
|
-
| [`TopKit/GUIDE.md`](TopKit/GUIDE.md) | **The Guide**: TOP for people, pattern by pattern. Start here. | Apache-2.0 |
|
|
90
|
-
| [`TopKit/`](TopKit
|
|
91
|
-
| [`TopKit/CONTRACTS.md`](TopKit/CONTRACTS.md) | **The Contracts Guide**: gates, promises, membership and error control, aboard a starship. | Apache-2.0 |
|
|
92
|
-
| [`
|
|
93
|
-
| [`
|
|
94
|
-
| [`
|
|
95
|
-
| [`
|
|
96
|
-
| [`
|
|
97
|
+
| [`spec/SPECIFICATION.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/spec/SPECIFICATION.md) | **The Specification**, written in rings from the kernel outward. The source of truth. | CC-BY-4.0 |
|
|
98
|
+
| [`TopKit/GUIDE.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TopKit/GUIDE.md) | **The Guide**: TOP for people, pattern by pattern. Start here. | Apache-2.0 |
|
|
99
|
+
| [`TopKit/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/TopKit) | **TopKit**, the Python reference implementation (the T is the Tag in T.O.P.). | Apache-2.0 |
|
|
100
|
+
| [`TopKit/CONTRACTS.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TopKit/CONTRACTS.md) | **The Contracts Guide**: gates, promises, membership and error control, aboard a starship. | Apache-2.0 |
|
|
101
|
+
| [`TopKit/FIELDS.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TopKit/FIELDS.md) | **The Fields Guide**: populations, partitions and the algebra between them. | Apache-2.0 |
|
|
102
|
+
| [`tests/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/tests) | The conformance suite, the examples and every document block, the oracle (an independent model of the paradigm checked against the kit on a random walk), and the differential fuzzer (the same random programs on two versions of the kit, transcripts compared). | Apache-2.0 |
|
|
103
|
+
| [`examples/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/examples) | A D&D character sheet, a mix-and-match biome, a drone fleet patched through Pins, a starship crew under published members. Each is a set of design patterns. | Apache-2.0 |
|
|
104
|
+
| [`benchmarks/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/benchmarks) | The runtime budget: reads, calls, tagging, memory. | Apache-2.0 |
|
|
105
|
+
| [`steps/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/steps) | **STEP**s, Standard TOP Enhancement Proposals. | CC-BY-4.0 |
|
|
106
|
+
| [`RELEASING.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/RELEASING.md) | How TopKit reaches PyPI, and what to check first. | Apache-2.0 |
|
|
97
107
|
|
|
98
108
|
The **Specification is the source of truth.** TopKit demonstrates it and
|
|
99
109
|
must perform as the Specification describes; any gap in TopKit is TopKit's
|
|
100
110
|
to fix, not a change to TOP.
|
|
101
111
|
|
|
102
|
-
## Using TopKit
|
|
112
|
+
## Using TopKit from a checkout
|
|
113
|
+
|
|
114
|
+
From a checkout of this repository:
|
|
103
115
|
|
|
104
116
|
```
|
|
105
117
|
pip install . # or: PYTHONPATH=. python3 ...
|
|
106
|
-
PYTHONPATH=. python3 -m unittest tests
|
|
118
|
+
PYTHONPATH=. python3 -m unittest discover -s tests -t .
|
|
107
119
|
PYTHONPATH=. python3 benchmarks/bench.py
|
|
108
120
|
```
|
|
109
121
|
|
|
110
|
-
Python 3.
|
|
122
|
+
Python 3.12 or later, no dependencies. TopKit is built so that an Agent's
|
|
111
123
|
attribute reads and Action calls cost what they cost on a plain object;
|
|
112
124
|
tagging is the slower, rarer act.
|
|
113
125
|
|
|
@@ -115,22 +127,22 @@ tagging is the slower, rarer act.
|
|
|
115
127
|
|
|
116
128
|
An implementation in any language is welcome. "TOP-conformant" means it
|
|
117
129
|
preserves the observable semantics in the Specification, ring by ring, and
|
|
118
|
-
passes the conformance suite. See [`CONFORMANCE.md`](CONFORMANCE.md). The
|
|
130
|
+
passes the conformance suite. See [`CONFORMANCE.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/CONFORMANCE.md). The
|
|
119
131
|
**TOP Verified** mark is granted by the steward, so the standard stays
|
|
120
132
|
meaningful.
|
|
121
133
|
|
|
122
134
|
## Governance & contributing
|
|
123
135
|
|
|
124
136
|
TOP is led by a **Director**, with a path to shared governance; see
|
|
125
|
-
[`GOVERNANCE.md`](GOVERNANCE.md). Propose changes through the **STEP**
|
|
126
|
-
process ([`CONTRIBUTING.md`](CONTRIBUTING.md)); every decision is recorded,
|
|
137
|
+
[`GOVERNANCE.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/GOVERNANCE.md). Propose changes through the **STEP**
|
|
138
|
+
process ([`CONTRIBUTING.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/CONTRIBUTING.md)); every decision is recorded,
|
|
127
139
|
in the open, with its reason.
|
|
128
140
|
|
|
129
141
|
## License & trademark
|
|
130
142
|
|
|
131
143
|
Code is **Apache-2.0**; the Specification and STEPs are **CC-BY-4.0**.
|
|
132
144
|
"Tag-Oriented Programming", "TOP", and "TopKit" are trademarks; see
|
|
133
|
-
[`TRADEMARK.md`](TRADEMARK.md). You may implement the paradigm freely; the
|
|
145
|
+
[`TRADEMARK.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TRADEMARK.md). You may implement the paradigm freely; the
|
|
134
146
|
marks identify the official spec and conformant implementations.
|
|
135
147
|
|
|
136
148
|
© 2026 Julio Toboso.
|
topkit-0.2.0a4/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Tag-Oriented Programming (TOP)™
|
|
2
|
+
|
|
3
|
+
> Compose semantic layers on one stable object identity.
|
|
4
|
+
|
|
5
|
+
**TOP** is a programming paradigm: a Target keeps its identity while Tags
|
|
6
|
+
add meaning, species, roles, backgrounds, capabilities, that cut across
|
|
7
|
+
ordinary class hierarchies. Where traditional code asks *"what is this
|
|
8
|
+
object?"*, TOP asks *"what is this object for, here?"*, and lets the answer
|
|
9
|
+
grow. The inspiration is tabletop character creation: species, class,
|
|
10
|
+
background and feats are independent choices, and the character sheet is
|
|
11
|
+
what they compose.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from TopKit import Tag, Record, Action, Underlay, Pre
|
|
15
|
+
|
|
16
|
+
class Character:
|
|
17
|
+
def __init__(self, name, level):
|
|
18
|
+
self.name, self.level = name, level
|
|
19
|
+
|
|
20
|
+
class Elf(Tag):
|
|
21
|
+
@Record
|
|
22
|
+
def spells(agent, stored): # piles up with other Tags
|
|
23
|
+
return (stored or []) + ["Light"]
|
|
24
|
+
|
|
25
|
+
class Wizard(Tag):
|
|
26
|
+
@Pre
|
|
27
|
+
def Can_Study(agent):
|
|
28
|
+
return agent.level >= 1
|
|
29
|
+
|
|
30
|
+
@Record
|
|
31
|
+
def spells(agent, stored):
|
|
32
|
+
return (stored or []) + ["Magic Missile"]
|
|
33
|
+
|
|
34
|
+
def Attack(agent):
|
|
35
|
+
return f"{agent.name} casts {agent.spells[-1]}"
|
|
36
|
+
|
|
37
|
+
class War_Caster(Tag):
|
|
38
|
+
@Pre
|
|
39
|
+
def Is_A_Caster(agent): # synergy: needs Wizard
|
|
40
|
+
return agent in Wizard
|
|
41
|
+
|
|
42
|
+
@Action
|
|
43
|
+
@Underlay
|
|
44
|
+
def Attack(agent, underlay):
|
|
45
|
+
return underlay() + " while holding a shield"
|
|
46
|
+
|
|
47
|
+
ari = Character("Ari", level=3)
|
|
48
|
+
Elf(ari); Wizard(ari); War_Caster(ari)
|
|
49
|
+
|
|
50
|
+
assert ari in Wizard # active membership
|
|
51
|
+
assert ari.spells == ["Light", "Magic Missile"]
|
|
52
|
+
assert ari.Attack() == "Ari casts Magic Missile while holding a shield"
|
|
53
|
+
assert ari.Wizard.Attack() == "Ari casts Magic Missile" # the view after Wizard
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Read [`TopKit/GUIDE.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TopKit/GUIDE.md) for the patterns, and run
|
|
57
|
+
`examples/dnd_character.py` and `examples/biome.py` for the long form;
|
|
58
|
+
`examples/fleet_patching.py` and `examples/crew_access.py` for the design
|
|
59
|
+
patterns Pins and published members give you.
|
|
60
|
+
|
|
61
|
+
## Installing
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
pip install topkit
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
TopKit is in alpha. While no final release exists, a plain
|
|
68
|
+
`pip install topkit` installs the newest alpha. Once a final release is
|
|
69
|
+
out, it installs that, and `pip install --pre topkit` asks for the newest
|
|
70
|
+
alpha.
|
|
71
|
+
|
|
72
|
+
## This repository
|
|
73
|
+
|
|
74
|
+
| Path | What | License |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| [`spec/SPECIFICATION.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/spec/SPECIFICATION.md) | **The Specification**, written in rings from the kernel outward. The source of truth. | CC-BY-4.0 |
|
|
77
|
+
| [`TopKit/GUIDE.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TopKit/GUIDE.md) | **The Guide**: TOP for people, pattern by pattern. Start here. | Apache-2.0 |
|
|
78
|
+
| [`TopKit/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/TopKit) | **TopKit**, the Python reference implementation (the T is the Tag in T.O.P.). | Apache-2.0 |
|
|
79
|
+
| [`TopKit/CONTRACTS.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TopKit/CONTRACTS.md) | **The Contracts Guide**: gates, promises, membership and error control, aboard a starship. | Apache-2.0 |
|
|
80
|
+
| [`TopKit/FIELDS.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TopKit/FIELDS.md) | **The Fields Guide**: populations, partitions and the algebra between them. | Apache-2.0 |
|
|
81
|
+
| [`tests/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/tests) | The conformance suite, the examples and every document block, the oracle (an independent model of the paradigm checked against the kit on a random walk), and the differential fuzzer (the same random programs on two versions of the kit, transcripts compared). | Apache-2.0 |
|
|
82
|
+
| [`examples/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/examples) | A D&D character sheet, a mix-and-match biome, a drone fleet patched through Pins, a starship crew under published members. Each is a set of design patterns. | Apache-2.0 |
|
|
83
|
+
| [`benchmarks/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/benchmarks) | The runtime budget: reads, calls, tagging, memory. | Apache-2.0 |
|
|
84
|
+
| [`steps/`](https://github.com/JulTob/Tag_Oriented_Programming/tree/main/steps) | **STEP**s, Standard TOP Enhancement Proposals. | CC-BY-4.0 |
|
|
85
|
+
| [`RELEASING.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/RELEASING.md) | How TopKit reaches PyPI, and what to check first. | Apache-2.0 |
|
|
86
|
+
|
|
87
|
+
The **Specification is the source of truth.** TopKit demonstrates it and
|
|
88
|
+
must perform as the Specification describes; any gap in TopKit is TopKit's
|
|
89
|
+
to fix, not a change to TOP.
|
|
90
|
+
|
|
91
|
+
## Using TopKit from a checkout
|
|
92
|
+
|
|
93
|
+
From a checkout of this repository:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
pip install . # or: PYTHONPATH=. python3 ...
|
|
97
|
+
PYTHONPATH=. python3 -m unittest discover -s tests -t .
|
|
98
|
+
PYTHONPATH=. python3 benchmarks/bench.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Python 3.12 or later, no dependencies. TopKit is built so that an Agent's
|
|
102
|
+
attribute reads and Action calls cost what they cost on a plain object;
|
|
103
|
+
tagging is the slower, rarer act.
|
|
104
|
+
|
|
105
|
+
## Conformance
|
|
106
|
+
|
|
107
|
+
An implementation in any language is welcome. "TOP-conformant" means it
|
|
108
|
+
preserves the observable semantics in the Specification, ring by ring, and
|
|
109
|
+
passes the conformance suite. See [`CONFORMANCE.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/CONFORMANCE.md). The
|
|
110
|
+
**TOP Verified** mark is granted by the steward, so the standard stays
|
|
111
|
+
meaningful.
|
|
112
|
+
|
|
113
|
+
## Governance & contributing
|
|
114
|
+
|
|
115
|
+
TOP is led by a **Director**, with a path to shared governance; see
|
|
116
|
+
[`GOVERNANCE.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/GOVERNANCE.md). Propose changes through the **STEP**
|
|
117
|
+
process ([`CONTRIBUTING.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/CONTRIBUTING.md)); every decision is recorded,
|
|
118
|
+
in the open, with its reason.
|
|
119
|
+
|
|
120
|
+
## License & trademark
|
|
121
|
+
|
|
122
|
+
Code is **Apache-2.0**; the Specification and STEPs are **CC-BY-4.0**.
|
|
123
|
+
"Tag-Oriented Programming", "TOP", and "TopKit" are trademarks; see
|
|
124
|
+
[`TRADEMARK.md`](https://github.com/JulTob/Tag_Oriented_Programming/blob/main/TRADEMARK.md). You may implement the paradigm freely; the
|
|
125
|
+
marks identify the official spec and conformant implementations.
|
|
126
|
+
|
|
127
|
+
© 2026 Julio Toboso.
|
|
@@ -11,10 +11,19 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
from functools import partial
|
|
13
13
|
from typing import Any
|
|
14
|
+
from typing import Callable
|
|
15
|
+
import sys
|
|
14
16
|
|
|
17
|
+
from . import declarations
|
|
18
|
+
from .contracts import _condition_member
|
|
19
|
+
from .contracts import _holds
|
|
15
20
|
from .errors import TagCompositionError
|
|
16
21
|
from .errors import TagResolutionError
|
|
22
|
+
from .declarations import STATE
|
|
17
23
|
from .declarations import _MISSING
|
|
24
|
+
from .declarations import _is_flag
|
|
25
|
+
from .declarations import _words_of
|
|
26
|
+
from .lifecycle import _teardown_all
|
|
18
27
|
from .state import _Bound
|
|
19
28
|
from .state import _Pinned_Operation
|
|
20
29
|
from .state import _Snapshot
|
|
@@ -46,6 +55,43 @@ def _host_member(
|
|
|
46
55
|
return None
|
|
47
56
|
|
|
48
57
|
|
|
58
|
+
IN_SEAT = (
|
|
59
|
+
"__contains__",
|
|
60
|
+
"__iter__",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _host_in_seat(
|
|
65
|
+
host_type: type,
|
|
66
|
+
) -> tuple[str, type] | None:
|
|
67
|
+
"""How the host answers `in`: the special method and the class that
|
|
68
|
+
defines it, or None when the seat is free (STEP-SPEC-7).
|
|
69
|
+
|
|
70
|
+
Resolved as Python resolves `in`: ``__contains__``, then ``__iter__``.
|
|
71
|
+
For each, the first class in the MRO that defines the name decides. A
|
|
72
|
+
method owns the seat; the value None says `in` is unavailable, Python
|
|
73
|
+
tries nothing further, and the seat is free. ``__getitem__`` alone is
|
|
74
|
+
not a seat a Flag respects: on a keyed host its `in` fails or never
|
|
75
|
+
ends; on an index-style host it worked, and stops meaning membership
|
|
76
|
+
once a Flag lands (the Director's call, STEP-SPEC-7)."""
|
|
77
|
+
|
|
78
|
+
for name in IN_SEAT:
|
|
79
|
+
for klass in host_type.__mro__:
|
|
80
|
+
if klass is object:
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
if name in klass.__dict__:
|
|
84
|
+
if klass.__dict__[name] is None:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
name,
|
|
89
|
+
klass,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
49
95
|
def _hooks_for(
|
|
50
96
|
host_type: type,
|
|
51
97
|
has_posts: bool,
|
|
@@ -56,7 +102,6 @@ def _hooks_for(
|
|
|
56
102
|
"__del__": _agent_del,
|
|
57
103
|
"_TOPKIT_HOST_TYPE": host_type,
|
|
58
104
|
"_TOPKIT_HOST_GETATTR": _host_member(host_type, "__getattr__"),
|
|
59
|
-
"_TOPKIT_HOST_DEL": _host_member(host_type, "__del__"),
|
|
60
105
|
}
|
|
61
106
|
|
|
62
107
|
if has_posts and _host_member(host_type, "__bool__") is None:
|
|
@@ -65,7 +110,7 @@ def _hooks_for(
|
|
|
65
110
|
if _host_member(host_type, "__format__") is None:
|
|
66
111
|
hooks["__format__"] = _agent_format
|
|
67
112
|
|
|
68
|
-
if has_flags and
|
|
113
|
+
if has_flags and _host_in_seat(host_type) is None:
|
|
69
114
|
hooks["__contains__"] = _agent_contains
|
|
70
115
|
|
|
71
116
|
if _host_member(host_type, "__copy__") is None:
|
|
@@ -81,7 +126,8 @@ def _agent_getattr(
|
|
|
81
126
|
agent: object,
|
|
82
127
|
name: str,
|
|
83
128
|
) -> Any:
|
|
84
|
-
"""Miss path only: Tag views by name, then
|
|
129
|
+
"""Miss path only: Tag views by name, then a condition by name as a
|
|
130
|
+
plain bool (STEP-SPEC-14), then the host's own __getattr__."""
|
|
85
131
|
|
|
86
132
|
state = _state_of(agent)
|
|
87
133
|
|
|
@@ -101,6 +147,16 @@ def _agent_getattr(
|
|
|
101
147
|
state,
|
|
102
148
|
)
|
|
103
149
|
|
|
150
|
+
if state.postconditions or state.preconditions:
|
|
151
|
+
verdict = _condition_member(
|
|
152
|
+
agent,
|
|
153
|
+
state,
|
|
154
|
+
name,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if verdict is not None:
|
|
158
|
+
return verdict
|
|
159
|
+
|
|
104
160
|
host_getattr = type(agent).__dict__.get("_TOPKIT_HOST_GETATTR")
|
|
105
161
|
|
|
106
162
|
if host_getattr is not None:
|
|
@@ -185,7 +241,7 @@ def _agent_contains(
|
|
|
185
241
|
probe: object,
|
|
186
242
|
) -> bool:
|
|
187
243
|
"""``"Undead" in ghoul`` and ``Undead in ghoul``: an active Flag, by
|
|
188
|
-
name or
|
|
244
|
+
name, alias or class."""
|
|
189
245
|
|
|
190
246
|
return _keyword(
|
|
191
247
|
agent,
|
|
@@ -197,18 +253,33 @@ def _keyword(
|
|
|
197
253
|
agent: object,
|
|
198
254
|
probe: object,
|
|
199
255
|
) -> bool:
|
|
200
|
-
from .declarations import _is_flag
|
|
201
|
-
|
|
202
256
|
state = _state_of(agent)
|
|
203
257
|
|
|
204
258
|
if state is None:
|
|
205
259
|
return False
|
|
206
260
|
|
|
207
261
|
if isinstance(probe, str):
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
262
|
+
if type(probe) is not str:
|
|
263
|
+
probe = str.__str__(probe) # its text: exact, and hashable
|
|
264
|
+
|
|
265
|
+
words = state.words
|
|
266
|
+
|
|
267
|
+
if words is None or words.flags_declared != declarations._flags_declared:
|
|
268
|
+
# Read the count before gathering: a @Flag declared meanwhile then
|
|
269
|
+
# marks these words stale, instead of letting them pass for new.
|
|
270
|
+
words = state.words = _words_of(
|
|
271
|
+
state.active,
|
|
272
|
+
declarations._flags_declared,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if probe in words.aliases:
|
|
276
|
+
return True
|
|
277
|
+
|
|
278
|
+
for tag in words.flags:
|
|
279
|
+
if tag.__name__ == probe:
|
|
280
|
+
return True
|
|
281
|
+
|
|
282
|
+
return False
|
|
212
283
|
|
|
213
284
|
return (
|
|
214
285
|
probe in state.active
|
|
@@ -219,28 +290,100 @@ def _keyword(
|
|
|
219
290
|
def _agent_bool(
|
|
220
291
|
agent: object,
|
|
221
292
|
) -> bool:
|
|
222
|
-
from .contracts import _holds
|
|
223
|
-
|
|
224
293
|
return _holds(agent)
|
|
225
294
|
|
|
226
295
|
|
|
227
|
-
def
|
|
296
|
+
def _host_finalizer(
|
|
228
297
|
agent: object,
|
|
298
|
+
kind: type = type,
|
|
299
|
+
missing: type = AttributeError,
|
|
229
300
|
) -> None:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
301
|
+
"""The host's own ``__del__``, found as Python finds it: through the
|
|
302
|
+
object's type, past the kit's runtime types, the first class that
|
|
303
|
+
defines one decides, ``None`` there means there is none, and a
|
|
304
|
+
descriptor is bound first.
|
|
305
|
+
|
|
306
|
+
It also runs late in interpreter exit, when this module's globals and
|
|
307
|
+
even the builtins may be gone, so it needs neither: ``kind`` and
|
|
308
|
+
``missing`` are bound here."""
|
|
309
|
+
|
|
310
|
+
for klass in kind(agent).__mro__:
|
|
311
|
+
if "_TOPKIT_HOST_TYPE" in klass.__dict__:
|
|
312
|
+
continue # a runtime type: its __del__ is the kit's finalizer
|
|
313
|
+
|
|
314
|
+
if "__del__" in klass.__dict__:
|
|
315
|
+
finalizer = klass.__dict__["__del__"]
|
|
316
|
+
break
|
|
317
|
+
else:
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
if finalizer is None:
|
|
321
|
+
return
|
|
322
|
+
|
|
233
323
|
try:
|
|
234
|
-
|
|
324
|
+
bind = kind(finalizer).__get__
|
|
325
|
+
except missing:
|
|
326
|
+
finalizer() # not a descriptor: Python calls it as it is
|
|
327
|
+
return
|
|
235
328
|
|
|
236
|
-
|
|
329
|
+
bind(
|
|
330
|
+
finalizer,
|
|
331
|
+
agent,
|
|
332
|
+
kind(agent),
|
|
333
|
+
)()
|
|
237
334
|
|
|
238
|
-
host_del = type(agent).__dict__.get("_TOPKIT_HOST_DEL")
|
|
239
335
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
336
|
+
def _agent_del(
|
|
337
|
+
agent: object,
|
|
338
|
+
finalizing: Callable[[], bool] = sys.is_finalizing,
|
|
339
|
+
state_key: str = STATE,
|
|
340
|
+
host_finalizer: Callable[[object], None] = _host_finalizer,
|
|
341
|
+
read: Callable[[object, str], Any] = object.__getattribute__,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""Deletion (§3.2, STEP-SPEC-18). The teardowns still due run, best
|
|
344
|
+
effort; then the Agent's ``__del__`` runs as its Overlay shows it: the
|
|
345
|
+
top Layer, which reaches the host's own through ``@Underlay``, or the
|
|
346
|
+
host's own when no Tag declares one. At interpreter exit only the
|
|
347
|
+
``__del__`` Layers run: teardowns there are At_Exit's, and opt-in. A
|
|
348
|
+
``__del__`` Layer's own error is reported as Python reports any
|
|
349
|
+
finalizer's.
|
|
350
|
+
|
|
351
|
+
At exit this module's globals, and even the builtins, may already be
|
|
352
|
+
gone. So the exit path uses neither: what it needs is bound here as a
|
|
353
|
+
default, or found on the Agent. The Agent is read as Python reads it,
|
|
354
|
+
never through the host's own ``__getattribute__``."""
|
|
355
|
+
|
|
356
|
+
state = read(agent, "__dict__").get(state_key)
|
|
357
|
+
|
|
358
|
+
if state is None:
|
|
359
|
+
host_finalizer(agent) # built from an Agent's runtime type, never tagged: a plain host
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
interrupted = None
|
|
363
|
+
|
|
364
|
+
if not finalizing():
|
|
365
|
+
try:
|
|
366
|
+
_teardown_all(agent)
|
|
367
|
+
except Exception:
|
|
368
|
+
pass # best effort (§3.2)
|
|
369
|
+
except BaseException as error:
|
|
370
|
+
interrupted = error # Ctrl-C in a teardown: the __del__ Layers still run
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
layer = state.actions.get("__del__")
|
|
374
|
+
|
|
375
|
+
if layer is not None:
|
|
376
|
+
state.composing += 1 # a Tag's Layer runs inside the composition door (§1.5)
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
layer(agent)
|
|
380
|
+
finally:
|
|
381
|
+
state.composing -= 1
|
|
382
|
+
elif "__del__" not in state.deleted:
|
|
383
|
+
host_finalizer(agent)
|
|
384
|
+
finally:
|
|
385
|
+
if interrupted is not None:
|
|
386
|
+
raise interrupted # reported after the Layers, whatever they raised
|
|
244
387
|
|
|
245
388
|
|
|
246
389
|
def _agent_copy(
|
|
@@ -70,6 +70,18 @@ def _bind_condition(
|
|
|
70
70
|
|
|
71
71
|
skip = 2 if uses_underlay else 1
|
|
72
72
|
|
|
73
|
+
if not uses_underlay and not with_inputs:
|
|
74
|
+
def Plain_Check(
|
|
75
|
+
agent: object,
|
|
76
|
+
inputs: dict[str, Any],
|
|
77
|
+
) -> Any:
|
|
78
|
+
return function(agent) # holds no prior binding: re-applying cannot pile up
|
|
79
|
+
|
|
80
|
+
return Plain_Check
|
|
81
|
+
|
|
82
|
+
if not uses_underlay:
|
|
83
|
+
prior = None # never called: do not keep the earlier binding alive
|
|
84
|
+
|
|
73
85
|
def Check(
|
|
74
86
|
agent: object,
|
|
75
87
|
inputs: dict[str, Any],
|
|
@@ -182,13 +194,62 @@ def _holds(
|
|
|
182
194
|
) -> bool:
|
|
183
195
|
"""True exactly when every visible Postcondition holds."""
|
|
184
196
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
state = _state_of(agent)
|
|
198
|
+
|
|
199
|
+
if state is None or state.checking or not state.postconditions:
|
|
200
|
+
return True # nothing promised, or asked from inside a check
|
|
201
|
+
|
|
202
|
+
state.checking = True
|
|
203
|
+
state.composing += 1
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
for name, check in state.postconditions.items():
|
|
207
|
+
try:
|
|
208
|
+
if not _verdict(check(agent, {}), name):
|
|
209
|
+
return False
|
|
210
|
+
except Exception:
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
return True
|
|
214
|
+
finally:
|
|
215
|
+
state.composing -= 1
|
|
216
|
+
state.checking = False
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _condition_member(
|
|
220
|
+
agent: object,
|
|
221
|
+
state: _State,
|
|
222
|
+
name: str,
|
|
223
|
+
) -> bool | None:
|
|
224
|
+
"""The condition called ``name`` on the Agent, read as a plain bool
|
|
225
|
+
(STEP-SPEC-14): a Postcondition first, then a Precondition. None when
|
|
226
|
+
the Agent has no condition of that name."""
|
|
227
|
+
|
|
228
|
+
check = state.postconditions.get(name)
|
|
229
|
+
|
|
230
|
+
if check is None:
|
|
231
|
+
check = state.preconditions.get(name)
|
|
232
|
+
|
|
233
|
+
if check is None:
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
reentrant = state.checking
|
|
237
|
+
state.checking = True
|
|
238
|
+
state.composing += 1
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
try:
|
|
242
|
+
return _verdict(
|
|
243
|
+
check(agent, {}),
|
|
244
|
+
name,
|
|
245
|
+
)
|
|
246
|
+
except TagContractError:
|
|
247
|
+
raise
|
|
248
|
+
except Exception:
|
|
249
|
+
return False
|
|
250
|
+
finally:
|
|
251
|
+
state.composing -= 1
|
|
252
|
+
state.checking = reentrant
|
|
192
253
|
|
|
193
254
|
|
|
194
255
|
def _status_of(
|