policy-pattern 0.1.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.
Files changed (36) hide show
  1. policy_pattern/__init__.py +0 -0
  2. policy_pattern/adapters/__init__.py +3 -0
  3. policy_pattern/adapters/memory.py +135 -0
  4. policy_pattern/errors/__init__.py +19 -0
  5. policy_pattern/errors/base_error.py +28 -0
  6. policy_pattern/errors/evaluation_error.py +16 -0
  7. policy_pattern/errors/identifier_error.py +15 -0
  8. policy_pattern/errors/model_validation_error.py +15 -0
  9. policy_pattern/errors/mutation_error.py +15 -0
  10. policy_pattern/errors/relation_error.py +15 -0
  11. policy_pattern/errors/unknown_name_error.py +16 -0
  12. policy_pattern/errors/unknown_type_error.py +16 -0
  13. policy_pattern/evaluator.py +285 -0
  14. policy_pattern/models/__init__.py +34 -0
  15. policy_pattern/models/_schema_validation.py +130 -0
  16. policy_pattern/models/compiled_model.py +43 -0
  17. policy_pattern/models/direct_subject.py +47 -0
  18. policy_pattern/models/identifier.py +55 -0
  19. policy_pattern/models/model.py +46 -0
  20. policy_pattern/models/mutation.py +52 -0
  21. policy_pattern/models/object_reference.py +42 -0
  22. policy_pattern/models/operators.py +116 -0
  23. policy_pattern/models/reference.py +36 -0
  24. policy_pattern/models/relation.py +44 -0
  25. policy_pattern/models/subject.py +22 -0
  26. policy_pattern/models/subject_set.py +56 -0
  27. policy_pattern/models/tuple.py +52 -0
  28. policy_pattern/models/tuple_to_userset.py +51 -0
  29. policy_pattern/models/type.py +71 -0
  30. policy_pattern/ports/__init__.py +7 -0
  31. policy_pattern/ports/tuple_store.py +118 -0
  32. policy_pattern/py.typed +0 -0
  33. policy_pattern-0.1.0.dist-info/METADATA +148 -0
  34. policy_pattern-0.1.0.dist-info/RECORD +36 -0
  35. policy_pattern-0.1.0.dist-info/WHEEL +4 -0
  36. policy_pattern-0.1.0.dist-info/licenses/LICENSE.md +201 -0
