policy-pattern 0.3.0__tar.gz → 0.4.1__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.
Files changed (42) hide show
  1. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/PKG-INFO +8 -11
  2. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/README.md +7 -10
  3. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/evaluator.py +37 -9
  4. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/pyproject.toml +2 -2
  5. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/pyproject.toml.orig +2 -2
  6. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/LICENSE.md +0 -0
  7. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/__init__.py +0 -0
  8. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/__init__.py +0 -0
  9. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/_db_protocols.py +0 -0
  10. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/_tuple_mapping.py +0 -0
  11. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/memory.py +0 -0
  12. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/adapters/postgres.py +0 -0
  13. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/__init__.py +0 -0
  14. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/base_error.py +0 -0
  15. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/evaluation_error.py +0 -0
  16. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/identifier_error.py +0 -0
  17. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/index_entry_too_large_error.py +0 -0
  18. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/model_validation_error.py +0 -0
  19. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/mutation_error.py +0 -0
  20. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/relation_error.py +0 -0
  21. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/unknown_name_error.py +0 -0
  22. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/errors/unknown_type_error.py +0 -0
  23. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/__init__.py +0 -0
  24. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/_schema_validation.py +0 -0
  25. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/compiled_model.py +0 -0
  26. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/direct_subject.py +0 -0
  27. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/identifier.py +0 -0
  28. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/model.py +0 -0
  29. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/mutation.py +0 -0
  30. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/object_reference.py +0 -0
  31. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/operators.py +0 -0
  32. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/reference.py +0 -0
  33. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/relation.py +0 -0
  34. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/subject.py +0 -0
  35. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/subject_set.py +0 -0
  36. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/tuple.py +0 -0
  37. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/tuple_to_userset.py +0 -0
  38. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/models/type.py +0 -0
  39. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/ports/__init__.py +0 -0
  40. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/ports/tuple_store.py +0 -0
  41. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/py.typed +0 -0
  42. {policy_pattern-0.3.0 → policy_pattern-0.4.1}/policy_pattern/sql/postgres/0001_create_policy_tuples.sql +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policy-pattern
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: A minimal, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar.
5
5
  Keywords: python,authorization,access-control,rebac,zanzibar,security
6
6
  Author: Guillem Pozo Sebastián
@@ -45,8 +45,7 @@ Description-Content-Type: text/markdown
45
45
  </a>
46
46
  </p>
47
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
48
+ `policy-pattern` is a standalone Python library for relationship-based access control (ReBAC). It models authorization as relationships between subjects and objects and evaluates those relationships through composable
50
49
  authorization rules.
51
50
 
52
51
  - **No authorization server**
@@ -131,18 +130,15 @@ evaluator = Evaluator(model=compiled, store=store)
131
130
  evaluator.check(alice, "viewer", document_42) # True — alice owns the parent folder
132
131
  ```
133
132
 
134
- `viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under
135
- `folder:engineering` inherits access from that folder without copying a permission onto every document.
133
+ `viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under `folder:engineering` inherits access from that folder without copying a permission onto every document.
136
134
 
137
- See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested
138
- groups, `EvaluationBudget`, and error handling.
135
+ See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested groups, `EvaluationBudget`, and error handling.
139
136
 
140
137
  ---
141
138
 
142
139
  ## Documentation
143
140
 
144
- The repository uses specifications and architecture documents as part of the implementation contract — they
145
- describe **what must be true**, independent of any one adapter's implementation.
141
+ The repository uses specifications and architecture documents as part of the implementation contract — they describe **what must be true**, independent of any one adapter's implementation.
146
142
 
147
143
  ```text
148
144
  docs/
@@ -153,8 +149,9 @@ docs/
153
149
  └── errors.md — the PPxxx error code ranges and what each one means
