policy-pattern 0.3.0__tar.gz → 0.4.0__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.0}/PKG-INFO +1 -1
  2. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/evaluator.py +37 -9
  3. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/pyproject.toml +1 -1
  4. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/pyproject.toml.orig +1 -1
  5. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/LICENSE.md +0 -0
  6. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/README.md +0 -0
  7. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/__init__.py +0 -0
  8. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/adapters/__init__.py +0 -0
  9. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/adapters/_db_protocols.py +0 -0
  10. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/adapters/_tuple_mapping.py +0 -0
  11. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/adapters/memory.py +0 -0
  12. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/adapters/postgres.py +0 -0
  13. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/__init__.py +0 -0
  14. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/base_error.py +0 -0
  15. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/evaluation_error.py +0 -0
  16. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/identifier_error.py +0 -0
  17. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/index_entry_too_large_error.py +0 -0
  18. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/model_validation_error.py +0 -0
  19. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/mutation_error.py +0 -0
  20. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/relation_error.py +0 -0
  21. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/unknown_name_error.py +0 -0
  22. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/errors/unknown_type_error.py +0 -0
  23. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/__init__.py +0 -0
  24. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/_schema_validation.py +0 -0
  25. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/compiled_model.py +0 -0
  26. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/direct_subject.py +0 -0
  27. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/identifier.py +0 -0
  28. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/model.py +0 -0
  29. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/mutation.py +0 -0
  30. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/object_reference.py +0 -0
  31. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/operators.py +0 -0
  32. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/reference.py +0 -0
  33. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/relation.py +0 -0
  34. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/subject.py +0 -0
  35. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/subject_set.py +0 -0
  36. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/tuple.py +0 -0
  37. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/tuple_to_userset.py +0 -0
  38. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/models/type.py +0 -0
  39. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/ports/__init__.py +0 -0
  40. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/ports/tuple_store.py +0 -0
  41. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/policy_pattern/py.typed +0 -0
  42. {policy_pattern-0.3.0 → policy_pattern-0.4.0}/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.0
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
@@ -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.0"
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"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "policy-pattern"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "A minimal, embeddable ReBAC authorization engine for Python, inspired by Google Zanzibar."
5
5
  readme = "README.md"
6
6
  authors = [
File without changes