proactive-gate 0.2.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.
- proactive_gate-0.2.1/.gitignore +11 -0
- proactive_gate-0.2.1/PKG-INFO +131 -0
- proactive_gate-0.2.1/README.md +100 -0
- proactive_gate-0.2.1/pyproject.toml +51 -0
- proactive_gate-0.2.1/src/proactive_gate/__init__.py +31 -0
- proactive_gate-0.2.1/src/proactive_gate/checks.py +522 -0
- proactive_gate-0.2.1/src/proactive_gate/clock.py +40 -0
- proactive_gate-0.2.1/src/proactive_gate/conformance.py +134 -0
- proactive_gate-0.2.1/src/proactive_gate/gate.py +395 -0
- proactive_gate-0.2.1/src/proactive_gate/policy.py +94 -0
- proactive_gate-0.2.1/src/proactive_gate/presets.py +128 -0
- proactive_gate-0.2.1/src/proactive_gate/py.typed +0 -0
- proactive_gate-0.2.1/src/proactive_gate/stores.py +168 -0
- proactive_gate-0.2.1/src/proactive_gate/types.py +221 -0
- proactive_gate-0.2.1/tests/conftest.py +6 -0
- proactive_gate-0.2.1/tests/test_clock.py +35 -0
- proactive_gate-0.2.1/tests/test_conformance.py +37 -0
- proactive_gate-0.2.1/tests/test_stores.py +95 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: proactive-gate
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Decide whether a proactive assistant may speak now: ordered checks, budgets, quiet hours, JSON policies, shared conformance fixtures with the TypeScript package.
|
|
5
|
+
Project-URL: Homepage, https://bubblegunn.github.io/proactive-gate/
|
|
6
|
+
Project-URL: Repository, https://github.com/Bubblegunn/proactive-gate
|
|
7
|
+
Project-URL: Issues, https://github.com/Bubblegunn/proactive-gate/issues
|
|
8
|
+
Project-URL: Specification, https://github.com/Bubblegunn/proactive-gate/tree/main/spec
|
|
9
|
+
Project-URL: Changelog, https://github.com/Bubblegunn/proactive-gate/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Efe Genc
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
Keywords: agents,notifications,policy,proactive,quiet-hours,rate-limit
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Communications
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
27
|
+
Requires-Dist: redis>=5; extra == 'dev'
|
|
28
|
+
Provides-Extra: redis
|
|
29
|
+
Requires-Dist: redis>=5; extra == 'redis'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# proactive-gate for Python
|
|
33
|
+
|
|
34
|
+
Decide whether a proactive assistant may speak to this person right now. Ordered checks
|
|
35
|
+
(consent, mode, snooze, mute, intensity, quiet hours, trust ramp, dismissal cooldown,
|
|
36
|
+
budgets), JSON policies, platform and regulatory presets, and a trace that says which check
|
|
37
|
+
stopped the message and why.
|
|
38
|
+
|
|
39
|
+
This is the Python sibling of the TypeScript package. Both implement the same behaviour
|
|
40
|
+
contract in [`spec/`](https://github.com/Bubblegunn/proactive-gate/tree/main/spec) and run
|
|
41
|
+
the same fixtures, so a policy written for one behaves the same in the other.
|
|
42
|
+
|
|
43
|
+
Until the first PyPI release, install from the repository:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
git clone https://github.com/Bubblegunn/proactive-gate
|
|
47
|
+
pip install ./proactive-gate/python
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
or, without cloning, `pip install "proactive-gate @ git+https://github.com/Bubblegunn/proactive-gate#subdirectory=python"`.
|
|
51
|
+
Python 3.11 or newer, no runtime dependencies. Add `[redis]` (`pip install "./proactive-gate/python[redis]"`)
|
|
52
|
+
for the Redis store. The PyPI name `proactive-gate` is reserved for this package; the
|
|
53
|
+
release workflow publishes there on the next tag.
|
|
54
|
+
|
|
55
|
+
## Use
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from datetime import datetime, timezone
|
|
59
|
+
from proactive_gate import Candidate, EvaluateInput, Gate, UserState, default_checks
|
|
60
|
+
|
|
61
|
+
gate = Gate(default_checks(daily_limit=3))
|
|
62
|
+
user = UserState.from_dict({
|
|
63
|
+
"id": "u1", "consent": True, "timezone": "Europe/Istanbul",
|
|
64
|
+
"quietHours": {"start": "22:00", "end": "08:00"}, "createdAt": "2026-01-01T00:00:00Z",
|
|
65
|
+
})
|
|
66
|
+
candidate = Candidate(id="c1", type="reminder", priority="normal", surfaces=("push", "feed"))
|
|
67
|
+
inp = EvaluateInput(user, candidate, datetime.now(timezone.utc))
|
|
68
|
+
|
|
69
|
+
decision = gate.evaluate(inp)
|
|
70
|
+
if decision.allowed and gate.commit(decision, inp):
|
|
71
|
+
send(candidate, decision.surfaces)
|
|
72
|
+
else:
|
|
73
|
+
print(decision.rejected_by, decision.reason)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`evaluate` reads; `commit` is the atomic increment right before you send, and it is
|
|
77
|
+
idempotent on `decision.id`. Every decision carries `trace`, one entry per check that ran.
|
|
78
|
+
|
|
79
|
+
## A policy is data
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import json
|
|
83
|
+
from proactive_gate import Gate
|
|
84
|
+
|
|
85
|
+
gate = Gate.from_policy(json.load(open("policy.json")))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`policy.json` is the same document the TypeScript package and the CLI read
|
|
89
|
+
(`{"specVersion": "1.0.0", "checks": [{"id": "consent"}, {"preset": "usTcpa"}, ...]}`).
|
|
90
|
+
Unknown check ids and presets raise `ValueError` naming the known ones; `shadow: true`
|
|
91
|
+
keeps a check observing without letting it decide.
|
|
92
|
+
|
|
93
|
+
## Async
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from proactive_gate import AsyncGate, RedisStore
|
|
97
|
+
import redis.asyncio as redis
|
|
98
|
+
|
|
99
|
+
gate = AsyncGate.from_policy(policy, RedisStore(redis.from_url("redis://localhost")))
|
|
100
|
+
decision = await gate.evaluate(inp)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`Gate` and `AsyncGate` share one decision loop (`Evaluation`): a check names the store keys
|
|
104
|
+
it needs and decides purely from their values, so the two gates cannot drift. `decide()`
|
|
105
|
+
runs that loop with values you supply, for tests with no store at all.
|
|
106
|
+
|
|
107
|
+
## Stores
|
|
108
|
+
|
|
109
|
+
`MemoryStore` (in-process), `SqliteStore` (standard library, one file per host),
|
|
110
|
+
`AsyncMemoryStore`, and `RedisStore` over `redis.asyncio` (INCR, then EXPIRE on the first
|
|
111
|
+
increment). Any object with `get`, `set`, `incr` and `delete` works.
|
|
112
|
+
|
|
113
|
+
## Presets
|
|
114
|
+
|
|
115
|
+
`proactive_gate.presets` holds the same fourteen presets as the TypeScript package, each with
|
|
116
|
+
its source URLs and a note on what it leaves out: LINE, WeChat, WeCom, Kakao, Korea's Network
|
|
117
|
+
Act, Japan's anti-spam law, China's minor mode, US TCPA, EU ePrivacy, Telegram and Slack.
|
|
118
|
+
They are reviewable defaults, not legal advice.
|
|
119
|
+
|
|
120
|
+
## Conformance
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
pytest
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
runs every fixture under `../spec/fixtures` through both gates. A fixture the Python package
|
|
127
|
+
cannot yet satisfy is listed in `spec/skip/python.txt` with a reason; the list is empty.
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# proactive-gate for Python
|
|
2
|
+
|
|
3
|
+
Decide whether a proactive assistant may speak to this person right now. Ordered checks
|
|
4
|
+
(consent, mode, snooze, mute, intensity, quiet hours, trust ramp, dismissal cooldown,
|
|
5
|
+
budgets), JSON policies, platform and regulatory presets, and a trace that says which check
|
|
6
|
+
stopped the message and why.
|
|
7
|
+
|
|
8
|
+
This is the Python sibling of the TypeScript package. Both implement the same behaviour
|
|
9
|
+
contract in [`spec/`](https://github.com/Bubblegunn/proactive-gate/tree/main/spec) and run
|
|
10
|
+
the same fixtures, so a policy written for one behaves the same in the other.
|
|
11
|
+
|
|
12
|
+
Until the first PyPI release, install from the repository:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
git clone https://github.com/Bubblegunn/proactive-gate
|
|
16
|
+
pip install ./proactive-gate/python
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
or, without cloning, `pip install "proactive-gate @ git+https://github.com/Bubblegunn/proactive-gate#subdirectory=python"`.
|
|
20
|
+
Python 3.11 or newer, no runtime dependencies. Add `[redis]` (`pip install "./proactive-gate/python[redis]"`)
|
|
21
|
+
for the Redis store. The PyPI name `proactive-gate` is reserved for this package; the
|
|
22
|
+
release workflow publishes there on the next tag.
|
|
23
|
+
|
|
24
|
+
## Use
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from proactive_gate import Candidate, EvaluateInput, Gate, UserState, default_checks
|
|
29
|
+
|
|
30
|
+
gate = Gate(default_checks(daily_limit=3))
|
|
31
|
+
user = UserState.from_dict({
|
|
32
|
+
"id": "u1", "consent": True, "timezone": "Europe/Istanbul",
|
|
33
|
+
"quietHours": {"start": "22:00", "end": "08:00"}, "createdAt": "2026-01-01T00:00:00Z",
|
|
34
|
+
})
|
|
35
|
+
candidate = Candidate(id="c1", type="reminder", priority="normal", surfaces=("push", "feed"))
|
|
36
|
+
inp = EvaluateInput(user, candidate, datetime.now(timezone.utc))
|
|
37
|
+
|
|
38
|
+
decision = gate.evaluate(inp)
|
|
39
|
+
if decision.allowed and gate.commit(decision, inp):
|
|
40
|
+
send(candidate, decision.surfaces)
|
|
41
|
+
else:
|
|
42
|
+
print(decision.rejected_by, decision.reason)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`evaluate` reads; `commit` is the atomic increment right before you send, and it is
|
|
46
|
+
idempotent on `decision.id`. Every decision carries `trace`, one entry per check that ran.
|
|
47
|
+
|
|
48
|
+
## A policy is data
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import json
|
|
52
|
+
from proactive_gate import Gate
|
|
53
|
+
|
|
54
|
+
gate = Gate.from_policy(json.load(open("policy.json")))
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`policy.json` is the same document the TypeScript package and the CLI read
|
|
58
|
+
(`{"specVersion": "1.0.0", "checks": [{"id": "consent"}, {"preset": "usTcpa"}, ...]}`).
|
|
59
|
+
Unknown check ids and presets raise `ValueError` naming the known ones; `shadow: true`
|
|
60
|
+
keeps a check observing without letting it decide.
|
|
61
|
+
|
|
62
|
+
## Async
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from proactive_gate import AsyncGate, RedisStore
|
|
66
|
+
import redis.asyncio as redis
|
|
67
|
+
|
|
68
|
+
gate = AsyncGate.from_policy(policy, RedisStore(redis.from_url("redis://localhost")))
|
|
69
|
+
decision = await gate.evaluate(inp)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`Gate` and `AsyncGate` share one decision loop (`Evaluation`): a check names the store keys
|
|
73
|
+
it needs and decides purely from their values, so the two gates cannot drift. `decide()`
|
|
74
|
+
runs that loop with values you supply, for tests with no store at all.
|
|
75
|
+
|
|
76
|
+
## Stores
|
|
77
|
+
|
|
78
|
+
`MemoryStore` (in-process), `SqliteStore` (standard library, one file per host),
|
|
79
|
+
`AsyncMemoryStore`, and `RedisStore` over `redis.asyncio` (INCR, then EXPIRE on the first
|
|
80
|
+
increment). Any object with `get`, `set`, `incr` and `delete` works.
|
|
81
|
+
|
|
82
|
+
## Presets
|
|
83
|
+
|
|
84
|
+
`proactive_gate.presets` holds the same fourteen presets as the TypeScript package, each with
|
|
85
|
+
its source URLs and a note on what it leaves out: LINE, WeChat, WeCom, Kakao, Korea's Network
|
|
86
|
+
Act, Japan's anti-spam law, China's minor mode, US TCPA, EU ePrivacy, Telegram and Slack.
|
|
87
|
+
They are reviewable defaults, not legal advice.
|
|
88
|
+
|
|
89
|
+
## Conformance
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
pytest
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
runs every fixture under `../spec/fixtures` through both gates. A fixture the Python package
|
|
96
|
+
cannot yet satisfy is listed in `spec/skip/python.txt` with a reason; the list is empty.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "proactive-gate"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "Decide whether a proactive assistant may speak now: ordered checks, budgets, quiet hours, JSON policies, shared conformance fixtures with the TypeScript package."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Efe Genc" }]
|
|
13
|
+
keywords = ["proactive", "notifications", "agents", "rate-limit", "quiet-hours", "policy"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Framework :: AsyncIO",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
"Topic :: Communications",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
redis = ["redis>=5"]
|
|
30
|
+
dev = ["pytest>=8", "mypy>=1.11", "redis>=5"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://bubblegunn.github.io/proactive-gate/"
|
|
34
|
+
Repository = "https://github.com/Bubblegunn/proactive-gate"
|
|
35
|
+
Issues = "https://github.com/Bubblegunn/proactive-gate/issues"
|
|
36
|
+
Specification = "https://github.com/Bubblegunn/proactive-gate/tree/main/spec"
|
|
37
|
+
Changelog = "https://github.com/Bubblegunn/proactive-gate/blob/main/CHANGELOG.md"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["src/proactive_gate"]
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.sdist]
|
|
43
|
+
include = ["src/proactive_gate", "tests", "README.md", "pyproject.toml"]
|
|
44
|
+
|
|
45
|
+
[tool.mypy]
|
|
46
|
+
strict = true
|
|
47
|
+
python_version = "3.11"
|
|
48
|
+
files = ["src/proactive_gate", "tests"]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""proactive-gate: decide whether a proactive assistant may speak now.
|
|
2
|
+
|
|
3
|
+
The Python sibling of the TypeScript package, held to the same behaviour
|
|
4
|
+
contract in ``spec/`` and the same fixtures.
|
|
5
|
+
"""
|
|
6
|
+
from . import checks, presets
|
|
7
|
+
from .checks import Check, default_checks
|
|
8
|
+
from .gate import AsyncGate, Evaluation, Gate, Hooks, decide
|
|
9
|
+
from .policy import KNOWN_CHECKS, CompiledPolicy, compile_policy, load_policy
|
|
10
|
+
from .presets import Preset
|
|
11
|
+
from .stores import AsyncMemoryStore, AsyncStore, MemoryStore, RedisStore, SqliteStore, Store
|
|
12
|
+
from .types import (
|
|
13
|
+
PRIORITY_RANK,
|
|
14
|
+
Candidate,
|
|
15
|
+
Context,
|
|
16
|
+
Decision,
|
|
17
|
+
EvaluateInput,
|
|
18
|
+
Outcome,
|
|
19
|
+
Priority,
|
|
20
|
+
TraceEntry,
|
|
21
|
+
UserState,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__version__ = "0.2.0"
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"PRIORITY_RANK", "KNOWN_CHECKS", "AsyncGate", "AsyncMemoryStore", "AsyncStore", "Candidate", "Check",
|
|
28
|
+
"CompiledPolicy", "Context", "Decision", "EvaluateInput", "Evaluation", "Gate", "Hooks", "MemoryStore",
|
|
29
|
+
"Outcome", "Preset", "Priority", "RedisStore", "SqliteStore", "Store", "TraceEntry", "UserState",
|
|
30
|
+
"__version__", "checks", "compile_policy", "decide", "default_checks", "load_policy", "presets",
|
|
31
|
+
]
|