revturbine 0.2.13__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 (83) hide show
  1. revturbine-0.2.13/.gitignore +28 -0
  2. revturbine-0.2.13/.pre-commit-config.yaml +16 -0
  3. revturbine-0.2.13/PKG-INFO +136 -0
  4. revturbine-0.2.13/README.md +111 -0
  5. revturbine-0.2.13/pyproject.toml +82 -0
  6. revturbine-0.2.13/revturbine_server/__init__.py +396 -0
  7. revturbine-0.2.13/src/revturbine/__init__.py +21 -0
  8. revturbine-0.2.13/src/revturbine/core/__init__.py +8 -0
  9. revturbine-0.2.13/src/revturbine/core/adapters/__init__.py +11 -0
  10. revturbine-0.2.13/src/revturbine/core/adapters/static.py +213 -0
  11. revturbine-0.2.13/src/revturbine/core/adapters/types.py +22 -0
  12. revturbine-0.2.13/src/revturbine/core/crypto.py +137 -0
  13. revturbine-0.2.13/src/revturbine/core/decisions/__init__.py +38 -0
  14. revturbine-0.2.13/src/revturbine/core/decisions/engine.py +285 -0
  15. revturbine-0.2.13/src/revturbine/core/decisions/types.py +181 -0
  16. revturbine-0.2.13/src/revturbine/core/entitlements/__init__.py +33 -0
  17. revturbine-0.2.13/src/revturbine/core/entitlements/entitlement_check.py +337 -0
  18. revturbine-0.2.13/src/revturbine/core/entitlements/rules.py +246 -0
  19. revturbine-0.2.13/src/revturbine/core/entitlements/segment_matching.py +68 -0
  20. revturbine-0.2.13/src/revturbine/core/helpers.py +944 -0
  21. revturbine-0.2.13/src/revturbine/core/normalization.py +109 -0
  22. revturbine-0.2.13/src/revturbine/core/placements/__init__.py +89 -0
  23. revturbine-0.2.13/src/revturbine/core/placements/local_resolver.py +809 -0
  24. revturbine-0.2.13/src/revturbine/core/placements/payload_resolution.py +532 -0
  25. revturbine-0.2.13/src/revturbine/core/placements/placement_decision.py +725 -0
  26. revturbine-0.2.13/src/revturbine/core/placements/trial_gating.py +248 -0
  27. revturbine-0.2.13/src/revturbine/core/providers/__init__.py +46 -0
  28. revturbine-0.2.13/src/revturbine/core/providers/registry.py +186 -0
  29. revturbine-0.2.13/src/revturbine/core/providers/types.py +317 -0
  30. revturbine-0.2.13/src/revturbine/core/rules/__init__.py +23 -0
  31. revturbine-0.2.13/src/revturbine/core/rules/plan_eligibility.py +116 -0
  32. revturbine-0.2.13/src/revturbine/core/runtime/__init__.py +20 -0
  33. revturbine-0.2.13/src/revturbine/core/runtime/local_runtime.py +444 -0
  34. revturbine-0.2.13/src/revturbine/core/state/__init__.py +86 -0
  35. revturbine-0.2.13/src/revturbine/core/state/cap_enforcer.py +245 -0
  36. revturbine-0.2.13/src/revturbine/core/state/impression_history.py +245 -0
  37. revturbine-0.2.13/src/revturbine/core/state/impression_history_stores.py +209 -0
  38. revturbine-0.2.13/src/revturbine/core/state/impression_history_types.py +103 -0
  39. revturbine-0.2.13/src/revturbine/core/state/interaction.py +56 -0
  40. revturbine-0.2.13/src/revturbine/core/state/interaction_tracker.py +210 -0
  41. revturbine-0.2.13/src/revturbine/core/state/storage.py +128 -0
  42. revturbine-0.2.13/src/revturbine/core/state/types.py +151 -0
  43. revturbine-0.2.13/src/revturbine/py.typed +0 -0
  44. revturbine-0.2.13/src/revturbine/sdk.py +286 -0
  45. revturbine-0.2.13/src/revturbine/types.py +3441 -0
  46. revturbine-0.2.13/tests/__init__.py +0 -0
  47. revturbine-0.2.13/tests/adapters/__init__.py +0 -0
  48. revturbine-0.2.13/tests/adapters/test_static.py +127 -0
  49. revturbine-0.2.13/tests/decisions/__init__.py +0 -0
  50. revturbine-0.2.13/tests/decisions/test_engine.py +465 -0
  51. revturbine-0.2.13/tests/decisions/test_engine_pilot.py +220 -0
  52. revturbine-0.2.13/tests/entitlements/__init__.py +0 -0
  53. revturbine-0.2.13/tests/entitlements/test_entitlement_check.py +196 -0
  54. revturbine-0.2.13/tests/entitlements/test_rules.py +157 -0
  55. revturbine-0.2.13/tests/entitlements/test_segment_matching.py +88 -0
  56. revturbine-0.2.13/tests/integration/__init__.py +0 -0
  57. revturbine-0.2.13/tests/integration/published_sdk_check.py +194 -0
  58. revturbine-0.2.13/tests/parity_contract/__init__.py +0 -0
  59. revturbine-0.2.13/tests/parity_contract/test_normalize.py +171 -0
  60. revturbine-0.2.13/tests/placements/__init__.py +0 -0
  61. revturbine-0.2.13/tests/placements/test_local_resolver.py +859 -0
  62. revturbine-0.2.13/tests/placements/test_payload_resolution.py +649 -0
  63. revturbine-0.2.13/tests/placements/test_placement_decision.py +717 -0
  64. revturbine-0.2.13/tests/providers/__init__.py +0 -0
  65. revturbine-0.2.13/tests/providers/test_registry.py +278 -0
  66. revturbine-0.2.13/tests/rules/__init__.py +0 -0
  67. revturbine-0.2.13/tests/rules/test_plan_eligibility.py +120 -0
  68. revturbine-0.2.13/tests/runtime/__init__.py +0 -0
  69. revturbine-0.2.13/tests/runtime/test_local_runtime.py +215 -0
  70. revturbine-0.2.13/tests/state/__init__.py +0 -0
  71. revturbine-0.2.13/tests/state/test_cap_enforcer.py +283 -0
  72. revturbine-0.2.13/tests/state/test_impression_history.py +277 -0
  73. revturbine-0.2.13/tests/state/test_impression_history_stores.py +299 -0
  74. revturbine-0.2.13/tests/state/test_interaction.py +106 -0
  75. revturbine-0.2.13/tests/state/test_interaction_tracker.py +277 -0
  76. revturbine-0.2.13/tests/state/test_storage.py +119 -0
  77. revturbine-0.2.13/tests/test_crypto.py +96 -0
  78. revturbine-0.2.13/tests/test_helpers.py +1064 -0
  79. revturbine-0.2.13/tests/test_normalization.py +131 -0
  80. revturbine-0.2.13/tests/test_public_api.py +204 -0
  81. revturbine-0.2.13/tests/test_smoke.py +17 -0
  82. revturbine-0.2.13/tests/test_trial_overlay_upsert.py +69 -0
  83. revturbine-0.2.13/tox.ini +26 -0