@@ -0,0 +1,56 @@
1
+ """
2
+ Value Object representing a set of subjects in the authorization graph.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from typing import ClassVar
7
+
8
+ from .identifier import Identifier
9
+ from .object_reference import ObjectReference
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class SubjectSet:
14
+ """
15
+ Value Object representing a set of subjects.
16
+
17
+ A subject set is a reference to *a relation on an object* — not a
18
+ single entity like `DirectSubject`/`ObjectReference`, but the set of
19
+ whoever holds that relation. `group:eng#member` refers to every
20
+ current member of `group:eng`, resolved at evaluation time, not a
21
+ fixed list stored here.
22
+
23
+ This is the other half of `Subject` (`Subject = DirectSubject |
24
+ SubjectSet`): `DirectSubject` names one entity;
25
+ `SubjectSet` names a computation over the graph ("whoever satisfies
26
+ this relation on this object right now"). It is exactly how nested
27
+ groups work in Zanzibar — `group:eng#member@group:backend#member`
28
+ makes every member of `group:backend` a member of `group:eng`,
29
+ without ever copying a membership list.
30
+
31
+ `relation` delegates to `Identifier` like every other identifier-shaped
32
+ field in this model (`ObjectReference.type`/`.id`,
33
+ `Relation.allowed_subject_types` entries) — no separate bounds/charset
34
+ logic, no separate error class needed for it.
35
+
36
+ Example:
37
+ SubjectSet(object=ObjectReference(type="group", id="eng"), relation="member")
38
+
39
+ `object` is validated by `ObjectReference` itself; `relation` is
40
+ validated as an `Identifier` with `field="relation"`.
41
+
42
+ Raises:
43
+ InvalidIdentifierError: Propagated from `Identifier` if `relation` is invalid,
44
+ or from `ObjectReference` if `type`/`id` is invalid.
45
+ """
46
+
47
+ object: ObjectReference
48
+ relation: str
49
+
50
+ _RELATION_IDENTIFIER_FIELD: ClassVar[str] = "relation"
51
+
52
+ def __post_init__(self) -> None:
53
+ """
54
+ Validates the subject set after initialization by delegating to Identifier.
55
+ """
56
+ Identifier(value=self.relation, field=self._RELATION_IDENTIFIER_FIELD)
@@ -0,0 +1,52 @@
1
+ """
2
+ Value Object representing a stored relationship fact in the authorization graph.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from .identifier import Identifier
8
+ from .object_reference import ObjectReference
9
+ from .subject import Subject
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class Tuple:
14
+ """
15
+ Value Object representing one stored fact: `object#relation@subject`.
16
+
17
+ e.g. `document:42#owner@user:john` is
18
+ `Tuple(subject=DirectSubject(object=ObjectReference(type="user", id="john")),
19
+ relation="owner", object=ObjectReference(type="document", id="42"))`.
20
+
21
+ `relation` only names a relation here — it is validated as an
22
+ `Identifier` (non-empty, bounded, printable), but *not* checked against
23
+ an actual `Relation`/`Permission` declaration. `Tuple` has no schema
24
+ context to check that against; confirming `relation` actually names a
25
+ storable `Relation` (never a `Permission`) is `compile()`'s job on the
26
+ `Model`, not this class's.
27
+
28
+ `subject`/`object` are validated by `Subject`'s variants
29
+ (`DirectSubject`/`SubjectSet`) and `ObjectReference` themselves —
30
+ `Tuple` only validates the one field it owns directly.
31
+
32
+ Example:
33
+ Tuple(
34
+ subject=DirectSubject(object=ObjectReference(type="user", id="john")),
35
+ relation="owner",
36
+ object=ObjectReference(type="document", id="42"),
37
+ )
38
+
39
+ Raises:
40
+ InvalidIdentifierError: If `relation` is invalid, or propagated from
41
+ `subject`/`object` construction.
42
+ """
43
+
44
+ subject: Subject
45
+ relation: str
46
+ object: ObjectReference
47
+
48
+ def __post_init__(self) -> None:
49
+ """
50
+ Validates the tuple after initialization by delegating relation to Identifier.
51
+ """
52
+ Identifier(value=self.relation, field="relation")
@@ -0,0 +1,51 @@
1
+ """
2
+ Value Object representing a computed permission expression that traverses to a
3
+ related object via a relation and evaluates a computed permission there.
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from typing import ClassVar
8
+
9
+ from .identifier import Identifier
10
+ from .operators import PermissionExpression
11
+
12
+
13
+ @dataclass(frozen=True, slots=True)
14
+ class TupleToUserset(PermissionExpression):
15
+ """
16
+ Traverses to a related object via `tupleset`, then evaluates `computed` there.
17
+
18
+ This is the one node that is not a set operation on *this* object — it
19
+ jumps to a *different* object first. `document:42#parent@folder:eng`
20
+ plus `TupleToUserset(tupleset="parent", computed="viewer")` reads as
21
+ "find the object(s) related to this one via `parent`, then check
22
+ `viewer` on *that* object instead." It is how folder hierarchies grant
23
+ access to every document underneath without copying a permission onto
24
+ each child individually, and how nested groups
25
+ (`group:eng#member@group:backend#member`) work without flattening
26
+ membership into a stored list.
27
+
28
+ `tupleset` must resolve to a `Relation`, never a `Permission` —
29
+ traversal needs an actual stored edge to follow (`document:42#parent`
30
+ has to exist as data); there is nothing to traverse if `parent` were
31
+ itself a computed rule. `computed` has no such restriction: it is
32
+ evaluated on the object found via `tupleset`, and can be either a
33
+ `Relation` or another `Permission` there, resolved the same way
34
+ `Reference` resolves its own `name`.
35
+
36
+ Example:
37
+ TupleToUserset(tupleset="parent", computed="viewer")
38
+ """
39
+
40
+ tupleset: str
41
+ computed: str
42
+
43
+ _FIELD_TUPLESET_VALUE: ClassVar[str] = "tupleset"
44
+ _FIELD_COMPUTED_VALUE: ClassVar[str] = "computed"
45
+
46
+ def __post_init__(self) -> None:
47
+ """
48
+ Validates `tupleset` and `computed` by delegating to `Identifier`.
49
+ """
50
+ Identifier(value=self.tupleset, field=self._FIELD_TUPLESET_VALUE)
51
+ Identifier(value=self.computed, field=self._FIELD_COMPUTED_VALUE)
@@ -0,0 +1,71 @@
1
+ """
2
+ Value Object representing a declaration mutable at authoring time, but immutable at evaluation time.
3
+ """
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass
7
+ from types import MappingProxyType
8
+ from typing import ClassVar
9
+
10
+ from ..errors import InvalidIdentifierError
11
+ from .identifier import Identifier
12
+ from .operators import PermissionExpression
13
+ from .relation import Relation
14
+
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class Type:
18
+ """
19
+ Value Object representing a type declaration in the authorization graph.
20
+
21
+ A `Type` declares a kind of object, along with the relations and
22
+ permissions it supports.
23
+
24
+ Example:
25
+ Type(name="document", relations={}, permissions={})
26
+
27
+ Raises:
28
+ InvalidIdentifierError: Propagated from `Identifier` if `name`, a
29
+ relation name, or a permission name is invalid, or if `name`
30
+ contains '#' (reserved for userset notation).
31
+ """
32
+
33
+ name: str
34
+ relations: Mapping[str, Relation]
35
+ permissions: Mapping[str, PermissionExpression]
36
+
37
+ _TYPE_NAME_FIELD: ClassVar[str] = "type"
38
+ _RELATION_NAME_FIELD: ClassVar[str] = "relation"
39
+ _PERMISSION_NAME_FIELD: ClassVar[str] = "permission"
40
+
41
+ def __post_init__(self) -> None:
42
+ """
43
+ Validate `name` and every relation/permission name, then make the mappings read-only views.
44
+
45
+ `relations`/`permissions` keys are the actual schema identifiers —
46
+ `Reference`/`tupleset` names resolve against them — so they need the
47
+ same `Identifier` validation as `name` itself, not just the values.
48
+
49
+ `frozen=True` only blocks reassigning `relations`/`permissions`
50
+ themselves — a plain `dict` field would still be mutable in place
51
+ (`type_.relations["x"] = y`). Wrapping a copy in `MappingProxyType`
52
+ closes that gap: neither the returned view nor the caller's
53
+ original dict (once copied) can affect this instance afterward.
54
+ """
55
+ Identifier(value=self.name, field=self._TYPE_NAME_FIELD)
56
+ if "#" in self.name:
57
+ # Reserved for userset notation ("type#relation") inside
58
+ # Relation.allowed_subject_types — _schema_validation.py tells a
59
+ # plain type-name entry apart from a userset one by checking for
60
+ # "#" in the string. If a real Type name could contain "#" too,
61
+ # that check couldn't tell them apart.
62
+ raise InvalidIdentifierError(
63
+ message=f"type name {self.name!r} cannot contain '#' — reserved for userset notation",
64
+ related=f"field={self._TYPE_NAME_FIELD!r}",
65
+ )
66
+ for relation_name in self.relations:
67
+ Identifier(value=relation_name, field=self._RELATION_NAME_FIELD)
68
+ for permission_name in self.permissions:
69
+ Identifier(value=permission_name, field=self._PERMISSION_NAME_FIELD)
70
+ object.__setattr__(self, "relations", MappingProxyType(dict(self.relations)))
71
+ object.__setattr__(self, "permissions", MappingProxyType(dict(self.permissions)))
@@ -0,0 +1,7 @@
1
+ from .tuple_store import ReadResult, TupleStore, View
2
+
3
+ __all__ = [
4
+ "ReadResult",
5
+ "TupleStore",
6
+ "View",
7
+ ]
@@ -0,0 +1,118 @@
1
+ """
2
+ Storage port: TupleStore/View abstractions and the bounded-read result type.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from contextlib import AbstractContextManager
7
+ from dataclasses import dataclass
8
+
9
+ from ..models import Mutation, ObjectReference, Subject, Tuple
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class ReadResult:
14
+ """
15
+ Result of a bounded `View.read_relation()` call.
16
+
17
+ `docs/semantics.md` requires that a truncated enumeration never be
18
+ representable as a complete one — `complete` carries that fact in the
19
+ type itself rather than a convention the caller has to already know
20
+ (e.g. a `limit + 1` sentinel). `tuples` is a `frozenset` — it carries no
21
+ order of its own. When more matching tuples exist than the caller's
22
+ `limit` allowed, `tuples` holds at most `limit` of them, deterministically
23
+ selected by the adapter's own canonical ranking (never arbitrary or
24
+ hash-order-dependent — see `MemoryTupleStore.read_relation`), and
25
+ `complete` is `False`; otherwise `tuples` holds all matches and
26
+ `complete` is `True`.
27
+
28
+ Example:
29
+ ReadResult(tuples=frozenset({...}), complete=True)
30
+ """
31
+
32
+ tuples: frozenset[Tuple]
33
+ complete: bool
34
+
35
+
36
+ class TupleStore(ABC):
37
+ """
38
+ Storage port for authorization tuples — mutations plus consistent-snapshot reads.
39
+
40
+ Deliberately schema-agnostic: this class stores and compares
41
+ structurally well-formed `(subject, relation, object)` triples and
42
+ knows nothing about `Relation` vs `Permission`, rewrite rules, or
43
+ `compile()` — that separation is what keeps a `PostgresTupleStore`
44
+ substitutable for `MemoryTupleStore` without either one leaking into
45
+ the rewrite engine (`docs/architecture.md`).
46
+
47
+ `write_tuples()` is the only mutating method an adapter must implement.
48
+ `delete_tuples()` is concrete, not abstract — a pure deletion is already
49
+ representable as `Mutation(additions=frozenset(), deletions=tuples)`,
50
+ so every adapter gets deletion for free instead of reimplementing the
51
+ same atomicity/disjointness guarantees `write_tuples()` already provides.
52
+
53
+ Example:
54
+ store.write_tuples(Mutation(additions=frozenset({t}), deletions=frozenset()))
55
+ store.delete_tuples(frozenset({t}))
56
+ """
57
+
58
+ @abstractmethod
59
+ def write_tuples(self, mutation: Mutation) -> None:
60
+ """
61
+ Atomically applies a mutation's additions and deletions.
62
+
63
+ See `docs/semantics.md` § Write mutations for the full contract
64
+ (atomicity, idempotency, visibility point) — this method's
65
+ implementation must satisfy it, this docstring does not restate it.
66
+ """
67
+ raise NotImplementedError
68
+
69
+ def delete_tuples(self, tuples: frozenset[Tuple]) -> None:
70
+ """
71
+ Deletes the given tuples atomically, as a deletion-only Mutation.
72
+ """
73
+ self.write_tuples(Mutation(additions=frozenset(), deletions=tuples))
74
+
75
+ @abstractmethod
76
+ def snapshot(self) -> AbstractContextManager["View"]:
77
+ """
78
+ Opens an evaluation-scoped, read-only view pinned to one consistent snapshot.
79
+
80
+ See `docs/semantics.md` § Snapshot consistency of check() — every
81
+ read issued through the same `View` must observe the same logical
82
+ store state for the view's whole lifetime.
83
+ """
84
+ raise NotImplementedError
85
+
86
+
87
+ class View(ABC):
88
+ """
89
+ Read-only handle pinned to one consistent snapshot of a `TupleStore`.
90
+
91
+ No write method exists here at all — `docs/semantics.md` already
92
+ decided mutations are issued against `TupleStore` itself, never through
93
+ a view; this class makes that an actual absence of a method, not just a
94
+ sentence in a doc.
95
+
96
+ Example:
97
+ with store.snapshot() as view:
98
+ view.read_tuple(subject, "owner", object)
99
+ view.read_relation(object, "owner", limit=100)
100
+ """
101
+
102
+ @abstractmethod
103
+ def read_tuple(self, subject: Subject, relation: str, object: ObjectReference) -> bool:
104
+ """
105
+ Returns whether the exact tuple `(subject, relation, object)` exists in this view.
106
+ """
107
+ raise NotImplementedError
108
+
109
+ @abstractmethod
110
+ def read_relation(self, object: ObjectReference, relation: str, limit: int) -> ReadResult:
111
+ """
112
+ Enumerates subjects/usersets holding `relation` on `object`, bounded by `limit`.
113
+
114
+ See `ReadResult`'s docstring for what `complete=False` means and
115
+ when it is set. An adapter must not materialize more than
116
+ `limit + 1` matching tuples before returning — that would violate the contract.
117
+ """
118
+ raise NotImplementedError
File without changes
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: policy-pattern
3
+ Version: 0.1.0
4
+ Summary: A minimal, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar.
5
+ Keywords: python,authorization,access-control,rebac,zanzibar,security
6
+ Author: Guillem Pozo Sebastián
7
+ Author-email: Guillem Pozo Sebastián <58363308+guillem-ps@users.noreply.github.com>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE.md
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Typing :: Typed
13
+ Classifier: Topic :: Security
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Requires-Python: >=3.12
21
+ Project-URL: Homepage, https://github.com/guillem-ps/policy-pattern
22
+ Project-URL: Repository, https://github.com/guillem-ps/policy-pattern
23
+ Project-URL: Issues, https://github.com/guillem-ps/policy-pattern/issues
24
+ Description-Content-Type: text/markdown
25
+
26
+ # policy-pattern
27
+
28
+ > A small, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar.
29
+
30
+ <p align="center">
31
+ <a href="https://github.com/guillem-ps/policy-pattern/actions/workflows/test.yaml" target="_blank">
32
+ <img src="https://github.com/guillem-ps/policy-pattern/actions/workflows/test.yaml/badge.svg" alt="Test Pipeline">
33
+ </a>
34
+ <a href="https://github.com/guillem-ps/policy-pattern/actions/workflows/lint.yaml" target="_blank">
35
+ <img src="https://github.com/guillem-ps/policy-pattern/actions/workflows/lint.yaml/badge.svg" alt="Lint Pipeline">
36
+ </a>
37
+ <a href="https://codecov.io/gh/guillem-ps/policy-pattern" target="_blank">
38
+ <img src="https://codecov.io/gh/guillem-ps/policy-pattern/graph/badge.svg" alt="Coverage Pipeline">
39
+ </a>
40
+ <a href="pyproject.toml" target="_blank">
41
+ <img src="https://img.shields.io/badge/version-0.1.0-blue" alt="Package Version">
42
+ </a>
43
+ <a href="pyproject.toml" target="_blank">
44
+ <img src="https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue" alt="Supported Python Versions">
45
+ </a>
46
+ </p>
47
+
48
+ `policy-pattern` is a standalone Python library for relationship-based access control (ReBAC). It models
49
+ authorization as relationships between subjects and objects and evaluates those relationships through composable
50
+ authorization rules.
51
+
52
+ - **No authorization server**
53
+ - **No required framework**
54
+ - **No required database**
55
+ - **No network hop**
56
+
57
+ The library is designed to be embedded directly into Python applications that need richer authorization semantics
58
+ than traditional role-based access control.
59
+
60
+ > **Status:** v1 is code-complete — model, `compile()`, the storage port, `MemoryTupleStore`, and the `Evaluator`
61
+ > all exist and are tested.
62
+
63
+ See [Architecture § Scope](docs/architecture.md#scope) for what's planned (v1.x/v2) and unscheduled ideas.
64
+
65
+ ---
66
+
67
+ ## Table of Contents
68
+
69
+ * [Example](#example)
70
+ * [Documentation](#documentation)
71
+ * [Contributing](#contributing)
72
+ * [License](#license)
73
+ * [References](#references)
74
+
75
+ ---
76
+
77
+ ## Example
78
+
79
+ ```python
80
+ model = Model()
81
+ model.types["document"] = Type(
82
+ name="document",
83
+ relations={
84
+ "owner": Relation(allowed_subject_types=("user",)),
85
+ "parent": Relation(allowed_subject_types=("folder",)),
86
+ },
87
+ permissions={
88
+ "viewer": Union(left=Reference(name="owner"), right=TupleToUserset(tupleset="parent", computed="viewer")),
89
+ },
90
+ )
91
+ compiled = model.compile()
92
+
93
+ evaluator = Evaluator(model=compiled, store=store)
94
+ evaluator.check(alice, "viewer", document_42)
95
+ ```
96
+
97
+ `viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under
98
+ `folder:engineering` inherits access from that folder without copying a permission onto every document.
99
+
100
+ See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested
101
+ groups, `EvaluationBudget`, and error handling.
102
+
103
+ ---
104
+
105
+ ## Documentation
106
+
107
+ The repository uses specifications and architecture documents as part of the implementation contract — they
108
+ describe **what must be true**, independent of any one adapter's implementation.
109
+
110
+ ```text
111
+ docs/
112
+ ├── README.md — documentation index, start here
113
+ ├── usage/README.md — worked examples for every rewrite operator, budgets, error handling
114
+ ├── architecture.md — the model shape, compile(), the storage port, the evaluator, and scope/roadmap
115
+ ├── semantics.md — the storage contract every TupleStore adapter must satisfy
116
+ └── errors.md — the PPxxx error code ranges and what each one means
117
+ ```
118
+
119
+ Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then
120
+ [Architecture](docs/architecture.md) to understand why the model is shaped the way it is.
121
+
122
+ ---
123
+
124
+ ## Contributing
125
+
126
+ We love community help! Before you open an issue or pull request, please read:
127
+
128
+ * [How to Contribute](.github/CONTRIBUTING.md)
129
+ * [Code of Conduct](.github/CODE_OF_CONDUCT.md)
130
+ * [Security Policy](SECURITY.md)
131
+
132
+ ---
133
+
134
+ ## License
135
+
136
+ Licensed under the [Apache License 2.0](LICENSE.md).
137
+
138
+ ---
139
+
140
+ ## References
141
+
142
+ The design is influenced by authorization systems and literature including:
143
+
144
+ * [Google Zanzibar](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/)
145
+ * [OpenFGA](https://openfga.dev)
146
+ * [SpiceDB](https://github.com/authzed/spicedb) / [AuthZed](https://authzed.com)
147
+
148
+ `policy-pattern` is an independent project and is not an implementation of, or affiliated with, those projects.
@@ -0,0 +1,36 @@
1
+ policy_pattern/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ policy_pattern/adapters/__init__.py,sha256=J2GWAI4H1SoveZ9ocF6D2Cg-aozMtKlKV_pDKs2Jq3o,69
3
+ policy_pattern/adapters/memory.py,sha256=wFWt5P6QJxtqkz7onuYdCt8-ISz9FoVj77VvUb_8G0U,5027
4
+ policy_pattern/errors/__init__.py,sha256=iJye0Mnn6n6JcN630fTCv5bxhFw5syV3Hs4eu-FD2D0,603
5
+ policy_pattern/errors/base_error.py,sha256=aXuhBzC0s2vMPcVy8OTXYuS17Pjhz9eCYQag0FbCJcY,967
6
+ policy_pattern/errors/evaluation_error.py,sha256=nB1sD5uL5FYxwXFgA28tE38XnKuSSu8CdmUdGzowQZw,463
7
+ policy_pattern/errors/identifier_error.py,sha256=Rd8dhzIKKCjAm46wjsJIJA7AVsjs3NhWrlJZ9csqRpQ,320
8
+ policy_pattern/errors/model_validation_error.py,sha256=LavYcAyQpqHJKoaCtXG_TC0Qv9zmyCTAeCerWHVrvj8,339
9
+ policy_pattern/errors/mutation_error.py,sha256=kazfo30ma3ZZq1LvOcjZK4esMUzxphB4n6hrSL9hmjk,314
10
+ policy_pattern/errors/relation_error.py,sha256=kWV-yxr7I-EmtHL3xnEqrTrkYNNU44bGswhwoV56W9c,314
11
+ policy_pattern/errors/unknown_name_error.py,sha256=2jzHRzlQSF4K3OL9oS2aGlS08EiKBSIIEIbzpZfi1GU,445
12
+ policy_pattern/errors/unknown_type_error.py,sha256=huIxIJVM1p6ngqdzq7sN9CQXY7shmhvSunZsb09y9Wk,446
13
+ policy_pattern/evaluator.py,sha256=KmM4RSfZ4fV2Ft2IJ37Wpt5-me3JI-lhKwyDwiVmSgM,12211
14
+ policy_pattern/models/__init__.py,sha256=pGhe4AnuH4Em9Iz9lpGL2hflt-ko1OshLSmrvWNs0Tg,837
15
+ policy_pattern/models/_schema_validation.py,sha256=W712R6iBQ4rM0HvqUOyXpqy5X5ys2vKRMzqdKdscMYw,6140
16
+ policy_pattern/models/compiled_model.py,sha256=Q00lhNwGl1jLlEuqbc57MZIKs5haJdKgp_qLjSPq28w,1581
17
+ policy_pattern/models/direct_subject.py,sha256=Q-77hHX5Hk3VMZGuFDK6uQJIX3k2Yoe_LMGstAyupfU,1889
18
+ policy_pattern/models/identifier.py,sha256=Jyt2oXiX2fWQW31UPFJXVnfVq7wCnseJg0DrJeemDYQ,1747
19
+ policy_pattern/models/model.py,sha256=2PdajMGKppBeRRnzRjyIOR-S1lWzBLfAGAvkT15X8Ro,1739
20
+ policy_pattern/models/mutation.py,sha256=B5IbfaTdvwvmrBQtXrciBFfe5qHGt0DCOYRgC2zdiKY,1660
21
+ policy_pattern/models/object_reference.py,sha256=r7tErTsOkFZSJk3gHXMRL2txiv1cyQdPRpT_nkcOWDY,1240
22
+ policy_pattern/models/operators.py,sha256=tVVBwWloJ2mL9aKpqjZERBO2MjuAhT0Kv1FfUFcEgCI,4337
23
+ policy_pattern/models/reference.py,sha256=WFbZ8TVukEleDbIpS32lWNMYC6jfxqimiYDyuvbsTRo,1252
24
+ policy_pattern/models/relation.py,sha256=xGz4GxfboiRf1i3HJe-1bAR4iFMViVc656Ykob-iCro,1473
25
+ policy_pattern/models/subject.py,sha256=isIXEWD2j1YCCyCn4C46Ffa7QSSd0saQ6rmr-9Uuoo4,1052
26
+ policy_pattern/models/subject_set.py,sha256=lRrOyP_SWPbACxs9rityDa1P570qGvSlZDxon0xmHUA,2089
27
+ policy_pattern/models/tuple.py,sha256=ty5dcn9mBpWlZlJlVAcwG1MaGxIyq1MzeSJRvoWw_WA,1790
28
+ policy_pattern/models/tuple_to_userset.py,sha256=IMGlZG5tJOHai1sddxMIM-4TbJwFMdJ8xE2B0Y1wPC8,2055
29
+ policy_pattern/models/type.py,sha256=98cXOUOkTU1XEpLKWk18MS-Xzn0psWMDh8Jm6ykvKJA,3048
30
+ policy_pattern/ports/__init__.py,sha256=qAg-9xRvVCzP80kYf08R-Mk7GraLokQpg4WC28xhQiA,117
31
+ policy_pattern/ports/tuple_store.py,sha256=qzHIlgUQVc5bHZPbu0NWJXoD8w2NV9XPBtHwoAzTbKw,4619
32
+ policy_pattern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ policy_pattern-0.1.0.dist-info/licenses/LICENSE.md,sha256=pBo98b3diIHXZ6w8GMvFkU9_m_uIrPzHvUu9-vqIbTI,11353
34
+ policy_pattern-0.1.0.dist-info/WHEEL,sha256=rp-k_VZaX8oABFEtLwG_Df95p8LSKt9-uPyQ4jKH0P4,81
35
+ policy_pattern-0.1.0.dist-info/METADATA,sha256=mgX-g3yVTFMjZvPUqeW-tZdHJzStp5aWltr4asCIao0,5687
36
+ policy_pattern-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.22
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any