zubbl-runtime 0.1.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 (46) hide show
  1. zubbl_runtime-0.1.0/PKG-INFO +28 -0
  2. zubbl_runtime-0.1.0/README.md +17 -0
  3. zubbl_runtime-0.1.0/pyproject.toml +22 -0
  4. zubbl_runtime-0.1.0/setup.cfg +4 -0
  5. zubbl_runtime-0.1.0/src/zubbl_runtime/__init__.py +138 -0
  6. zubbl_runtime-0.1.0/src/zubbl_runtime/bootstrap.py +166 -0
  7. zubbl_runtime-0.1.0/src/zubbl_runtime/client.py +493 -0
  8. zubbl_runtime-0.1.0/src/zubbl_runtime/diagnostics.py +43 -0
  9. zubbl_runtime-0.1.0/src/zubbl_runtime/endpoint_catalogue.py +156 -0
  10. zubbl_runtime-0.1.0/src/zubbl_runtime/enforcement_result.py +197 -0
  11. zubbl_runtime-0.1.0/src/zubbl_runtime/errors.py +28 -0
  12. zubbl_runtime-0.1.0/src/zubbl_runtime/models.py +15 -0
  13. zubbl_runtime-0.1.0/src/zubbl_runtime/policy_cache.py +126 -0
  14. zubbl_runtime-0.1.0/src/zubbl_runtime/policy_evaluator.py +379 -0
  15. zubbl_runtime-0.1.0/src/zubbl_runtime/policy_manager.py +396 -0
  16. zubbl_runtime-0.1.0/src/zubbl_runtime/policy_models.py +264 -0
  17. zubbl_runtime-0.1.0/src/zubbl_runtime/policy_normalizer.py +427 -0
  18. zubbl_runtime-0.1.0/src/zubbl_runtime/rate_limit_store.py +151 -0
  19. zubbl_runtime-0.1.0/src/zubbl_runtime/resilience.py +18 -0
  20. zubbl_runtime-0.1.0/src/zubbl_runtime/route_matching.py +55 -0
  21. zubbl_runtime-0.1.0/src/zubbl_runtime/runtime_context.py +92 -0
  22. zubbl_runtime-0.1.0/src/zubbl_runtime/runtime_enforcer.py +368 -0
  23. zubbl_runtime-0.1.0/src/zubbl_runtime/signing.py +104 -0
  24. zubbl_runtime-0.1.0/src/zubbl_runtime/transport.py +176 -0
  25. zubbl_runtime-0.1.0/src/zubbl_runtime.egg-info/PKG-INFO +28 -0
  26. zubbl_runtime-0.1.0/src/zubbl_runtime.egg-info/SOURCES.txt +44 -0
  27. zubbl_runtime-0.1.0/src/zubbl_runtime.egg-info/dependency_links.txt +1 -0
  28. zubbl_runtime-0.1.0/src/zubbl_runtime.egg-info/requires.txt +5 -0
  29. zubbl_runtime-0.1.0/src/zubbl_runtime.egg-info/top_level.txt +1 -0
  30. zubbl_runtime-0.1.0/tests/test_bootstrap.py +87 -0
  31. zubbl_runtime-0.1.0/tests/test_client.py +699 -0
  32. zubbl_runtime-0.1.0/tests/test_diagnostics.py +221 -0
  33. zubbl_runtime-0.1.0/tests/test_endpoint_catalogue.py +342 -0
  34. zubbl_runtime-0.1.0/tests/test_enforcement_result.py +159 -0
  35. zubbl_runtime-0.1.0/tests/test_policy_cache.py +140 -0
  36. zubbl_runtime-0.1.0/tests/test_policy_evaluator.py +455 -0
  37. zubbl_runtime-0.1.0/tests/test_policy_evaluator_registry.py +108 -0
  38. zubbl_runtime-0.1.0/tests/test_policy_manager.py +533 -0
  39. zubbl_runtime-0.1.0/tests/test_policy_models.py +165 -0
  40. zubbl_runtime-0.1.0/tests/test_policy_normalizer.py +284 -0
  41. zubbl_runtime-0.1.0/tests/test_rate_limit_store.py +293 -0
  42. zubbl_runtime-0.1.0/tests/test_route_matching.py +45 -0
  43. zubbl_runtime-0.1.0/tests/test_runtime_context.py +85 -0
  44. zubbl_runtime-0.1.0/tests/test_runtime_enforcer.py +434 -0
  45. zubbl_runtime-0.1.0/tests/test_signing.py +132 -0
  46. zubbl_runtime-0.1.0/tests/test_transport.py +158 -0
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: zubbl-runtime
3
+ Version: 0.1.0
4
+ Summary: Core runtime client for Zubbl Runtime Guard
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: httpx>=0.27.0
8
+ Provides-Extra: test
9
+ Requires-Dist: pytest>=8.0; extra == "test"
10
+ Requires-Dist: pytest-asyncio>=0.23; extra == "test"
11
+
12
+ # Zubbl Runtime
13
+
14
+ Core Python runtime client for Zubbl Runtime Guard.
15
+
16
+ `zubbl-runtime` provides the shared runtime, bootstrap, signing, policy enforcement, resilience and transport components used by the Zubbl framework adapters.
17
+
18
+ ## Requirements
19
+
20
+ - Python 3.10+
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install zubbl-runtime
26
+ ```
27
+
28
+ Most developers should install a framework adapter such as `zubbl-fastapi`, which installs this package automatically.
@@ -0,0 +1,17 @@
1
+ # Zubbl Runtime
2
+
3
+ Core Python runtime client for Zubbl Runtime Guard.
4
+
5
+ `zubbl-runtime` provides the shared runtime, bootstrap, signing, policy enforcement, resilience and transport components used by the Zubbl framework adapters.
6
+
7
+ ## Requirements
8
+
9
+ - Python 3.10+
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install zubbl-runtime
15
+ ```
16
+
17
+ Most developers should install a framework adapter such as `zubbl-fastapi`, which installs this package automatically.
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zubbl-runtime"
7
+ version = "0.1.0"
8
+ description = "Core runtime client for Zubbl Runtime Guard"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "httpx>=0.27.0",
13
+ ]
14
+
15
+ [tool.setuptools.packages.find]
16
+ where = ["src"]
17
+
18
+ [project.optional-dependencies]
19
+ test = [
20
+ "pytest>=8.0",
21
+ "pytest-asyncio>=0.23",
22
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,138 @@
1
+ from .bootstrap import BootstrapClient, BootstrapError
2
+ from .client import RuntimeClient, RuntimeClientError
3
+ from .diagnostics import RuntimeDiagnostics
4
+ from .endpoint_catalogue import (
5
+ EndpointCatalogueClient,
6
+ EndpointCatalogueError,
7
+ EndpointCatalogueSyncResult,
8
+ )
9
+ from .enforcement_result import (
10
+ EnforcementResult,
11
+ EnforcementResultBuilder,
12
+ )
13
+ from .models import BootstrapConfig
14
+ from .transport import SignedTransport, TransportError
15
+ from .runtime_context import RuntimeRequestContext
16
+ from .runtime_enforcer import (
17
+ RuntimeAction,
18
+ RuntimeDecision,
19
+ RuntimeEnforcer,
20
+ )
21
+ from .rate_limit_store import (
22
+ InMemoryRateLimitStore,
23
+ RateLimitKey,
24
+ RateLimitResult,
25
+ )
26
+ from .route_matching import (
27
+ compile_route_pattern,
28
+ normalise_route_path,
29
+ route_matches,
30
+ )
31
+ from .policy_manager import PolicyManager
32
+ from .policy_evaluator import (
33
+ EvaluationAction,
34
+ GeoEvaluationHandler,
35
+ PolicyEvaluation,
36
+ PolicyEvaluationHandler,
37
+ PolicyEvaluator,
38
+ PolicyEvaluatorRegistry,
39
+ RateLimitEvaluationHandler,
40
+ VpnEvaluationHandler,
41
+ default_policy_evaluator_registry,
42
+ )
43
+ from .policy_normalizer import (
44
+ AdaptiveInstruction,
45
+ CanonicalPolicyInstruction,
46
+ EnforcementMode,
47
+ GeoInstruction,
48
+ PolicyInstruction,
49
+ PolicyKind,
50
+ RateLimitInstruction,
51
+ UnknownPolicyInstruction,
52
+ VpnInstruction,
53
+ normalise_policy_tile,
54
+ normalise_policy_tiles,
55
+ )
56
+ from .policy_cache import PolicyCache
57
+ from .policy_models import (
58
+ DecisionSource,
59
+ EffectivePolicy,
60
+ PolicyCacheKey,
61
+ PolicyDecision,
62
+ PolicyResolution,
63
+ PolicyScope,
64
+ PolicySnapshot,
65
+ PolicyTile,
66
+ PolicyValidationError,
67
+ )
68
+ from .signing import (
69
+ build_canonical_request,
70
+ generate_nonce,
71
+ sha256_hex,
72
+ sign_request,
73
+ verify_signature,
74
+ )
75
+
76
+ __all__ = [
77
+ "BootstrapClient",
78
+ "BootstrapConfig",
79
+ "BootstrapError",
80
+ "PolicyValidationError",
81
+ "PolicyTile",
82
+ "PolicySnapshot",
83
+ "PolicyScope",
84
+ "PolicyResolution",
85
+ "normalise_policy_tiles",
86
+ "normalise_policy_tile",
87
+ "VpnInstruction",
88
+ "UnknownPolicyInstruction",
89
+ "RateLimitInstruction",
90
+ "PolicyKind",
91
+ "PolicyInstruction",
92
+ "GeoInstruction",
93
+ "EnforcementMode",
94
+ "CanonicalPolicyInstruction",
95
+ "AdaptiveInstruction",
96
+ "default_policy_evaluator_registry",
97
+ "VpnEvaluationHandler",
98
+ "RateLimitEvaluationHandler",
99
+ "PolicyEvaluatorRegistry",
100
+ "PolicyEvaluationHandler",
101
+ "GeoEvaluationHandler",
102
+ "EnforcementResultBuilder",
103
+ "EndpointCatalogueSyncResult",
104
+ "EndpointCatalogueError",
105
+ "EndpointCatalogueClient",
106
+ "EnforcementResult",
107
+ "PolicyEvaluator",
108
+ "PolicyEvaluation",
109
+ "EvaluationAction",
110
+ "PolicyManager",
111
+ "PolicyDecision",
112
+ "PolicyCacheKey",
113
+ "PolicyCache",
114
+ "EffectivePolicy",
115
+ "DecisionSource",
116
+ "route_matches",
117
+ "normalise_route_path",
118
+ "compile_route_pattern",
119
+ "RateLimitResult",
120
+ "RateLimitKey",
121
+ "InMemoryRateLimitStore",
122
+ "RuntimeEnforcer",
123
+ "RuntimeDecision",
124
+ "RuntimeAction",
125
+ "RuntimeRequestContext",
126
+ "RuntimeDiagnostics",
127
+ "RuntimeClient",
128
+ "RuntimeClientError",
129
+ "SignedTransport",
130
+ "TransportError",
131
+ "build_canonical_request",
132
+ "generate_nonce",
133
+ "sha256_hex",
134
+ "sign_request",
135
+ "verify_signature",
136
+ ]
137
+
138
+ __version__ = "0.1.0"
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from dataclasses import dataclass
5
+
6
+ import httpx
7
+
8
+ from .models import BootstrapConfig
9
+
10
+
11
+ class BootstrapError(RuntimeError):
12
+ """Raised when Runtime Guard bootstrap cannot be completed."""
13
+
14
+
15
+ @dataclass(slots=True)
16
+ class _CachedBootstrap:
17
+ config: BootstrapConfig
18
+ expires_at: float
19
+
20
+
21
+ class BootstrapClient:
22
+ """
23
+ Resolves Runtime Guard configuration from a Zubbl API key.
24
+
25
+ Bootstrap results are cached in memory only. The signing secret is never
26
+ persisted to disk by this client.
27
+
28
+ A shared HTTP client may be supplied by RuntimeClient. When no client is
29
+ supplied, standalone use retains the original per-request client lifecycle.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ api_key: str,
35
+ *,
36
+ base_url: str = "https://api.zubbl.com",
37
+ timeout_seconds: float = 8.0,
38
+ http_client: httpx.AsyncClient | None = None,
39
+ ) -> None:
40
+ cleaned_api_key = api_key.strip()
41
+
42
+ if not cleaned_api_key:
43
+ raise ValueError("api_key must not be empty")
44
+
45
+ self._api_key = cleaned_api_key
46
+ self._base_url = base_url.rstrip("/")
47
+ self._timeout_seconds = timeout_seconds
48
+ self._http_client = http_client
49
+ self._cache: _CachedBootstrap | None = None
50
+
51
+ async def _post_bootstrap(
52
+ self,
53
+ url: str,
54
+ *,
55
+ headers: dict[str, str],
56
+ ) -> httpx.Response:
57
+ if self._http_client is not None:
58
+ return await self._http_client.post(
59
+ url,
60
+ headers=headers,
61
+ )
62
+
63
+ # Backwards-compatible standalone behaviour.
64
+ async with httpx.AsyncClient(
65
+ timeout=self._timeout_seconds,
66
+ ) as client:
67
+ return await client.post(
68
+ url,
69
+ headers=headers,
70
+ )
71
+
72
+ async def bootstrap(
73
+ self,
74
+ *,
75
+ force_refresh: bool = False,
76
+ ) -> BootstrapConfig:
77
+ now = time.monotonic()
78
+
79
+ if (
80
+ not force_refresh
81
+ and self._cache is not None
82
+ and self._cache.expires_at > now
83
+ ):
84
+ return self._cache.config
85
+
86
+ url = f"{self._base_url}/api/sdk/bootstrap"
87
+
88
+ try:
89
+ response = await self._post_bootstrap(
90
+ url,
91
+ headers={
92
+ "Authorization": f"Bearer {self._api_key}",
93
+ "User-Agent": "zubbl-runtime/0.1.0",
94
+ },
95
+ )
96
+ except httpx.TimeoutException as exc:
97
+ raise BootstrapError(
98
+ "Zubbl bootstrap request timed out"
99
+ ) from exc
100
+ except httpx.HTTPError as exc:
101
+ raise BootstrapError(
102
+ "Could not connect to Zubbl bootstrap endpoint"
103
+ ) from exc
104
+
105
+ if response.status_code in {401, 403}:
106
+ raise BootstrapError(
107
+ "Zubbl API key is invalid, revoked or unavailable"
108
+ )
109
+
110
+ if response.status_code != 200:
111
+ raise BootstrapError(
112
+ f"Zubbl bootstrap failed with HTTP "
113
+ f"{response.status_code}"
114
+ )
115
+
116
+ try:
117
+ payload = response.json()
118
+
119
+ config = BootstrapConfig(
120
+ tenant_id=str(payload["tenant_id"]),
121
+ app_id=str(payload["app_id"]),
122
+ signing_secret=str(payload["signing_secret"]),
123
+ runtime_version=str(payload["runtime_version"]),
124
+ telemetry_enabled=bool(payload["telemetry_enabled"]),
125
+ endpoint_discovery=bool(payload["endpoint_discovery"]),
126
+ cache_ttl_seconds=int(
127
+ payload["cache_ttl_seconds"]
128
+ ),
129
+ polling_interval_seconds=int(
130
+ payload["polling_interval_seconds"]
131
+ ),
132
+ protocol_version=int(payload["protocol_version"]),
133
+ runtime_fail_mode=str(
134
+ payload["runtime_fail_mode"]
135
+ ),
136
+ )
137
+ except (KeyError, TypeError, ValueError) as exc:
138
+ raise BootstrapError(
139
+ "Zubbl bootstrap returned an invalid configuration"
140
+ ) from exc
141
+
142
+ if (
143
+ not config.tenant_id
144
+ or not config.app_id
145
+ or not config.signing_secret
146
+ ):
147
+ raise BootstrapError(
148
+ "Zubbl bootstrap configuration is incomplete"
149
+ )
150
+
151
+ if config.runtime_fail_mode not in {"open", "closed"}:
152
+ raise BootstrapError(
153
+ "Zubbl bootstrap returned an invalid runtime_fail_mode"
154
+ )
155
+
156
+ self._cache = _CachedBootstrap(
157
+ config=config,
158
+ expires_at=(
159
+ now + max(1, config.cache_ttl_seconds)
160
+ ),
161
+ )
162
+
163
+ return config
164
+
165
+ def clear_cache(self) -> None:
166
+ self._cache = None