capability 1.0.0__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.
- capability/__init__.py +64 -0
- capability/_attach.py +44 -0
- capability/_compiler.py +80 -0
- capability/_core.py +76 -0
- capability/_dispatch.py +95 -0
- capability/_folds.py +56 -0
- capability/_phase.py +29 -0
- capability/_trace.py +51 -0
- capability/py.typed +0 -0
- capability/reflect.py +34 -0
- capability-1.0.0.dist-info/METADATA +241 -0
- capability-1.0.0.dist-info/RECORD +14 -0
- capability-1.0.0.dist-info/WHEEL +4 -0
- capability-1.0.0.dist-info/licenses/LICENSE +21 -0
capability/__init__.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""capability — type-safe composable primitives.
|
|
2
|
+
|
|
3
|
+
A *capability* is an immutable value that carries its own contribution to a typed context. A
|
|
4
|
+
*fold* threads a context through a sequence of capabilities, applying each (open-world: unknown
|
|
5
|
+
ones are skipped). The same capabilities run through different `Phase`s give different,
|
|
6
|
+
independent results — validation, docs, DDL — and `Phase`s compose into a `Compiler` with
|
|
7
|
+
set-like operators (`+ - & |`) that folds every phase over the values in one pass.
|
|
8
|
+
|
|
9
|
+
Five properties, by construction:
|
|
10
|
+
|
|
11
|
+
- **self-describing** — a capability carries its own contribution (no external visitor),
|
|
12
|
+
- **composable** — phases compose into compilers, and compilers into each other,
|
|
13
|
+
- **inspectable** — capabilities and contexts are immutable data (`explain` is free),
|
|
14
|
+
- **open-world** — add a capability or a target without editing the core (`isinstance` dispatch),
|
|
15
|
+
- **type-safe** — the typed `apply` is checked at the call site; `pyright --strict` clean.
|
|
16
|
+
|
|
17
|
+
The whole engine is `fold` + a `Step`. Everything else is optional layers.
|
|
18
|
+
|
|
19
|
+
from capability import Phase, by_protocol
|
|
20
|
+
|
|
21
|
+
validate = Phase(Rule, by_protocol(Validated, lambda c, ctx: c.validate(ctx)))
|
|
22
|
+
rule = validate.run(fields)
|
|
23
|
+
|
|
24
|
+
`fold`/`afold` are the same overloaded function: a sync step returns the context; an async
|
|
25
|
+
step returns a coroutine. The recommended dispatch is the type-safe `by_protocol`; a
|
|
26
|
+
reflection-based alternative (dispatch by method *name*) lives in `capability.reflect`.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from capability._attach import from_annotated, from_sidecar
|
|
30
|
+
from capability._compiler import Bundle, Compiler
|
|
31
|
+
from capability._core import AsyncStep, Capability, Step, afold, fold
|
|
32
|
+
from capability._dispatch import Table, by_protocol, by_table, chain
|
|
33
|
+
from capability._phase import Phase
|
|
34
|
+
from capability._trace import FoldStep, Trace, explain, traced_fold
|
|
35
|
+
from capability._folds import Children, TreeStep, fold_tree, rfold, scan
|
|
36
|
+
|
|
37
|
+
__version__ = "1.0.0"
|
|
38
|
+
__author__ = "prostomarkeloff"
|
|
39
|
+
|
|
40
|
+
__all__ = (
|
|
41
|
+
"AsyncStep",
|
|
42
|
+
"Bundle",
|
|
43
|
+
"Capability",
|
|
44
|
+
"Children",
|
|
45
|
+
"Compiler",
|
|
46
|
+
"FoldStep",
|
|
47
|
+
"Phase",
|
|
48
|
+
"Step",
|
|
49
|
+
"Table",
|
|
50
|
+
"Trace",
|
|
51
|
+
"TreeStep",
|
|
52
|
+
"afold",
|
|
53
|
+
"by_protocol",
|
|
54
|
+
"by_table",
|
|
55
|
+
"chain",
|
|
56
|
+
"explain",
|
|
57
|
+
"fold",
|
|
58
|
+
"fold_tree",
|
|
59
|
+
"from_annotated",
|
|
60
|
+
"from_sidecar",
|
|
61
|
+
"rfold",
|
|
62
|
+
"scan",
|
|
63
|
+
"traced_fold",
|
|
64
|
+
)
|
capability/_attach.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Attach — pluggable extractors for *where capabilities live*.
|
|
2
|
+
|
|
3
|
+
The library is agnostic about the carrier. These helpers cover the two common ones without
|
|
4
|
+
reflection (`get_args` / `vars` / `__mro__` are sanctioned introspection, not `getattr`):
|
|
5
|
+
|
|
6
|
+
- `from_annotated(tp)` — capabilities packed into `Annotated[T, cap, cap, ...]` metadata.
|
|
7
|
+
- `from_sidecar(cls, attr=...)` — capabilities stored in a class-level sidecar attribute.
|
|
8
|
+
|
|
9
|
+
Both return `tuple[Capability, ...]`; since `Capability` is the empty marker, every metadata
|
|
10
|
+
object qualifies. Narrow to a concrete capability type in the phase that consumes them.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import typing
|
|
14
|
+
|
|
15
|
+
from capability._core import Capability
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def from_annotated(tp: typing.Any) -> tuple[Capability, ...]:
|
|
19
|
+
"""Return the metadata objects of an `Annotated[T, *metadata]` form (empty for plain types).
|
|
20
|
+
|
|
21
|
+
`tp` is intentionally `Any`: annotation forms are not ordinary `type`s."""
|
|
22
|
+
args = typing.get_args(tp)
|
|
23
|
+
if not args:
|
|
24
|
+
return ()
|
|
25
|
+
return tuple(args[1:])
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def from_sidecar(cls: type, *, attr: str, inherit: bool = True) -> tuple[Capability, ...]:
|
|
29
|
+
"""Return capabilities stored under `attr` on a class. Reflection-free (`vars` + `__mro__`).
|
|
30
|
+
|
|
31
|
+
With `inherit=True` (default) the attribute is resolved along the MRO — the nearest class that
|
|
32
|
+
defines it wins, so a subclass inherits a base's capabilities. With `inherit=False` only
|
|
33
|
+
`cls`'s own body is read; bases are ignored. Either way, a missing attribute yields `()`."""
|
|
34
|
+
if inherit:
|
|
35
|
+
for klass in cls.__mro__:
|
|
36
|
+
found = vars(klass).get(attr)
|
|
37
|
+
if found is not None:
|
|
38
|
+
return tuple(found)
|
|
39
|
+
return ()
|
|
40
|
+
own = vars(cls).get(attr)
|
|
41
|
+
return tuple(own) if own is not None else ()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ("from_annotated", "from_sidecar")
|
capability/_compiler.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""The compiler algebra — compose `Phase`s and run them over one description in a single pass.
|
|
2
|
+
|
|
3
|
+
`Compiler` is a keyed set of phases with set-like operators (`+ - & |`). Running it folds the
|
|
4
|
+
*same* item sequence through every phase once (banana-split fusion) and returns a `Bundle`.
|
|
5
|
+
|
|
6
|
+
`Bundle.get(phase)` is typed: it returns the phase's own context type `C`. The heterogeneous
|
|
7
|
+
storage is the one place the package keeps an explicit `typing.Any` (a heterogeneous typed map
|
|
8
|
+
cannot be expressed otherwise in Python) — it is quarantined here and never leaks: callers only
|
|
9
|
+
ever touch the typed `get`.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import dataclasses
|
|
13
|
+
import typing
|
|
14
|
+
from collections.abc import Iterable
|
|
15
|
+
|
|
16
|
+
from capability._phase import Phase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _key[Cap](phase: Phase[Cap, typing.Any]) -> str:
|
|
20
|
+
return phase.name or f"@{id(phase)}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
24
|
+
class Bundle[Cap]:
|
|
25
|
+
"""The result of running a `Compiler`: each phase's context, retrievable by the phase.
|
|
26
|
+
|
|
27
|
+
Access is type-safe via `get`; the internal store is heterogeneous by necessity."""
|
|
28
|
+
|
|
29
|
+
entries: tuple[tuple[Phase[Cap, typing.Any], typing.Any], ...]
|
|
30
|
+
|
|
31
|
+
def get[C](self, phase: Phase[Cap, C]) -> C:
|
|
32
|
+
"""Return the context this `phase` produced. Keyed the same way as the compiler algebra —
|
|
33
|
+
by `phase.name` (or by identity for anonymous phases) — so an equivalently-named phase
|
|
34
|
+
resolves to the same entry instead of a surprising `KeyError`."""
|
|
35
|
+
wanted = _key(phase)
|
|
36
|
+
for stored, value in self.entries:
|
|
37
|
+
if _key(stored) == wanted:
|
|
38
|
+
return value
|
|
39
|
+
raise KeyError(wanted)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
43
|
+
class Compiler[Cap]:
|
|
44
|
+
"""A composable, keyed set of phases. Operators key phases by `Phase.name` (anonymous
|
|
45
|
+
phases key by identity), giving an idempotent semilattice over phase sets."""
|
|
46
|
+
|
|
47
|
+
phases: tuple[Phase[Cap, typing.Any], ...] = ()
|
|
48
|
+
|
|
49
|
+
def run(self, items: Iterable[Cap]) -> Bundle[Cap]:
|
|
50
|
+
"""Fold one materialised item sequence through every phase, once each."""
|
|
51
|
+
seq = tuple(items)
|
|
52
|
+
return Bundle(tuple((phase, phase.run(seq)) for phase in self.phases))
|
|
53
|
+
|
|
54
|
+
def __add__(self, other: "Compiler[Cap] | Phase[Cap, typing.Any]") -> "Compiler[Cap]":
|
|
55
|
+
"""Left-biased union. Idempotent (`A + A == A`): phases already present win."""
|
|
56
|
+
added = other.phases if isinstance(other, Compiler) else (other,)
|
|
57
|
+
seen = {_key(phase) for phase in self.phases}
|
|
58
|
+
extra = tuple(phase for phase in added if _key(phase) not in seen)
|
|
59
|
+
return Compiler((*self.phases, *extra))
|
|
60
|
+
|
|
61
|
+
def __sub__(self, other: "Compiler[Cap]") -> "Compiler[Cap]":
|
|
62
|
+
"""Restriction: drop phases whose key appears in `other`."""
|
|
63
|
+
drop = {_key(phase) for phase in other.phases}
|
|
64
|
+
return Compiler(tuple(phase for phase in self.phases if _key(phase) not in drop))
|
|
65
|
+
|
|
66
|
+
def __and__(self, other: "Compiler[Cap]") -> "Compiler[Cap]":
|
|
67
|
+
"""Intersection: keep only phases whose key appears in both."""
|
|
68
|
+
keep = {_key(phase) for phase in other.phases}
|
|
69
|
+
return Compiler(tuple(phase for phase in self.phases if _key(phase) in keep))
|
|
70
|
+
|
|
71
|
+
def __or__(self, other: "Compiler[Cap]") -> "Compiler[Cap]":
|
|
72
|
+
"""Right-biased merge: `other`'s phases override same-keyed phases in `self`."""
|
|
73
|
+
override = {_key(phase): phase for phase in other.phases}
|
|
74
|
+
merged = tuple(override.get(_key(phase), phase) for phase in self.phases)
|
|
75
|
+
present = {_key(phase) for phase in self.phases}
|
|
76
|
+
extra = tuple(phase for phase in other.phases if _key(phase) not in present)
|
|
77
|
+
return Compiler((*merged, *extra))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = ("Bundle", "Compiler")
|
capability/_core.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""The substrate: the `fold` primitive, the `Step`/`AsyncStep` policies, the `Capability` marker.
|
|
2
|
+
|
|
3
|
+
`fold` is a typed left-fold. It threads an immutable accumulator through a sequence of items,
|
|
4
|
+
applying a *dispatch policy* to each. The policy lives entirely in the `Step` — `fold` itself
|
|
5
|
+
never inspects an item, never names a target. A new target is a new `Step`, not an edit here.
|
|
6
|
+
|
|
7
|
+
A `Step`/`AsyncStep` is a small frozen wrapper around the dispatch function (not a bare callable).
|
|
8
|
+
Two distinct nominal types is what lets `fold` choose the sync vs async branch with an
|
|
9
|
+
unambiguous `isinstance` — and you never build them by hand: use `by_protocol`/`by_table`/`chain`.
|
|
10
|
+
|
|
11
|
+
`fold` is `@overload`-ed: a sync `Step` returns the final context; an `AsyncStep` makes `fold`
|
|
12
|
+
return a coroutine. `afold` is a literal alias of `fold` for readers who prefer `await afold(...)`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import dataclasses
|
|
16
|
+
import typing
|
|
17
|
+
from collections.abc import Awaitable, Callable, Coroutine, Iterable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@typing.runtime_checkable
|
|
21
|
+
class Capability(typing.Protocol):
|
|
22
|
+
"""The universal item marker — an *empty* protocol, so structurally **anything** is a
|
|
23
|
+
`Capability`. It gives the dispatch combinators a pyrules-clean "any item" type (instead of
|
|
24
|
+
`object`) while preserving the open-world rule: a capability need not inherit anything; it
|
|
25
|
+
merely needs some `Step` that knows how to apply it."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
29
|
+
class Step[Cap, C]:
|
|
30
|
+
"""A sync dispatch policy: wraps `fn(item, ctx) -> ctx`. Built by `by_protocol`/`by_table`/
|
|
31
|
+
`chain`; consumed by `fold`. A policy that does not recognise an item returns the context
|
|
32
|
+
unchanged (the open-world skip)."""
|
|
33
|
+
|
|
34
|
+
fn: Callable[[Cap, C], C]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
38
|
+
class AsyncStep[Cap, C]:
|
|
39
|
+
"""An async dispatch policy: wraps `fn(item, ctx) -> Awaitable[ctx]`. Built by passing an
|
|
40
|
+
`async def` apply to `by_protocol`; consumed by `fold`/`afold` (which return a coroutine).
|
|
41
|
+
Disjoint from `Step` so `fold` can branch on `isinstance` with no type ambiguity."""
|
|
42
|
+
|
|
43
|
+
fn: Callable[[Cap, C], Awaitable[C]]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def _afold[Cap, C](items: Iterable[Cap], initial: C, step: AsyncStep[Cap, C]) -> C:
|
|
47
|
+
ctx = initial
|
|
48
|
+
for item in items:
|
|
49
|
+
ctx = await step.fn(item, ctx)
|
|
50
|
+
return ctx
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@typing.overload
|
|
54
|
+
def fold[Cap, C](
|
|
55
|
+
items: Iterable[Cap], initial: C, step: AsyncStep[Cap, C]
|
|
56
|
+
) -> Coroutine[typing.Any, typing.Any, C]: ...
|
|
57
|
+
@typing.overload
|
|
58
|
+
def fold[Cap, C](items: Iterable[Cap], initial: C, step: Step[Cap, C]) -> C: ...
|
|
59
|
+
def fold[Cap, C](
|
|
60
|
+
items: Iterable[Cap], initial: C, step: Step[Cap, C] | AsyncStep[Cap, C]
|
|
61
|
+
) -> C | Coroutine[typing.Any, typing.Any, C]:
|
|
62
|
+
"""Thread `initial` through `items` left-to-right with `step`. Order-dependent, total,
|
|
63
|
+
reflection-free. Returns the final context for a sync step, or a coroutine for an async one."""
|
|
64
|
+
if isinstance(step, AsyncStep):
|
|
65
|
+
return _afold(items, initial, step)
|
|
66
|
+
ctx = initial
|
|
67
|
+
for item in items:
|
|
68
|
+
ctx = step.fn(item, ctx)
|
|
69
|
+
return ctx
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
afold = fold
|
|
73
|
+
"""Alias of `fold`. Identical overloaded behaviour; reads better as `await afold(...)`."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = ("AsyncStep", "Capability", "Step", "afold", "fold")
|
capability/_dispatch.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Dispatch combinators — the typed, reflection-free way to build a `Step`.
|
|
2
|
+
|
|
3
|
+
The library does not pick a side of the Expression Problem; it hands you both directions:
|
|
4
|
+
|
|
5
|
+
- `by_protocol` is open **on the data side** — add a new capability that implements the
|
|
6
|
+
protocol and existing folds pick it up. (Use when capabilities proliferate.)
|
|
7
|
+
- `by_table` is open **on the interpreter side** — add a new fold with its own handler
|
|
8
|
+
table. (Use when targets proliferate.)
|
|
9
|
+
- `chain` runs several policies on one fold pass (keep them the same item type — see `chain`).
|
|
10
|
+
|
|
11
|
+
`by_protocol` is `@overload`-ed: a sync `apply` yields a `Step`; an async `apply` (an
|
|
12
|
+
`async def`) yields an `AsyncStep` (for `fold`/`afold`). One name, both worlds. None of this
|
|
13
|
+
uses `getattr` or string-dispatch — if you want name-driven dispatch, see `capability.reflect`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import inspect
|
|
17
|
+
import typing
|
|
18
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
19
|
+
|
|
20
|
+
from capability._core import AsyncStep, Capability, Step
|
|
21
|
+
|
|
22
|
+
type Table[Cap, C] = Mapping[type[Cap], Callable[[Cap, C], C]]
|
|
23
|
+
"""A handler table for `by_table`: maps an exact capability class to its handler."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@typing.overload
|
|
27
|
+
def by_protocol[P, C](
|
|
28
|
+
proto: type[P], apply: Callable[[P, C], Awaitable[C]]
|
|
29
|
+
) -> AsyncStep[Capability, C]: ...
|
|
30
|
+
@typing.overload
|
|
31
|
+
def by_protocol[P, C](proto: type[P], apply: Callable[[P, C], C]) -> Step[Capability, C]: ...
|
|
32
|
+
def by_protocol[P, C](
|
|
33
|
+
proto: type[P], apply: Callable[[P, C], C | Awaitable[C]]
|
|
34
|
+
) -> Step[Capability, C] | AsyncStep[Capability, C]:
|
|
35
|
+
"""Apply `apply(item, ctx)` to items that are instances of `proto`; skip the rest.
|
|
36
|
+
|
|
37
|
+
`proto` must be `@runtime_checkable`. Note its runtime cost: `isinstance` against a
|
|
38
|
+
runtime-checkable Protocol matches on **method-name presence**, not signatures — so the typed
|
|
39
|
+
`apply` (`lambda c, ctx: c.compile_x(ctx)`) is what gives you the real, signature-checked
|
|
40
|
+
dispatch at the call site. For async, pass an `async def` apply; the returned `AsyncStep` is
|
|
41
|
+
consumed by `fold`/`afold`."""
|
|
42
|
+
if inspect.iscoroutinefunction(apply):
|
|
43
|
+
|
|
44
|
+
async def afn(item: Capability, ctx: C) -> C:
|
|
45
|
+
if isinstance(item, proto):
|
|
46
|
+
return await apply(item, ctx)
|
|
47
|
+
return ctx
|
|
48
|
+
|
|
49
|
+
return AsyncStep(afn)
|
|
50
|
+
|
|
51
|
+
def fn(item: Capability, ctx: C) -> C:
|
|
52
|
+
if isinstance(item, proto):
|
|
53
|
+
produced = apply(item, ctx)
|
|
54
|
+
if isinstance(produced, Awaitable):
|
|
55
|
+
raise TypeError(
|
|
56
|
+
"async apply must be an 'async def', not a coroutine-returning lambda"
|
|
57
|
+
)
|
|
58
|
+
return produced
|
|
59
|
+
return ctx
|
|
60
|
+
|
|
61
|
+
return Step(fn)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def by_table[Cap, C](table: Table[Cap, C]) -> Step[Cap, C]:
|
|
65
|
+
"""Apply the handler registered for an item's exact class; skip items with no entry.
|
|
66
|
+
|
|
67
|
+
Keyed by exact `type(item)` (not `isinstance`), so a handler for a base class does not
|
|
68
|
+
fire for subclasses — that is what makes the interpreter side independently extensible."""
|
|
69
|
+
|
|
70
|
+
def fn(item: Cap, ctx: C) -> C:
|
|
71
|
+
handler = table.get(type(item))
|
|
72
|
+
if handler is not None:
|
|
73
|
+
return handler(item, ctx)
|
|
74
|
+
return ctx
|
|
75
|
+
|
|
76
|
+
return Step(fn)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def chain[Cap, C](*steps: Step[Cap, C]) -> Step[Cap, C]:
|
|
80
|
+
"""Compose dispatch policies on one fold pass: apply each `step` to every item, left to right.
|
|
81
|
+
Use to run several `by_protocol` policies over the same context in a single traversal.
|
|
82
|
+
|
|
83
|
+
`Step` is invariant in its item type, so chaining a `by_protocol` (`Step[Capability, C]`) with a
|
|
84
|
+
narrower `by_table` step won't type-check under strict mode — keep chained steps homogeneous,
|
|
85
|
+
or build the table as `by_table[Capability, C](...)` (handlers over `Capability`) to mix."""
|
|
86
|
+
|
|
87
|
+
def fn(item: Cap, ctx: C) -> C:
|
|
88
|
+
for inner in steps:
|
|
89
|
+
ctx = inner.fn(item, ctx)
|
|
90
|
+
return ctx
|
|
91
|
+
|
|
92
|
+
return Step(fn)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
__all__ = ("Table", "by_protocol", "by_table", "chain")
|
capability/_folds.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Extra fold shapes — cheap variants of the core left `fold`.
|
|
2
|
+
|
|
3
|
+
The core `fold` (in `_core`) is the canonical left fold: the catamorphism over a flat list.
|
|
4
|
+
These are the neighbours you occasionally want — a right fold, a scan that keeps every
|
|
5
|
+
intermediate context, and the tree catamorphism for recursive descriptions. All are small,
|
|
6
|
+
sync, reflection-free, and read a `Step`'s `.fn` directly.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable, Iterable
|
|
10
|
+
|
|
11
|
+
from capability._core import Step
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def rfold[Cap, C](items: Iterable[Cap], initial: C, step: Step[Cap, C]) -> C:
|
|
15
|
+
"""Right fold: apply `step` to items last-to-first. Equals `fold(reversed(items), ...)`.
|
|
16
|
+
|
|
17
|
+
Materialises `items` (needs to walk them in reverse). Use when the natural accumulation runs
|
|
18
|
+
from the tail — e.g. building a nested wrapper chain outermost-last."""
|
|
19
|
+
ctx = initial
|
|
20
|
+
for item in reversed(tuple(items)):
|
|
21
|
+
ctx = step.fn(item, ctx)
|
|
22
|
+
return ctx
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def scan[Cap, C](items: Iterable[Cap], initial: C, step: Step[Cap, C]) -> tuple[C, ...]:
|
|
26
|
+
"""Left scan: every intermediate context, starting with `initial`.
|
|
27
|
+
|
|
28
|
+
`scan(xs, init, step)[0] == init`, `scan(...)[-1] == fold(xs, init, step)`, and the length is
|
|
29
|
+
`len(xs) + 1`. Handy for visualising how a fold evolves, or for incremental/streaming use."""
|
|
30
|
+
states: list[C] = [initial]
|
|
31
|
+
ctx = initial
|
|
32
|
+
for item in items:
|
|
33
|
+
ctx = step.fn(item, ctx)
|
|
34
|
+
states.append(ctx)
|
|
35
|
+
return tuple(states)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
type Children[N] = Callable[[N], Iterable[N]]
|
|
39
|
+
"""Given a node, yield its child nodes."""
|
|
40
|
+
|
|
41
|
+
type TreeStep[N, R] = Callable[[N, tuple[R, ...]], R]
|
|
42
|
+
"""Given a node and its already-folded children, produce this node's result."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def fold_tree[N, R](root: N, children: Children[N], step: TreeStep[N, R]) -> R:
|
|
46
|
+
"""Bottom-up catamorphism over a tree: children are folded before their parent. Use it when
|
|
47
|
+
descriptions are recursive (an expression / query AST) rather than a flat capability tuple."""
|
|
48
|
+
|
|
49
|
+
def go(node: N) -> R:
|
|
50
|
+
folded = tuple(go(child) for child in children(node))
|
|
51
|
+
return step(node, folded)
|
|
52
|
+
|
|
53
|
+
return go(root)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
__all__ = ("Children", "TreeStep", "fold_tree", "rfold", "scan")
|
capability/_phase.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""`Phase` — a reified target: a context factory plus the `Step` that fills it.
|
|
2
|
+
|
|
3
|
+
A `Phase` is the answer to "what does it mean to compile these capabilities into *this*
|
|
4
|
+
output?" One immutable description (a tuple of capabilities) run through different phases
|
|
5
|
+
yields different, independent results — the "one description, many targets" property.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import dataclasses
|
|
9
|
+
from collections.abc import Callable, Iterable
|
|
10
|
+
|
|
11
|
+
from capability._core import Step, fold
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
15
|
+
class Phase[Cap, C]:
|
|
16
|
+
"""A named fold target: `initial()` seeds the accumulator, `step` is the dispatch policy.
|
|
17
|
+
|
|
18
|
+
`name` is used only by the compiler algebra (as the merge key) and by `explain`."""
|
|
19
|
+
|
|
20
|
+
initial: Callable[[], C]
|
|
21
|
+
step: Step[Cap, C]
|
|
22
|
+
name: str = ""
|
|
23
|
+
|
|
24
|
+
def run(self, items: Iterable[Cap]) -> C:
|
|
25
|
+
"""Fold `items` through this phase, returning the accumulated context."""
|
|
26
|
+
return fold(items, self.initial(), self.step)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
__all__ = ("Phase",)
|
capability/_trace.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Inspectability — `traced_fold` records every step; `explain` renders it.
|
|
2
|
+
|
|
3
|
+
Because capabilities and contexts are immutable, "did this step change the context?" is a
|
|
4
|
+
cheap identity check (`before is not after`) — no defensive copies. Tracing is a separate
|
|
5
|
+
function so the hot `fold` path stays branch-free and zero-cost when you do not need it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import dataclasses
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
|
|
11
|
+
from capability._core import Step
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
15
|
+
class FoldStep:
|
|
16
|
+
"""One recorded fold step: the item's type name and whether it changed the context."""
|
|
17
|
+
|
|
18
|
+
item_type: str
|
|
19
|
+
changed: bool
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclasses.dataclass(slots=True)
|
|
23
|
+
class Trace:
|
|
24
|
+
"""A per-call sink for `FoldStep`s. An instance, not module state — pass a fresh one in."""
|
|
25
|
+
|
|
26
|
+
steps: list[FoldStep] = dataclasses.field(default_factory=list[FoldStep])
|
|
27
|
+
|
|
28
|
+
def record(self, item_type: str, changed: bool) -> None:
|
|
29
|
+
self.steps.append(FoldStep(item_type, changed))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def traced_fold[Cap, C](items: Iterable[Cap], initial: C, step: Step[Cap, C], trace: Trace) -> C:
|
|
33
|
+
"""Like sync `fold`, but append a `FoldStep` to `trace` for each item."""
|
|
34
|
+
ctx = initial
|
|
35
|
+
for item in items:
|
|
36
|
+
before = ctx
|
|
37
|
+
ctx = step.fn(item, ctx)
|
|
38
|
+
trace.record(type(item).__qualname__, before is not ctx)
|
|
39
|
+
return ctx
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def explain(trace: Trace) -> str:
|
|
43
|
+
"""Render a trace as human-readable lines: which items changed the context, which skipped."""
|
|
44
|
+
lines = [
|
|
45
|
+
f" {fold_step.item_type}: {'changed' if fold_step.changed else 'skipped'}"
|
|
46
|
+
for fold_step in trace.steps
|
|
47
|
+
]
|
|
48
|
+
return "\n".join(lines)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ("FoldStep", "Trace", "explain", "traced_fold")
|
capability/py.typed
ADDED
|
File without changes
|
capability/reflect.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Name-based dispatch — a legitimate alternative to `by_protocol`.
|
|
2
|
+
|
|
3
|
+
Dispatch by a method *name* resolved at runtime: any item exposing a method called `method`
|
|
4
|
+
(found by walking the MRO and reading each class `__dict__` via `vars`) gets it applied; the rest
|
|
5
|
+
are skipped. Free of `getattr`, so it stays lint-clean — but name-driven, so the method's
|
|
6
|
+
signature is not checked. The *recommended* path remains `capability.by_protocol` with a typed
|
|
7
|
+
`apply`; reach here when name dispatch is what you want.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
|
|
12
|
+
from capability._core import Capability, Step
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def by_method[C](method: str, _context: type[C]) -> Step[Capability, C]:
|
|
16
|
+
"""Apply `item.<method>(ctx)` to items exposing `method`; skip the rest.
|
|
17
|
+
|
|
18
|
+
Looks the method up along the item's MRO via `vars` (no `getattr`) and calls it unbound.
|
|
19
|
+
`_context` is a type-only witness that pins the accumulator type `C` (name dispatch cannot
|
|
20
|
+
infer it). Unchecked by design: the method's signature is not verified. Prefer
|
|
21
|
+
`capability.by_protocol` when you can express the call as a typed `apply` lambda."""
|
|
22
|
+
|
|
23
|
+
def fn(item: Capability, ctx: C) -> C:
|
|
24
|
+
for klass in type(item).__mro__:
|
|
25
|
+
found = vars(klass).get(method)
|
|
26
|
+
if found is not None:
|
|
27
|
+
bound: Callable[[Capability, C], C] = found
|
|
28
|
+
return bound(item, ctx)
|
|
29
|
+
return ctx
|
|
30
|
+
|
|
31
|
+
return Step(fn)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = ("by_method",)
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: capability
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Type-safe composable primitives.
|
|
5
|
+
Project-URL: Homepage, https://github.com/prostomarkeloff/capability
|
|
6
|
+
Project-URL: Repository, https://github.com/prostomarkeloff/capability
|
|
7
|
+
Project-URL: Issues, https://github.com/prostomarkeloff/capability/issues
|
|
8
|
+
Author: prostomarkeloff
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: capabilities,catamorphism,compiler,defunctionalization,dispatch,expression-problem,fold,visitor
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.13
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
<div align="center">
|
|
25
|
+
|
|
26
|
+
# capability
|
|
27
|
+
|
|
28
|
+
**Type-safe composable primitives.**
|
|
29
|
+
|
|
30
|
+
[](https://www.python.org/downloads/)
|
|
31
|
+
[](https://github.com/microsoft/pyright)
|
|
32
|
+
[](#correctness)
|
|
33
|
+
[](https://opensource.org/licenses/MIT)
|
|
34
|
+
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
Every time you turn a structure into outputs — validate it, document it, compile it to SQL, lint
|
|
38
|
+
it — you hand-write the same machinery: an `if isinstance(...)` ladder, a visitor class, a registry
|
|
39
|
+
keyed by type. It is bespoke each time. It does not compose — you can't add a case from outside, or
|
|
40
|
+
run two passes in one traversal, without rewiring it. And the type checker can't see through the
|
|
41
|
+
dispatch, so a missing case is a runtime surprise, not a red squiggle.
|
|
42
|
+
|
|
43
|
+
A stateless coding agent makes it sharper: it re-derives that machinery from a few thousand lines of
|
|
44
|
+
context, gets one case wrong, and the dispatch hides the gap.
|
|
45
|
+
|
|
46
|
+
`capability` is the handful of primitives that machinery reduces to: a **fold** over
|
|
47
|
+
**self-describing values**, typed end to end. The values carry their own behaviour; a fold runs
|
|
48
|
+
them; and they compose — a new value, a new pass, a new target are each an addition, never an edit
|
|
49
|
+
to the core.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv add capability # zero dependencies, stdlib only
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## The primitives
|
|
56
|
+
|
|
57
|
+
A *capability* is a frozen value that carries its own contribution to a typed context. A *fold*
|
|
58
|
+
threads a context through a sequence of them, dispatching to each by an `isinstance` check against a
|
|
59
|
+
protocol — a value that doesn't speak a target's protocol is skipped. Three targets here: a
|
|
60
|
+
request-time validator, a database column, a block of help text.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from dataclasses import dataclass, replace
|
|
64
|
+
from typing import Protocol, runtime_checkable
|
|
65
|
+
|
|
66
|
+
from capability import Phase, by_protocol
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True, slots=True)
|
|
70
|
+
class Rule: # a request-time validator
|
|
71
|
+
max_len: int | None = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@runtime_checkable
|
|
75
|
+
class Validated(Protocol):
|
|
76
|
+
def validate(self, ctx: Rule) -> Rule: ...
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True, slots=True)
|
|
80
|
+
class Ddl: # a database column
|
|
81
|
+
column: str = "TEXT"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@runtime_checkable
|
|
85
|
+
class Stored(Protocol):
|
|
86
|
+
def ddl(self, ctx: Ddl) -> Ddl: ...
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True, slots=True)
|
|
90
|
+
class Doc: # help lines, accumulated
|
|
91
|
+
lines: tuple[str, ...] = ()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@runtime_checkable
|
|
95
|
+
class Documented(Protocol):
|
|
96
|
+
def doc(self, ctx: Doc) -> Doc: ...
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Each interpretation reads the context the previous fact left, and extends it.
|
|
100
|
+
@dataclass(frozen=True, slots=True)
|
|
101
|
+
class MaxLen:
|
|
102
|
+
n: int
|
|
103
|
+
|
|
104
|
+
def validate(self, ctx: Rule) -> Rule:
|
|
105
|
+
return replace(ctx, max_len=self.n)
|
|
106
|
+
|
|
107
|
+
def ddl(self, ctx: Ddl) -> Ddl:
|
|
108
|
+
return replace(ctx, column=f"VARCHAR({self.n})")
|
|
109
|
+
|
|
110
|
+
def doc(self, ctx: Doc) -> Doc:
|
|
111
|
+
return replace(ctx, lines=(*ctx.lines, f"at most {self.n} characters"))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass(frozen=True, slots=True)
|
|
115
|
+
class Unique:
|
|
116
|
+
def ddl(self, ctx: Ddl) -> Ddl:
|
|
117
|
+
return replace(ctx, column=f"{ctx.column} UNIQUE")
|
|
118
|
+
|
|
119
|
+
def doc(self, ctx: Doc) -> Doc:
|
|
120
|
+
return replace(ctx, lines=(*ctx.lines, "must be unique"))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
validate = Phase(Rule, by_protocol(Validated, lambda c, ctx: c.validate(ctx)))
|
|
124
|
+
store = Phase(Ddl, by_protocol(Stored, lambda c, ctx: c.ddl(ctx)))
|
|
125
|
+
document = Phase(Doc, by_protocol(Documented, lambda c, ctx: c.doc(ctx)))
|
|
126
|
+
|
|
127
|
+
field = (MaxLen(255), Unique())
|
|
128
|
+
|
|
129
|
+
validate.run(field) # Rule(max_len=255) — Unique has no validate, skipped
|
|
130
|
+
store.run(field) # Ddl(column='VARCHAR(255) UNIQUE') — both facts fold in
|
|
131
|
+
document.run(field) # Doc(lines=('at most 255 characters', 'must be unique'))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Add a fact (`MinLen`, `Indexed`): it implements the protocols it has, the rest skip it. Add a
|
|
135
|
+
target: a new `Phase`, no fact changes. Every piece is a small typed value, and nothing in the core
|
|
136
|
+
enumerates them.
|
|
137
|
+
|
|
138
|
+
It does not remove the work — `MaxLen` still spells out `validate`, `ddl`, `doc` by hand. It removes
|
|
139
|
+
the *machinery*: no visitor, no registry, no dispatch ladder — and the typed `apply` keeps each call
|
|
140
|
+
honest under `pyright --strict`.
|
|
141
|
+
|
|
142
|
+
## Compose them
|
|
143
|
+
|
|
144
|
+
`Phase`s compose into a `Compiler` — a keyed set with set-like operators that folds every phase over
|
|
145
|
+
the facts in a single pass:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from capability import Compiler
|
|
149
|
+
|
|
150
|
+
schema = Compiler((validate, store, document)) # a composable set of phases
|
|
151
|
+
bundle = schema.run(field) # one traversal, every phase at once
|
|
152
|
+
|
|
153
|
+
bundle.get(store) # Ddl(column='VARCHAR(255) UNIQUE')
|
|
154
|
+
bundle.get(document) # Doc(lines=('at most 255 characters', 'must be unique'))
|
|
155
|
+
|
|
156
|
+
# compilers compose like sets: a + b (union) a - b (restrict) a & b (intersect) a | b (override)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The operators form an idempotent semilattice (`A + A == A`), so composing compilers is predictable;
|
|
160
|
+
running one fuses every phase into a single traversal (banana-split). Composition is the point —
|
|
161
|
+
primitives into phases, phases into compilers, compilers into each other. `Bundle.get` is typed: it
|
|
162
|
+
hands back exactly the phase's own context type.
|
|
163
|
+
|
|
164
|
+
## The whole engine
|
|
165
|
+
|
|
166
|
+
The core primitive, with the typing stripped, is a left fold:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
def fold(items, initial, step):
|
|
170
|
+
ctx = initial
|
|
171
|
+
for item in items:
|
|
172
|
+
ctx = step(item, ctx)
|
|
173
|
+
return ctx
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`fold` names no target — the meaning is all in `step`, which you build with `by_protocol` /
|
|
177
|
+
`by_table` (you don't hand `fold` a bare lambda; a `Step` carries the policy and lets `fold` pick
|
|
178
|
+
the sync or async path). A new target is a new `step`, not an edit. The package is nine small
|
|
179
|
+
files, ~370 lines, zero dependencies.
|
|
180
|
+
|
|
181
|
+
**The contexts, protocols, and targets are yours.** The core ships `fold` and the dispatch
|
|
182
|
+
policies; what they compute *to* is your code.
|
|
183
|
+
|
|
184
|
+
## Layers
|
|
185
|
+
|
|
186
|
+
Everything past `fold` + a `Step` is a layer you can ignore until you want it.
|
|
187
|
+
|
|
188
|
+
| Import | What it gives you |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `fold`, `Step` | the primitive: a typed left fold + the dispatch-policy wrapper |
|
|
191
|
+
| `by_protocol` | open on the **data** axis — a new fact that implements the protocol is picked up |
|
|
192
|
+
| `by_table` | open on the **interpreter** axis — dispatch by exact `type`, a per-target handler table |
|
|
193
|
+
| `chain` | run several dispatch policies in one pass |
|
|
194
|
+
| `Phase` | a reified target — a context factory + its step; `Phase.run(items)` |
|
|
195
|
+
| `Compiler`, `Bundle` | compose phases (`+ - & \|`) and run them in one pass; `Bundle.get(phase)` returns that phase's typed context |
|
|
196
|
+
| `fold_tree` | the catamorphism for **recursive** (tree) descriptions |
|
|
197
|
+
| `rfold`, `scan` | a right fold; a left scan that keeps every intermediate context |
|
|
198
|
+
| `traced_fold`, `explain` | record each step and render it — free, because the data is immutable |
|
|
199
|
+
| `from_annotated`, `from_sidecar` | read facts off `Annotated[T, ...]` metadata or a class sidecar |
|
|
200
|
+
| `afold` | the async overload — an `async def` apply makes `fold` return a coroutine |
|
|
201
|
+
| `capability.reflect.by_method` | opt-in name-driven dispatch |
|
|
202
|
+
|
|
203
|
+
## You've met this before
|
|
204
|
+
|
|
205
|
+
`capability` is not a new idea — it's a known one, generalised and kept as small typed data:
|
|
206
|
+
|
|
207
|
+
- **`functools.singledispatch`** registers handlers externally, one function at a time, away from
|
|
208
|
+
the data. Here the interpretations live *on* the value, and one `fold` runs many at once.
|
|
209
|
+
- **Pydantic's `Annotated` metadata** is this move for a single target — validation. `capability`
|
|
210
|
+
is the same, generalised to arbitrary phases with an algebra to compose them; `from_annotated`
|
|
211
|
+
reads exactly that metadata.
|
|
212
|
+
- **Object algebras / tagless-final** are the typed-FP relatives. This is the *data* encoding, so
|
|
213
|
+
the program stays inspectable — you can print it, diff it, `explain` it — which a closure
|
|
214
|
+
encoding can't.
|
|
215
|
+
|
|
216
|
+
## Correctness
|
|
217
|
+
|
|
218
|
+
- zero dependencies, stdlib only;
|
|
219
|
+
- `src/` is `pyright --strict` clean; 100% branch coverage; the `Compiler` algebra and the fold
|
|
220
|
+
laws are property-tested;
|
|
221
|
+
- no `getattr` / `hasattr` in the dispatch code. To be exact about what `by_protocol` does at
|
|
222
|
+
runtime: `isinstance` against a `@runtime_checkable` Protocol, which matches on **method-name
|
|
223
|
+
presence**, not signatures — the typed `lambda c, ctx: c.validate(ctx)` is what buys the
|
|
224
|
+
call-site check. Name-driven dispatch is opt-in, in `capability.reflect`.
|
|
225
|
+
|
|
226
|
+
## Lineage
|
|
227
|
+
|
|
228
|
+
A capability is Reynolds' (1972) defunctionalised closure — a decision turned from code into an
|
|
229
|
+
inspectable record. The hot path is a left fold; the catamorphism it instances (Meijer–Fokkinga–
|
|
230
|
+
Paterson 1991, after Malcolm 1990) is `fold_tree` / `rfold`, for recursive shapes. And it keeps
|
|
231
|
+
Wadler's Expression Problem open on both axes.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
<div align="center">
|
|
236
|
+
|
|
237
|
+
**Small, typed, composable — the rest is yours.**
|
|
238
|
+
|
|
239
|
+
Made with 🧬 by [@prostomarkeloff](https://github.com/prostomarkeloff)
|
|
240
|
+
|
|
241
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
capability/__init__.py,sha256=MRAlmOcMJPv_Ee_6s-jAJpq7HpUZdnF23UErQpFyN94,2356
|
|
2
|
+
capability/_attach.py,sha256=xDSDyrpqtaM33V7LECZgP50xz_OAPg4cDN2Zh7aWI9Y,1798
|
|
3
|
+
capability/_compiler.py,sha256=MDB7VbZmDWFxefPfUD2tnV9bpzlKJSzN9TPg8P-HT70,3672
|
|
4
|
+
capability/_core.py,sha256=KKVBBVwWCIe4RVixCBuPzJBrn7WeUg_xPQfONumRQOM,3210
|
|
5
|
+
capability/_dispatch.py,sha256=wgs5xCC9cyb0nD9baDn01C_zGv6LsOY2XMS19ZUihF8,3920
|
|
6
|
+
capability/_folds.py,sha256=jGjs15b9AKWfVl4jEcLkc7-ejuNiTYytbo9KbOiQC6U,2170
|
|
7
|
+
capability/_phase.py,sha256=lc2sxHK3dhwNeL9m_YIT2-SDIqAOTaXdud3YtV5tK0g,993
|
|
8
|
+
capability/_trace.py,sha256=qGuxRzEuvryHXbhv0ogFNenLDwlI96Z1fjIjtmQ376A,1702
|
|
9
|
+
capability/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
capability/reflect.py,sha256=kOZs26lIty9gDbczttepY_mFQL3poYyZgeHbO9WrETU,1437
|
|
11
|
+
capability-1.0.0.dist-info/METADATA,sha256=8RCjpl1midL1nN0uS_2Oce3hjhq3bqOEsj-IphnQIW8,9898
|
|
12
|
+
capability-1.0.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
13
|
+
capability-1.0.0.dist-info/licenses/LICENSE,sha256=kBpSkOISotXfPuz4LJR2wBk9exHEgcC7BsZH7Jb73M8,1072
|
|
14
|
+
capability-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 prostomarkeloff
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|