@@ -0,0 +1,28 @@
1
+ # Python virtualenvs
2
+ .venv/
3
+ venv/
4
+ env/
5
+
6
+ # Bytecode and caches
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+
11
+ # Tool caches
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .tox/
16
+
17
+ # Build / packaging artifacts
18
+ build/
19
+ dist/
20
+ *.egg-info/
21
+ *.egg
22
+ .eggs/
23
+
24
+ # Coverage
25
+ .coverage
26
+ .coverage.*
27
+ htmlcov/
28
+ coverage.xml
@@ -0,0 +1,16 @@
1
+ # Python-side pre-commit hooks for the revturbine package.
2
+ # Scoped to server-python/; install from this directory with `pre-commit install`.
3
+ repos:
4
+ - repo: https://github.com/astral-sh/ruff-pre-commit
5
+ rev: v0.6.9
6
+ hooks:
7
+ - id: ruff
8
+ args: [--fix, --exit-non-zero-on-fix]
9
+ - id: ruff-format
10
+ - repo: https://github.com/pre-commit/mirrors-mypy
11
+ rev: v1.10.0
12
+ hooks:
13
+ - id: mypy
14
+ additional_dependencies: [pydantic>=2.0]
15
+ files: ^server-python/src/
16
+ args: [--config-file=server-python/pyproject.toml]
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: revturbine
3
+ Version: 0.2.13
4
+ Summary: RevTurbine SDK for Python — local-mode and HTTP-mode placement and entitlement decisioning
5
+ Author: RevTurbine
6
+ License-Expression: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: pydantic[email]>=2.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: mypy>=1.10; extra == 'dev'
20
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
21
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
22
+ Requires-Dist: pytest>=8.0; extra == 'dev'
23
+ Requires-Dist: ruff>=0.6; extra == 'dev'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # RevTurbine SDK — Python
27
+
28
+ Headless, **in-memory** entitlement and placement decisioning for Python
29
+ services. `RevTurbineCustomerSdk` (plan 33 TASK-7) decides in-process from a
30
+ caller-supplied user context + `ExportedConfig` snapshot — no network, no
31
+ persistence beyond memory. Its decision output is **byte-identical** to the
32
+ canonical TypeScript `LocalRuntime` (cross-language parity is gate-enforced via
33
+ `revturbine-sdk-internal/tests/parity/`).
34
+
35
+ **Full docs:** the published Astro docs site — *Getting Started → Python SDK*
36
+ (source: `pages-build/src/content/docs/getting-started/python.mdx`).
37
+
38
+ ## Install
39
+
40
+ Published as a GitHub **Release on `revt-eng/revturbine-external`** (the
41
+ customer-accessible repo — there is no PyPI). Requires Python ≥ 3.10.
42
+ `revturbine-external` is private, so fetch the wheel authenticated, then
43
+ install the file:
44
+
45
+ ```bash
46
+ gh release download python-v0.1.0 \
47
+ --repo revt-eng/revturbine-external \
48
+ --pattern "revturbine-*.whl"
49
+ pip install ./revturbine-*.whl
50
+ ```
51
+
52
+ A plain `pip install <url>` will not work — private-repo release assets
53
+ require authentication. (Contributors developing in this repo use the
54
+ editable install under [Local development](#local-development) instead.)
55
+
56
+ ## Quick start
57
+
58
+ ```python
59
+ import json
60
+ from revturbine import RevTurbineCustomerSdk
61
+
62
+ with open("exported_config.json") as f:
63
+ exported_config = json.load(f)
64
+
65
+ sdk = RevTurbineCustomerSdk(
66
+ user_context={"tenant_id": "tenant_abc", "user_id": "user_123", "plan_handle": "pro"},
67
+ exported_config=exported_config,
68
+ )
69
+
70
+ if not sdk.check_entitlement("advanced_analytics")["allowed"]:
71
+ raise PermissionError("not entitled")
72
+
73
+ decision = sdk.get_placement_decision(
74
+ {"placement_id": "pl_dashboard_upsell", "user_id": "user_123"}
75
+ )
76
+ ```
77
+
78
+ ### Public API
79
+
80
+ - `RevTurbineCustomerSdk(*, user_context, exported_config)` — keyword-only;
81
+ `user_context` requires `tenant_id` + `user_id` (optional `plan_handle`,
82
+ `plan_name`, `usage`). Stateless / in-memory — no storage parameter.
83
+ - `check_entitlement(handle, context=None) -> EntitlementCheckResult`
84
+ - `get_placement_decision(input) -> PlacementDecision`
85
+ - `get_placement_decisions(inputs) -> list[PlacementDecision]`
86
+
87
+ That is the entire public surface. Browser/full-SDK concerns (React, hooks,
88
+ HTTP/dual-mode, decision cache, segment/targeting derivation,
89
+ `identify`/`dismiss`/`snooze`/`convert`/`get_trial_status`/`capture`/…) are
90
+ intentionally **not** ported — plan 33 REQ-14 non-goals.
91
+
92
+ ## Project layout
93
+
94
+ ```
95
+ server-python/
96
+ ├── pyproject.toml ← package metadata + tool config (ruff, mypy, pytest)
97
+ ├── tox.ini ← test/lint/type matrix
98
+ ├── .pre-commit-config.yaml ← ruff + mypy hooks (scoped to this dir)
99
+ ├── src/revturbine/ ← the headless SDK (sdk.py + core/ port)
100
+ ├── revturbine_server/ ← legacy thin-RPC client (see below)
101
+ └── tests/ ← pytest suite (incl. tests/test_public_api.py)
102
+ ```
103
+
104
+ ## Legacy `revturbine_server`
105
+
106
+ A historical thin-RPC HTTP client (`from revturbine_server import
107
+ RevTurbineServer`) also ships in this package. It is **independent of and
108
+ unrelated to** `RevTurbineCustomerSdk` — not folded in, not a dual-mode of it.
109
+ It currently depends on an unmaintained generated `revturbine.types` module
110
+ (plan REQ-4 / TASK-2) and is **not standalone-importable** until that
111
+ generator lands. Prefer `RevTurbineCustomerSdk` for all new server
112
+ integrations.
113
+
114
+ ## Local development
115
+
116
+ ```bash
117
+ pip install -e ".[dev]"
118
+ pytest # unit + smoke + parity-contract tests
119
+ mypy # strict type-check of src/ and tests/
120
+ ruff check src tests # lint (CI scope)
121
+ ruff format --check src tests
122
+ tox # full matrix (py310–py313 + lint + type)
123
+ ```
124
+
125
+ Cross-language parity (run from the SDK repo root):
126
+
127
+ ```bash
128
+ node scripts/run-parity.mjs # TS + Python runners, byte-diff (the gate)
129
+ ```
130
+
131
+ ## Dependencies
132
+
133
+ - Runtime: `pydantic>=2.0`.
134
+ - Stdlib only otherwise. The legacy `revturbine_server` client uses only
135
+ `urllib` / `json` / `uuid` (plus the unmaintained generated-types import
136
+ noted above).
@@ -0,0 +1,111 @@
1
+ # RevTurbine SDK — Python
2
+
3
+ Headless, **in-memory** entitlement and placement decisioning for Python
4
+ services. `RevTurbineCustomerSdk` (plan 33 TASK-7) decides in-process from a
5
+ caller-supplied user context + `ExportedConfig` snapshot — no network, no
6
+ persistence beyond memory. Its decision output is **byte-identical** to the
7
+ canonical TypeScript `LocalRuntime` (cross-language parity is gate-enforced via
8
+ `revturbine-sdk-internal/tests/parity/`).
9
+
10
+ **Full docs:** the published Astro docs site — *Getting Started → Python SDK*
11
+ (source: `pages-build/src/content/docs/getting-started/python.mdx`).
12
+
13
+ ## Install
14
+
15
+ Published as a GitHub **Release on `revt-eng/revturbine-external`** (the
16
+ customer-accessible repo — there is no PyPI). Requires Python ≥ 3.10.
17
+ `revturbine-external` is private, so fetch the wheel authenticated, then
18
+ install the file:
19
+
20
+ ```bash
21
+ gh release download python-v0.1.0 \
22
+ --repo revt-eng/revturbine-external \
23
+ --pattern "revturbine-*.whl"
24
+ pip install ./revturbine-*.whl
25
+ ```
26
+
27
+ A plain `pip install <url>` will not work — private-repo release assets
28
+ require authentication. (Contributors developing in this repo use the
29
+ editable install under [Local development](#local-development) instead.)
30
+
31
+ ## Quick start
32
+
33
+ ```python
34
+ import json
35
+ from revturbine import RevTurbineCustomerSdk
36
+
37
+ with open("exported_config.json") as f:
38
+ exported_config = json.load(f)
39
+
40
+ sdk = RevTurbineCustomerSdk(
41
+ user_context={"tenant_id": "tenant_abc", "user_id": "user_123", "plan_handle": "pro"},
42
+ exported_config=exported_config,
43
+ )
44
+
45
+ if not sdk.check_entitlement("advanced_analytics")["allowed"]:
46
+ raise PermissionError("not entitled")
47
+
48
+ decision = sdk.get_placement_decision(
49
+ {"placement_id": "pl_dashboard_upsell", "user_id": "user_123"}
50
+ )
51
+ ```
52
+
53
+ ### Public API
54
+
55
+ - `RevTurbineCustomerSdk(*, user_context, exported_config)` — keyword-only;
56
+ `user_context` requires `tenant_id` + `user_id` (optional `plan_handle`,
57
+ `plan_name`, `usage`). Stateless / in-memory — no storage parameter.
58
+ - `check_entitlement(handle, context=None) -> EntitlementCheckResult`
59
+ - `get_placement_decision(input) -> PlacementDecision`
60
+ - `get_placement_decisions(inputs) -> list[PlacementDecision]`
61
+
62
+ That is the entire public surface. Browser/full-SDK concerns (React, hooks,
63
+ HTTP/dual-mode, decision cache, segment/targeting derivation,
64
+ `identify`/`dismiss`/`snooze`/`convert`/`get_trial_status`/`capture`/…) are
65
+ intentionally **not** ported — plan 33 REQ-14 non-goals.
66
+
67
+ ## Project layout
68
+
69
+ ```
70
+ server-python/
71
+ ├── pyproject.toml ← package metadata + tool config (ruff, mypy, pytest)
72
+ ├── tox.ini ← test/lint/type matrix
73
+ ├── .pre-commit-config.yaml ← ruff + mypy hooks (scoped to this dir)
74
+ ├── src/revturbine/ ← the headless SDK (sdk.py + core/ port)
75
+ ├── revturbine_server/ ← legacy thin-RPC client (see below)
76
+ └── tests/ ← pytest suite (incl. tests/test_public_api.py)
77
+ ```
78
+
79
+ ## Legacy `revturbine_server`
80
+
81
+ A historical thin-RPC HTTP client (`from revturbine_server import
82
+ RevTurbineServer`) also ships in this package. It is **independent of and
83
+ unrelated to** `RevTurbineCustomerSdk` — not folded in, not a dual-mode of it.
84
+ It currently depends on an unmaintained generated `revturbine.types` module
85
+ (plan REQ-4 / TASK-2) and is **not standalone-importable** until that
86
+ generator lands. Prefer `RevTurbineCustomerSdk` for all new server
87
+ integrations.
88
+
89
+ ## Local development
90
+
91
+ ```bash
92
+ pip install -e ".[dev]"
93
+ pytest # unit + smoke + parity-contract tests
94
+ mypy # strict type-check of src/ and tests/
95
+ ruff check src tests # lint (CI scope)
96
+ ruff format --check src tests
97
+ tox # full matrix (py310–py313 + lint + type)
98
+ ```
99
+
100
+ Cross-language parity (run from the SDK repo root):
101
+
102
+ ```bash
103
+ node scripts/run-parity.mjs # TS + Python runners, byte-diff (the gate)
104
+ ```
105
+
106
+ ## Dependencies
107
+
108
+ - Runtime: `pydantic>=2.0`.
109
+ - Stdlib only otherwise. The legacy `revturbine_server` client uses only
110
+ `urllib` / `json` / `uuid` (plus the unmaintained generated-types import
111
+ noted above).
@@ -0,0 +1,82 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "revturbine"
7
+ version = "0.2.13"
8
+ description = "RevTurbine SDK for Python — local-mode and HTTP-mode placement and entitlement decisioning"
9
+ requires-python = ">=3.10"
10
+ license = "MIT"
11
+ readme = "README.md"
12
+ authors = [{ name = "RevTurbine" }]
13
+ dependencies = [
14
+ # `pydantic[email]` (not bare pydantic): the vendored generated
15
+ # `revturbine.types` models (plan 33 REQ-4) declare EmailStr fields,
16
+ # which import `email-validator` at model-build time — required for
17
+ # `import revturbine.types` / `revturbine_server` to load. (REQ-12
18
+ # justification: it is a hard import-time dep of the required
19
+ # generated-types artifact, not an optional add.)
20
+ "pydantic[email]>=2.0",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 3 - Alpha",
24
+ "Intended Audience :: Developers",
25
+ "Operating System :: OS Independent",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Typing :: Typed",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "pytest>=8.0",
37
+ "pytest-cov>=5.0",
38
+ "mypy>=1.10",
39
+ "ruff>=0.6",
40
+ "pre-commit>=3.7",
41
+ ]
42
+
43
+ # The typed `revturbine` package (src-layout, the headless in-memory SDK —
44
+ # plan 33 TASK-7's `RevTurbineCustomerSdk`) ships alongside the legacy
45
+ # `revturbine_server` thin-RPC HTTP client. They are independent and
46
+ # composable: the headless-server scope decision keeps `revturbine_server`
47
+ # unchanged rather than folding it into a dual-mode dispatch (the original
48
+ # plan's `runtime_mode` framing is superseded).
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/revturbine", "revturbine_server"]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py310"
55
+ src = ["src", "tests"]
56
+ # `src/revturbine/types.py` is the vendored datamodel-codegen artifact
57
+ # (plan 33 REQ-4) — generated, never hand-edited, not lint-owned here.
58
+ extend-exclude = ["src/revturbine/types.py"]
59
+
60
+ [tool.ruff.lint]
61
+ select = ["E", "F", "I", "W", "B", "UP", "SIM"]
62
+
63
+ [tool.ruff.lint.per-file-ignores]
64
+ "tests/*" = ["E501"]
65
+
66
+ [tool.mypy]
67
+ python_version = "3.10"
68
+ strict = true
69
+ warn_unused_ignores = true
70
+ warn_redundant_casts = true
71
+ files = ["src/revturbine", "tests"]
72
+ # `src/revturbine/types.py` is the vendored datamodel-codegen artifact
73
+ # (plan 33 REQ-4) — generated, never hand-edited, not strict-typed here.
74
+ # `revturbine_server` now imports that vendored `revturbine.types` fine,
75
+ # but its legacy typing stays out of mypy --strict by deliberate scope
76
+ # decision (independent legacy surface; not retyped here).
77
+ exclude = ["revturbine_server/", "build/", "src/revturbine/types\\.py"]
78
+
79
+ [tool.pytest.ini_options]
80
+ testpaths = ["tests"]
81
+ python_files = ["test_*.py"]
82
+ addopts = ["-ra", "--strict-markers"]