tba-concrete-capability 0.4.0.dev2__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.
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: tba-concrete-capability
3
+ Version: 0.4.0.dev2
4
+ Summary: Neutral generic capability implementation for TBA identity.
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.10,<3.15
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Dist: tba-bases-capability (==0.4.0.dev2)
14
+ Requires-Dist: tba-contracts-identity (==0.4.0.dev2)
15
+ Requires-Dist: tba-primitives-identity (==0.4.0.dev2)
16
+ Description-Content-Type: text/markdown
17
+
18
+ # tba-concrete-capability
19
+
20
+ Layer-10 neutral `Capability`. Authors provide a `CapabilityDefinition` and one
21
+ mapping of names to `CapabilityOperation` objects. Required operations must have
22
+ callable targets at construction. Optional operations may be present without a
23
+ target and are reported as unavailable. State is supplied as a fixed
24
+ `CapabilityState` or a live state provider.
25
+
26
+ The package has no implicit providers, persistence, policies, retries, timeouts,
27
+ namespaces, or environment-derived settings.
28
+
@@ -0,0 +1,10 @@
1
+ # tba-concrete-capability
2
+
3
+ Layer-10 neutral `Capability`. Authors provide a `CapabilityDefinition` and one
4
+ mapping of names to `CapabilityOperation` objects. Required operations must have
5
+ callable targets at construction. Optional operations may be present without a
6
+ target and are reported as unavailable. State is supplied as a fixed
7
+ `CapabilityState` or a live state provider.
8
+
9
+ The package has no implicit providers, persistence, policies, retries, timeouts,
10
+ namespaces, or environment-derived settings.
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "tba-concrete-capability"
3
+ version = "0.4.0.dev2"
4
+ description = "Neutral generic capability implementation for TBA identity."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10,<3.15"
7
+ license = "Apache-2.0"
8
+ dependencies = [
9
+ "tba-bases-capability==0.4.0.dev2",
10
+ "tba-contracts-identity==0.4.0.dev2",
11
+ "tba-primitives-identity==0.4.0.dev2",
12
+ ]
13
+
14
+ [build-system]
15
+ requires = ["poetry-core>=1.0.0"]
16
+ build-backend = "poetry.core.masonry.api"
17
+
18
+ [tool.poetry]
19
+ packages = [{ include = "tba_concrete_capability", from = "src" }]
@@ -0,0 +1,205 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import time
5
+ from collections.abc import Mapping
6
+ from dataclasses import asdict, replace
7
+
8
+ from tba_bases_capability import CapabilityBase
9
+ from tba_primitives_identity.clock import utc_now_iso
10
+ from tba_primitives_identity.primitives import new_opaque_id, new_prefixed_id
11
+ from tba_contracts_identity.capabilities import (
12
+ CapabilityCallContext,
13
+ CapabilityCallable,
14
+ CapabilityCallResult,
15
+ CapabilityContextError,
16
+ CapabilityDeadlineExceededError,
17
+ CapabilityDefinition,
18
+ CapabilityDelegationCycleError,
19
+ CapabilityOperation,
20
+ CapabilityOperationNotFoundError,
21
+ CapabilityOperationUnavailableError,
22
+ CapabilityState,
23
+ CapabilityStateProvider,
24
+ ICapability,
25
+ )
26
+
27
+
28
+ class Capability(CapabilityBase):
29
+ """Neutral generic capability with only explicitly supplied configuration."""
30
+
31
+ def __init__(
32
+ self,
33
+ definition: CapabilityDefinition,
34
+ /,
35
+ *,
36
+ operations: Mapping[str, CapabilityOperation],
37
+ attributes: Mapping[str, object] | None = None,
38
+ state: CapabilityState | CapabilityStateProvider | None = None,
39
+ ) -> None:
40
+ if not definition.capability_id.strip():
41
+ raise ValueError("capability_id is required")
42
+ if not definition.version.strip():
43
+ raise ValueError("capability version is required")
44
+ if not operations:
45
+ raise ValueError("at least one capability operation is required")
46
+ normalized_operations: dict[str, CapabilityOperation] = {}
47
+ for name, operation in operations.items():
48
+ if not name.strip():
49
+ raise ValueError("capability operation name is required")
50
+ if not isinstance(operation, CapabilityOperation):
51
+ raise TypeError(f"invalid capability operation: {name}")
52
+ if operation.target is not None and not callable(operation.target):
53
+ raise TypeError(f"capability operation target must be callable: {name}")
54
+ if operation.required and operation.target is None:
55
+ raise NotImplementedError(
56
+ f"required capability operation is not implemented: {name}"
57
+ )
58
+ normalized_operations[name] = operation
59
+ self._capability_definition = definition
60
+ self._capability_operations = normalized_operations
61
+ self._capability_attributes = dict(attributes or {})
62
+ if state is None:
63
+ self._capability_state_provider: CapabilityStateProvider = CapabilityState
64
+ elif isinstance(state, CapabilityState):
65
+ self._capability_state_provider = lambda state=state: state
66
+ elif callable(state):
67
+ self._capability_state_provider = state
68
+ else:
69
+ raise TypeError("capability state must be a CapabilityState or state provider")
70
+
71
+ def definition(self) -> CapabilityDefinition:
72
+ return self._capability_definition
73
+
74
+ def operations(self) -> Mapping[str, CapabilityOperation]:
75
+ return dict(self._capability_operations)
76
+
77
+ def attributes(self) -> Mapping[str, object]:
78
+ return dict(self._capability_attributes)
79
+
80
+ def callables(self) -> Mapping[str, CapabilityCallable]:
81
+ return {
82
+ name: operation.target
83
+ for name, operation in self._capability_operations.items()
84
+ if operation.target is not None
85
+ }
86
+
87
+ def state(self) -> CapabilityState:
88
+ state = self._capability_state_provider()
89
+ if not isinstance(state, CapabilityState):
90
+ raise TypeError("capability state provider must return CapabilityState")
91
+ return state
92
+
93
+ def capability_report(self) -> dict[str, object]:
94
+ """Return the complete, operator-inspectable effective capability set."""
95
+ report: dict[str, object] = dict(asdict(self._capability_definition))
96
+ report["operations"] = tuple(self._capability_operations)
97
+ report["bound_operations"] = tuple(sorted(self.callables()))
98
+ report["delegated_operations"] = tuple(
99
+ sorted(
100
+ name
101
+ for name, operation in self._capability_operations.items()
102
+ if operation.target is not None and operation.delegated
103
+ )
104
+ )
105
+ report["optional_operations"] = tuple(
106
+ sorted(
107
+ name
108
+ for name, operation in self._capability_operations.items()
109
+ if not operation.required
110
+ )
111
+ )
112
+ report["unavailable_optional_operations"] = tuple(
113
+ sorted(
114
+ name
115
+ for name, operation in self._capability_operations.items()
116
+ if not operation.required and operation.target is None
117
+ )
118
+ )
119
+ report["state"] = asdict(self.state())
120
+ return report
121
+
122
+ async def call(
123
+ self,
124
+ operation: str,
125
+ *args: object,
126
+ context: CapabilityCallContext | None = None,
127
+ **kwargs: object,
128
+ ) -> CapabilityCallResult:
129
+ try:
130
+ operation_definition = self._capability_operations[operation]
131
+ except KeyError as exc:
132
+ raise CapabilityOperationNotFoundError(
133
+ f"unknown capability operation: {operation}"
134
+ ) from exc
135
+ target = operation_definition.target
136
+ if target is None:
137
+ raise CapabilityOperationUnavailableError(
138
+ f"unbound optional capability operation: {operation}"
139
+ )
140
+ active = context or CapabilityCallContext(call_id=new_opaque_id())
141
+ capability_id = self._capability_definition.capability_id
142
+ if active.capability_id not in {None, capability_id}:
143
+ raise CapabilityContextError(
144
+ "call context capability_id does not match target capability"
145
+ )
146
+ if active.operation not in {None, operation}:
147
+ raise CapabilityContextError(
148
+ "call context operation does not match target operation"
149
+ )
150
+ active = replace(
151
+ active,
152
+ capability_id=capability_id,
153
+ operation=operation,
154
+ )
155
+ if active.deadline is not None and time.time() > active.deadline:
156
+ raise CapabilityDeadlineExceededError(
157
+ "capability call deadline exceeded"
158
+ )
159
+ value = target(*args, **kwargs)
160
+ if inspect.isawaitable(value):
161
+ value = await value
162
+ return CapabilityCallResult(
163
+ value=value,
164
+ capability_id=self._capability_definition.capability_id,
165
+ operation=operation,
166
+ call_id=active.call_id,
167
+ delegated=operation_definition.delegated,
168
+ context=active,
169
+ )
170
+
171
+ async def subcall(
172
+ self,
173
+ capability: ICapability,
174
+ operation: str,
175
+ *args: object,
176
+ context: CapabilityCallContext,
177
+ authority: tuple[str, ...] | None = None,
178
+ **kwargs: object,
179
+ ) -> CapabilityCallResult:
180
+ target_capability_id = capability.definition().capability_id
181
+ delegation_chain = context.delegation_chain + (
182
+ self._capability_definition.capability_id,
183
+ )
184
+ if target_capability_id in delegation_chain:
185
+ raise CapabilityDelegationCycleError(
186
+ "capability delegation cycle detected"
187
+ )
188
+ child_authority = context.authority if authority is None else authority
189
+ if not set(child_authority).issubset(context.authority):
190
+ raise CapabilityContextError(
191
+ "delegated authority must narrow or preserve parent authority"
192
+ )
193
+ child = replace(
194
+ context,
195
+ parent_call_id=context.call_id,
196
+ call_id=new_opaque_id(),
197
+ capability_id=target_capability_id,
198
+ operation=operation,
199
+ delegation_chain=delegation_chain,
200
+ authority=tuple(child_authority),
201
+ )
202
+ return await capability.call(operation, *args, context=child, **kwargs)
203
+
204
+
205
+ __all__ = ["Capability", "new_prefixed_id", "utc_now_iso"]