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.
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ dist/
3
+ *.log
4
+ .DS_Store
5
+ python/.venv/
6
+ python/dist/
7
+ python/build/
8
+ __pycache__/
9
+ *.egg-info/
10
+ .pytest_cache/
11
+ .mypy_cache/
@@ -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
+ ]