154
150
  ```
155
151
 
156
- Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then
157
- [Architecture](docs/architecture.md) to understand why the model is shaped the way it is.
152
+ Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then [Architecture](docs/architecture.md) to understand why the model is shaped the way it is.
153
+
154
+ See [`CODE_STYLE.md`](.github/CODE_STYLE.md) and [`TESTING.md`](TESTING.md) for contributor conventions no tool enforces.
158
155
 
159
156
  ---
160
157
 
@@ -20,8 +20,7 @@
20
20
  </a>
21
21
  </p>
22
22
 
23
- `policy-pattern` is a standalone Python library for relationship-based access control (ReBAC). It models
24
- authorization as relationships between subjects and objects and evaluates those relationships through composable
23
+ `policy-pattern` is a standalone Python library for relationship-based access control (ReBAC). It models authorization as relationships between subjects and objects and evaluates those relationships through composable
25
24
  authorization rules.
26
25
 
27
26
  - **No authorization server**
@@ -106,18 +105,15 @@ evaluator = Evaluator(model=compiled, store=store)
106
105
  evaluator.check(alice, "viewer", document_42) # True — alice owns the parent folder
107
106
  ```
108
107
 
109
- `viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under
110
- `folder:engineering` inherits access from that folder without copying a permission onto every document.
108
+ `viewer` here means "the document's own owner, or the owner of whatever folder it lives in" — a document under `folder:engineering` inherits access from that folder without copying a permission onto every document.
111
109
 
112
- See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested
113
- groups, `EvaluationBudget`, and error handling.
110
+ See the [Usage Guide](docs/usage/README.md) for the full, runnable walkthrough — every rewrite operator, nested groups, `EvaluationBudget`, and error handling.
114
111
 
115
112
  ---
116
113
 
117
114
  ## Documentation
118
115
 
119
- The repository uses specifications and architecture documents as part of the implementation contract — they
120
- describe **what must be true**, independent of any one adapter's implementation.
116
+ The repository uses specifications and architecture documents as part of the implementation contract — they describe **what must be true**, independent of any one adapter's implementation.
121
117
 
122
118
  ```text
123
119
  docs/
@@ -128,8 +124,9 @@ docs/
128
124
  └── errors.md — the PPxxx error code ranges and what each one means
129
125
  ```
130
126
 
131
- Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then
132
- [Architecture](docs/architecture.md) to understand why the model is shaped the way it is.
127
+ Start with the [Usage Guide](docs/usage/README.md) to see the library working end to end, then [Architecture](docs/architecture.md) to understand why the model is shaped the way it is.
128
+
129
+ See [`CODE_STYLE.md`](.github/CODE_STYLE.md) and [`TESTING.md`](TESTING.md) for contributor conventions no tool enforces.
133
130
 
134
131
  ---
135
132
 
@@ -2,6 +2,7 @@
2
2
  Evaluator: resolves check() over a CompiledModel and a TupleStore.
3
3
  """
4
4
 
5
+ from collections.abc import Sequence
5
6
  from dataclasses import dataclass, field
6
7
 
7
8
  from .errors import EvaluationError, UnknownNameError, UnknownTypeError
@@ -24,7 +25,11 @@ from .ports import TupleStore, View
24
25
  @dataclass(frozen=True, slots=True)
25
26
  class EvaluationBudget:
26
27
  """
27
- Per-`check()` resource limits.
28
+ Resource limits for one `check()` or `check_many()` call.
29
+
30
+ `max_nodes` and `max_store_reads` accumulate across a `check_many()` call.
31
+ `max_depth` bounds the live `context.visited` frontier and resets as each
32
+ request finishes, so it does not accumulate across the batch.
28
33
 
29
34
  No `__post_init__` validation — a degenerate value (e.g. `max_depth=0`)
30
35
  is well-defined without a guard: it simply fails on the first node
@@ -35,16 +40,16 @@ class EvaluationBudget:
35
40
  EvaluationBudget(max_depth=10, max_nodes=500, max_store_reads=100)
36
41
  """
37
42
 
38
- # Maximum recursion depth for a single `check()` call. Each recursive call
43
+ # Maximum recursion depth for one call. Each recursive call
39
44
  # to `_check_name()` adds one to the depth; exceeding this limit raises an
40
45
  # `EvaluationError`.
41
46
  max_depth: int = 25
42
47
 
43
- # Maximum number of nodes visited during a single `check()` call. Each
48
+ # Maximum number of nodes visited during one call. Each
44
49
  # call to `_check_name()` increments this count; exceeding this limit raises an `EvaluationError`.
45
50
  max_nodes: int = 10_000
46
51
 
47
- # Maximum number of store reads during a single `check()` call. Each call
52
+ # Maximum number of store reads during one call. Each call
48
53
  # to `_charge_read()` increments this count; exceeding this limit raises an `EvaluationError`.
49
54
  max_store_reads: int = 1_000
50
55
 
@@ -52,7 +57,7 @@ class EvaluationBudget:
52
57
  @dataclass(slots=True)
53
58
  class EvaluationContext:
54
59
  """
55
- Live bookkeeping for exactly one `check()` call.
60
+ Live bookkeeping for exactly one `check()` or `check_many()` call.
56
61
 
57
62
  Deliberately mutable, unlike almost everything else in this package —
58
63
  this is not a domain value, it is the in-flight state of one
@@ -63,13 +68,19 @@ class EvaluationContext:
63
68
  (added before recursing, discarded after returning) — anything
64
69
  permanently resolved lives in `memo` instead, so the two never overlap
65
70
  in meaning.
71
+
72
+ The key carries the subject because one `check()` has a constant subject,
73
+ while `check_many()` can mix subjects; a narrower key could reuse one
74
+ subject's cached decision for another and cause a false allow.
66
75
  """
67
76
 
68
77
  view: View
69
78
  budget: EvaluationBudget
70
- visited: set[tuple[ObjectReference, str]] = field(default_factory=lambda: set[tuple[ObjectReference, str]]())
71
- memo: dict[tuple[ObjectReference, str], bool] = field(
72
- default_factory=lambda: dict[tuple[ObjectReference, str], bool]()
79
+ visited: set[tuple[Subject, ObjectReference, str]] = field(
80
+ default_factory=lambda: set[tuple[Subject, ObjectReference, str]]()
81
+ )
82
+ memo: dict[tuple[Subject, ObjectReference, str], bool] = field(
83
+ default_factory=lambda: dict[tuple[Subject, ObjectReference, str], bool]()
73
84
  )
74
85
  node_count: int = 0
75
86
  store_read_count: int = 0
@@ -120,11 +131,28 @@ class Evaluator:
120
131
  context = EvaluationContext(view=view, budget=budget)
121
132
  return self._check_name(subject, name, object, context)
122
133
 
134
+ def check_many(
135
+ self,
136
+ requests: Sequence[tuple[Subject, str, ObjectReference]],
137
+ budget: EvaluationBudget | None = None,
138
+ ) -> list[bool]:
139
+ """
140
+ Returns one decision per request, preserving input order and duplicates.
141
+
142
+ The whole batch shares one snapshot, context, and budget. An error on
143
+ any request aborts the call and propagates without partial results.
144
+ """
145
+ if budget is None:
146
+ budget = EvaluationBudget()
147
+ with self._store.snapshot() as view:
148
+ context = EvaluationContext(view=view, budget=budget)
149
+ return [self._check_name(subject, name, object, context) for subject, name, object in requests]
150
+
123
151
  def _check_name(self, subject: Subject, name: str, object: ObjectReference, context: EvaluationContext) -> bool:
124
152
  """
125
153
  Dispatches (object, name) to a relation or permission check, with cycle/memo/budget bookkeeping.
126
154
  """
127
- key = (object, name)
155
+ key = (subject, object, name)
128
156
  if key in context.memo:
129
157
  return context.memo[key]
130
158
  if key in context.visited:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "policy-pattern"
3
- version = "0.3.0"
3
+ version = "0.4.1"
4
4
  description = "A minimal, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -38,7 +38,7 @@ Repository = "https://github.com/guillem-ps/policy-pattern"
38
38
  Issues = "https://github.com/guillem-ps/policy-pattern/issues"
39
39
 
40
40
  [build-system]
41
- requires = ["uv_build>=0.11.16,<0.13.0"]
41
+ requires = ["uv_build>=0.11.16,<0.14.0"]
42
42
  build-backend = "uv_build"
43
43
 
44
44
  [tool.uv.build-backend]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "policy-pattern"
3
- version = "0.3.0"
3
+ version = "0.4.1"
4
4
  description = "A minimal, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -37,7 +37,7 @@ Repository = "https://github.com/guillem-ps/policy-pattern"
37
37
  Issues = "https://github.com/guillem-ps/policy-pattern/issues"
38
38
 
39
39
  [build-system]
40
- requires = ["uv_build>=0.11.16,<0.13.0"]
40
+ requires = ["uv_build>=0.11.16,<0.14.0"]
41
41
  build-backend = "uv_build"
42
42
 
43
43
  [tool.uv.build-backend]