fireweave 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.
- fireweave-0.1.0/LICENSE +21 -0
- fireweave-0.1.0/PKG-INFO +97 -0
- fireweave-0.1.0/README.md +62 -0
- fireweave-0.1.0/pyproject.toml +59 -0
- fireweave-0.1.0/setup.cfg +4 -0
- fireweave-0.1.0/src/fireweave/__init__.py +126 -0
- fireweave-0.1.0/src/fireweave/_version.py +5 -0
- fireweave-0.1.0/src/fireweave/adapters/__init__.py +23 -0
- fireweave-0.1.0/src/fireweave/adapters/base.py +117 -0
- fireweave-0.1.0/src/fireweave/adapters/local.py +76 -0
- fireweave-0.1.0/src/fireweave/adapters/memory.py +115 -0
- fireweave-0.1.0/src/fireweave/adapters/posthog.py +426 -0
- fireweave-0.1.0/src/fireweave/adapters/remote.py +310 -0
- fireweave-0.1.0/src/fireweave/aio.py +176 -0
- fireweave-0.1.0/src/fireweave/capabilities.py +59 -0
- fireweave-0.1.0/src/fireweave/client.py +809 -0
- fireweave-0.1.0/src/fireweave/config.py +99 -0
- fireweave-0.1.0/src/fireweave/context.py +199 -0
- fireweave-0.1.0/src/fireweave/decision.py +42 -0
- fireweave-0.1.0/src/fireweave/errors.py +255 -0
- fireweave-0.1.0/src/fireweave/openfeature/__init__.py +22 -0
- fireweave-0.1.0/src/fireweave/openfeature/local_provider.py +211 -0
- fireweave-0.1.0/src/fireweave/openfeature/provider.py +163 -0
- fireweave-0.1.0/src/fireweave/py.typed +0 -0
- fireweave-0.1.0/src/fireweave/runtime.py +413 -0
- fireweave-0.1.0/src/fireweave/types.py +34 -0
- fireweave-0.1.0/src/fireweave.egg-info/PKG-INFO +97 -0
- fireweave-0.1.0/src/fireweave.egg-info/SOURCES.txt +42 -0
- fireweave-0.1.0/src/fireweave.egg-info/dependency_links.txt +1 -0
- fireweave-0.1.0/src/fireweave.egg-info/requires.txt +11 -0
- fireweave-0.1.0/src/fireweave.egg-info/top_level.txt +1 -0
- fireweave-0.1.0/tests/test_client_extensions.py +374 -0
- fireweave-0.1.0/tests/test_concurrency.py +159 -0
- fireweave-0.1.0/tests/test_conformance.py +26 -0
- fireweave-0.1.0/tests/test_context.py +150 -0
- fireweave-0.1.0/tests/test_control_points.py +46 -0
- fireweave-0.1.0/tests/test_errors.py +127 -0
- fireweave-0.1.0/tests/test_lifecycle.py +201 -0
- fireweave-0.1.0/tests/test_local_provider.py +154 -0
- fireweave-0.1.0/tests/test_openfeature_provider.py +164 -0
- fireweave-0.1.0/tests/test_posthog_adapter.py +416 -0
- fireweave-0.1.0/tests/test_register_target.py +142 -0
- fireweave-0.1.0/tests/test_remote_adapter.py +61 -0
- fireweave-0.1.0/tests/test_runtime_evaluation.py +87 -0
fireweave-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fireweave AI, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fireweave-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fireweave
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fireweave polyglot SDK for Python — OpenFeature-compatible, server-first flag evaluation and release-safety extensions (spec v0.1.0).
|
|
5
|
+
Author: Fireweave AI, Inc.
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FireWeave-HQ/fireweave-sdk
|
|
8
|
+
Project-URL: Documentation, https://github.com/FireWeave-HQ/fireweave-sdk/tree/master/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/FireWeave-HQ/fireweave-sdk
|
|
10
|
+
Project-URL: Issues, https://github.com/FireWeave-HQ/fireweave-sdk/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/FireWeave-HQ/fireweave-sdk/blob/master/CHANGELOG.md
|
|
12
|
+
Project-URL: Source, https://github.com/FireWeave-HQ/fireweave-sdk/tree/master/sdks/python
|
|
13
|
+
Keywords: feature-flags,openfeature,posthog,rollout,release
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: posthog
|
|
27
|
+
Requires-Dist: posthog==7.31.0; extra == "posthog"
|
|
28
|
+
Provides-Extra: openfeature
|
|
29
|
+
Requires-Dist: openfeature-sdk<0.11,>=0.10.0; extra == "openfeature"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: posthog==7.31.0; extra == "dev"
|
|
32
|
+
Requires-Dist: openfeature-sdk<0.11,>=0.10.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# fireweave (Python SDK)
|
|
37
|
+
|
|
38
|
+
Fireweave polyglot SDK for Python — OpenFeature-compatible, server-first flag
|
|
39
|
+
evaluation with release-safety extensions (spec v0.1.0).
|
|
40
|
+
|
|
41
|
+
- **Core**: zero runtime dependencies (`fireweave` + `InMemoryAdapter`).
|
|
42
|
+
- **Extras**: `fireweave[posthog]` (PostHog backend, `posthog==7.31.0`),
|
|
43
|
+
`fireweave[openfeature]` (OpenFeature provider, `openfeature-sdk>=0.10,<0.11`).
|
|
44
|
+
|
|
45
|
+
## Quick start (offline, in-memory)
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from fireweave import FireweaveClient, FireweaveRuntime, InMemoryAdapter
|
|
49
|
+
|
|
50
|
+
adapter = InMemoryAdapter({
|
|
51
|
+
"new-checkout": {"type": "boolean", "enabled": True, "variant": "on", "value": True},
|
|
52
|
+
})
|
|
53
|
+
runtime = FireweaveRuntime(adapter)
|
|
54
|
+
runtime.initialize()
|
|
55
|
+
client = FireweaveClient(runtime)
|
|
56
|
+
|
|
57
|
+
assert client.flags.get_boolean_value("new-checkout", False) is True
|
|
58
|
+
client.shutdown()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## OpenFeature
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from openfeature import api
|
|
65
|
+
from fireweave import FireweaveRuntime, InMemoryAdapter
|
|
66
|
+
from fireweave.openfeature import FireweaveProvider
|
|
67
|
+
|
|
68
|
+
api.set_provider(FireweaveProvider(FireweaveRuntime(InMemoryAdapter({...}))))
|
|
69
|
+
client = api.get_client()
|
|
70
|
+
client.get_boolean_value("new-checkout", False)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## PostHog backend
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from fireweave import FireweaveConfig, FireweaveRuntime, FireweaveClient
|
|
77
|
+
from fireweave.adapters.posthog import PostHogAdapter
|
|
78
|
+
|
|
79
|
+
config = FireweaveConfig(project_api_key="phc_...", host="https://us.i.posthog.com")
|
|
80
|
+
runtime = FireweaveRuntime(PostHogAdapter(config=config), config)
|
|
81
|
+
runtime.initialize(backend_required=True)
|
|
82
|
+
client = FireweaveClient(runtime)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Sync / async
|
|
86
|
+
|
|
87
|
+
The core is a thread-safe **sync** runtime; asyncio servers use
|
|
88
|
+
`fireweave.aio.AsyncFireweaveClient`, which offloads calls via
|
|
89
|
+
`asyncio.to_thread`. See `docs/adr/0004` and the module docstring.
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python -m venv .venv && .venv/bin/pip install -e '.[dev]'
|
|
95
|
+
.venv/bin/pytest # unit + conformance tests
|
|
96
|
+
.venv/bin/python conformance/run_conformance.py # normalized results JSON
|
|
97
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# fireweave (Python SDK)
|
|
2
|
+
|
|
3
|
+
Fireweave polyglot SDK for Python — OpenFeature-compatible, server-first flag
|
|
4
|
+
evaluation with release-safety extensions (spec v0.1.0).
|
|
5
|
+
|
|
6
|
+
- **Core**: zero runtime dependencies (`fireweave` + `InMemoryAdapter`).
|
|
7
|
+
- **Extras**: `fireweave[posthog]` (PostHog backend, `posthog==7.31.0`),
|
|
8
|
+
`fireweave[openfeature]` (OpenFeature provider, `openfeature-sdk>=0.10,<0.11`).
|
|
9
|
+
|
|
10
|
+
## Quick start (offline, in-memory)
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from fireweave import FireweaveClient, FireweaveRuntime, InMemoryAdapter
|
|
14
|
+
|
|
15
|
+
adapter = InMemoryAdapter({
|
|
16
|
+
"new-checkout": {"type": "boolean", "enabled": True, "variant": "on", "value": True},
|
|
17
|
+
})
|
|
18
|
+
runtime = FireweaveRuntime(adapter)
|
|
19
|
+
runtime.initialize()
|
|
20
|
+
client = FireweaveClient(runtime)
|
|
21
|
+
|
|
22
|
+
assert client.flags.get_boolean_value("new-checkout", False) is True
|
|
23
|
+
client.shutdown()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## OpenFeature
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from openfeature import api
|
|
30
|
+
from fireweave import FireweaveRuntime, InMemoryAdapter
|
|
31
|
+
from fireweave.openfeature import FireweaveProvider
|
|
32
|
+
|
|
33
|
+
api.set_provider(FireweaveProvider(FireweaveRuntime(InMemoryAdapter({...}))))
|
|
34
|
+
client = api.get_client()
|
|
35
|
+
client.get_boolean_value("new-checkout", False)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## PostHog backend
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from fireweave import FireweaveConfig, FireweaveRuntime, FireweaveClient
|
|
42
|
+
from fireweave.adapters.posthog import PostHogAdapter
|
|
43
|
+
|
|
44
|
+
config = FireweaveConfig(project_api_key="phc_...", host="https://us.i.posthog.com")
|
|
45
|
+
runtime = FireweaveRuntime(PostHogAdapter(config=config), config)
|
|
46
|
+
runtime.initialize(backend_required=True)
|
|
47
|
+
client = FireweaveClient(runtime)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Sync / async
|
|
51
|
+
|
|
52
|
+
The core is a thread-safe **sync** runtime; asyncio servers use
|
|
53
|
+
`fireweave.aio.AsyncFireweaveClient`, which offloads calls via
|
|
54
|
+
`asyncio.to_thread`. See `docs/adr/0004` and the module docstring.
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
python -m venv .venv && .venv/bin/pip install -e '.[dev]'
|
|
60
|
+
.venv/bin/pytest # unit + conformance tests
|
|
61
|
+
.venv/bin/python conformance/run_conformance.py # normalized results JSON
|
|
62
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fireweave"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Fireweave polyglot SDK for Python — OpenFeature-compatible, server-first flag evaluation and release-safety extensions (spec v0.1.0)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Fireweave AI, Inc." }]
|
|
14
|
+
keywords = ["feature-flags", "openfeature", "posthog", "rollout", "release"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
# Core has NO runtime dependencies: the OpenFeature provider and the PostHog
|
|
27
|
+
# adapter are optional integrations behind extras. `import fireweave` plus the
|
|
28
|
+
# InMemory adapter work with the standard library only.
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/FireWeave-HQ/fireweave-sdk"
|
|
33
|
+
Documentation = "https://github.com/FireWeave-HQ/fireweave-sdk/tree/master/docs"
|
|
34
|
+
Repository = "https://github.com/FireWeave-HQ/fireweave-sdk"
|
|
35
|
+
Issues = "https://github.com/FireWeave-HQ/fireweave-sdk/issues"
|
|
36
|
+
Changelog = "https://github.com/FireWeave-HQ/fireweave-sdk/blob/master/CHANGELOG.md"
|
|
37
|
+
"Source" = "https://github.com/FireWeave-HQ/fireweave-sdk/tree/master/sdks/python"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
# ADR-0002 pin: PostHog snapshot API (`evaluate_flags`) — exact pin to absorb
|
|
41
|
+
# the in-flight /flags migration.
|
|
42
|
+
posthog = ["posthog==7.31.0"]
|
|
43
|
+
# Pre-1.0 OpenFeature SDK: pin below 0.11 (decision brief §3 / ADR-0003).
|
|
44
|
+
openfeature = ["openfeature-sdk>=0.10.0,<0.11"]
|
|
45
|
+
dev = [
|
|
46
|
+
"posthog==7.31.0",
|
|
47
|
+
"openfeature-sdk>=0.10.0,<0.11",
|
|
48
|
+
"pytest>=8",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
where = ["src"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
fireweave = ["py.typed"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
addopts = "-q"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Fireweave SDK for Python (spec v0.1.0).
|
|
2
|
+
|
|
3
|
+
Core installs with zero runtime dependencies. Optional extras:
|
|
4
|
+
|
|
5
|
+
- ``fireweave[posthog]`` — PostHog backend adapter (``fireweave.adapters.posthog``)
|
|
6
|
+
- ``fireweave[openfeature]`` — OpenFeature provider (``fireweave.openfeature``)
|
|
7
|
+
|
|
8
|
+
Quick start (in-memory, offline)::
|
|
9
|
+
|
|
10
|
+
from fireweave import FireweaveClient, FireweaveRuntime, InMemoryAdapter
|
|
11
|
+
|
|
12
|
+
adapter = InMemoryAdapter({"my-flag": {"type": "boolean", "enabled": True,
|
|
13
|
+
"variant": "on", "value": True}})
|
|
14
|
+
runtime = FireweaveRuntime(adapter)
|
|
15
|
+
runtime.initialize()
|
|
16
|
+
client = FireweaveClient(runtime)
|
|
17
|
+
client.control_points.get_boolean_value("my-flag", False)
|
|
18
|
+
client.shutdown()
|
|
19
|
+
|
|
20
|
+
There are no hidden global clients: everything is constructed explicitly and
|
|
21
|
+
injectable for tests.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from ._version import SPEC_VERSION, __version__
|
|
25
|
+
from .adapters import (
|
|
26
|
+
BackendAdapter,
|
|
27
|
+
FlagResolution,
|
|
28
|
+
FireweaveLocalAdapter,
|
|
29
|
+
FireweaveRemoteAdapter,
|
|
30
|
+
InMemoryAdapter,
|
|
31
|
+
RegisterTargetOptions,
|
|
32
|
+
RegisterTargetResult,
|
|
33
|
+
)
|
|
34
|
+
from .capabilities import CANONICAL_CAPABILITIES, CapabilityRegistry
|
|
35
|
+
from .client import (
|
|
36
|
+
CapabilityResult,
|
|
37
|
+
ExposureResult,
|
|
38
|
+
FireweaveClient,
|
|
39
|
+
FlushResult,
|
|
40
|
+
ReleaseContext,
|
|
41
|
+
ReleaseResult,
|
|
42
|
+
SignalResult,
|
|
43
|
+
)
|
|
44
|
+
from .config import DEFAULT_ALLOWED_HOSTS, FireweaveConfig
|
|
45
|
+
from .context import ContextLimits, EvaluationContext, merge_contexts, validate_context
|
|
46
|
+
from .decision import Decision, Reason
|
|
47
|
+
from .errors import (
|
|
48
|
+
AlreadyClosedError,
|
|
49
|
+
AuthenticationError,
|
|
50
|
+
AuthorizationError,
|
|
51
|
+
BackendUnavailableError,
|
|
52
|
+
ConfigurationError,
|
|
53
|
+
ErrorKind,
|
|
54
|
+
FireweaveError,
|
|
55
|
+
FlagNotFoundError,
|
|
56
|
+
InternalError,
|
|
57
|
+
InvalidContextError,
|
|
58
|
+
MalformedResponseError,
|
|
59
|
+
NetworkError,
|
|
60
|
+
NotReadyError,
|
|
61
|
+
RateLimitedError,
|
|
62
|
+
TargetingKeyMissingError,
|
|
63
|
+
TimeoutError_,
|
|
64
|
+
TypeMismatchError,
|
|
65
|
+
UnsupportedCapabilityError,
|
|
66
|
+
)
|
|
67
|
+
from .runtime import EvaluationOptions, FireweaveRuntime, LifecycleState
|
|
68
|
+
from .types import FlagType, JsonValue
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"__version__",
|
|
72
|
+
"SPEC_VERSION",
|
|
73
|
+
# runtime / client
|
|
74
|
+
"FireweaveClient",
|
|
75
|
+
"FireweaveRuntime",
|
|
76
|
+
"FireweaveConfig",
|
|
77
|
+
"DEFAULT_ALLOWED_HOSTS",
|
|
78
|
+
"LifecycleState",
|
|
79
|
+
"EvaluationOptions",
|
|
80
|
+
# adapters
|
|
81
|
+
"BackendAdapter",
|
|
82
|
+
"FlagResolution",
|
|
83
|
+
"InMemoryAdapter",
|
|
84
|
+
"FireweaveLocalAdapter",
|
|
85
|
+
"FireweaveRemoteAdapter",
|
|
86
|
+
"RegisterTargetOptions",
|
|
87
|
+
"RegisterTargetResult",
|
|
88
|
+
# context / decisions
|
|
89
|
+
"ContextLimits",
|
|
90
|
+
"EvaluationContext",
|
|
91
|
+
"merge_contexts",
|
|
92
|
+
"validate_context",
|
|
93
|
+
"Decision",
|
|
94
|
+
"Reason",
|
|
95
|
+
"FlagType",
|
|
96
|
+
"JsonValue",
|
|
97
|
+
# capabilities
|
|
98
|
+
"CANONICAL_CAPABILITIES",
|
|
99
|
+
"CapabilityRegistry",
|
|
100
|
+
# extension results
|
|
101
|
+
"ReleaseContext",
|
|
102
|
+
"ReleaseResult",
|
|
103
|
+
"ExposureResult",
|
|
104
|
+
"FlushResult",
|
|
105
|
+
"SignalResult",
|
|
106
|
+
"CapabilityResult",
|
|
107
|
+
# errors
|
|
108
|
+
"ErrorKind",
|
|
109
|
+
"FireweaveError",
|
|
110
|
+
"NotReadyError",
|
|
111
|
+
"FlagNotFoundError",
|
|
112
|
+
"TypeMismatchError",
|
|
113
|
+
"InvalidContextError",
|
|
114
|
+
"TargetingKeyMissingError",
|
|
115
|
+
"AuthenticationError",
|
|
116
|
+
"AuthorizationError",
|
|
117
|
+
"RateLimitedError",
|
|
118
|
+
"TimeoutError_",
|
|
119
|
+
"NetworkError",
|
|
120
|
+
"BackendUnavailableError",
|
|
121
|
+
"MalformedResponseError",
|
|
122
|
+
"UnsupportedCapabilityError",
|
|
123
|
+
"ConfigurationError",
|
|
124
|
+
"AlreadyClosedError",
|
|
125
|
+
"InternalError",
|
|
126
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Backend adapters: protocol, in-memory, local, remote (default), PostHog (optional)."""
|
|
2
|
+
|
|
3
|
+
from .base import (
|
|
4
|
+
BackendAdapter,
|
|
5
|
+
FlagResolution,
|
|
6
|
+
RegisterTargetOptions,
|
|
7
|
+
RegisterTargetResult,
|
|
8
|
+
TargetKind,
|
|
9
|
+
)
|
|
10
|
+
from .local import FireweaveLocalAdapter
|
|
11
|
+
from .memory import InMemoryAdapter
|
|
12
|
+
from .remote import FireweaveRemoteAdapter
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"BackendAdapter",
|
|
16
|
+
"FlagResolution",
|
|
17
|
+
"RegisterTargetOptions",
|
|
18
|
+
"RegisterTargetResult",
|
|
19
|
+
"TargetKind",
|
|
20
|
+
"InMemoryAdapter",
|
|
21
|
+
"FireweaveLocalAdapter",
|
|
22
|
+
"FireweaveRemoteAdapter",
|
|
23
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""BackendAdapter protocol and vendor-neutral resolution record.
|
|
2
|
+
|
|
3
|
+
Adapters translate a vendor backend (PostHog, in-memory fixtures, ...) into
|
|
4
|
+
:class:`FlagResolution` records. No vendor types cross this boundary — the
|
|
5
|
+
runtime and public API only ever see Fireweave-owned shapes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Dict, Literal, Optional, Protocol, runtime_checkable
|
|
12
|
+
|
|
13
|
+
from ..context import EvaluationContext
|
|
14
|
+
from ..errors import FireweaveError
|
|
15
|
+
from ..types import JsonValue
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BackendAdapter",
|
|
19
|
+
"FlagResolution",
|
|
20
|
+
"TargetKind",
|
|
21
|
+
"RegisterTargetOptions",
|
|
22
|
+
"RegisterTargetResult",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
TargetKind = Literal["user", "device"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class RegisterTargetOptions:
|
|
30
|
+
"""Options for ``POST /v1/targets/register`` (spec/remote-register-target).
|
|
31
|
+
|
|
32
|
+
Omitted fields are left off the wire rather than sent as null/default —
|
|
33
|
+
the server defaults ``kind`` to ``user`` when absent.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
kind: Optional[TargetKind] = None
|
|
37
|
+
properties: Optional[Dict[str, JsonValue]] = None
|
|
38
|
+
environment: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class RegisterTargetResult:
|
|
43
|
+
"""Outcome of target registration.
|
|
44
|
+
|
|
45
|
+
``ok=False`` means the target was NOT registered — rules that depend on its
|
|
46
|
+
properties will not match until a later attempt succeeds. Callers in a
|
|
47
|
+
login path normally ignore this; a careful caller logs it.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
ok: bool
|
|
51
|
+
error: Optional[FireweaveError] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class FlagResolution:
|
|
56
|
+
"""Vendor-neutral outcome of resolving one flag.
|
|
57
|
+
|
|
58
|
+
``matched`` is False when targeting conditions did not select the caller
|
|
59
|
+
(the runtime then serves the caller default with reason DEFAULT).
|
|
60
|
+
``fireweave_reason`` lets adapters force a canonical reason (e.g. SPLIT).
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
value: JsonValue
|
|
64
|
+
variant: Optional[str] = None
|
|
65
|
+
enabled: bool = True
|
|
66
|
+
matched: bool = True
|
|
67
|
+
version: Optional[int] = None
|
|
68
|
+
vendor_flag_id: Optional[int] = None
|
|
69
|
+
reason_code: Optional[str] = None
|
|
70
|
+
condition_index: Optional[int] = None
|
|
71
|
+
payload: Optional[JsonValue] = None
|
|
72
|
+
fireweave_reason: Optional[str] = None
|
|
73
|
+
from_cache: bool = False
|
|
74
|
+
quota_limited: bool = False
|
|
75
|
+
extra_metadata: Dict[str, Any] = field(default_factory=dict)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@runtime_checkable
|
|
79
|
+
class BackendAdapter(Protocol):
|
|
80
|
+
"""Protocol every Fireweave backend adapter implements.
|
|
81
|
+
|
|
82
|
+
``resolve`` raises a :class:`fireweave.errors.FireweaveError` subtype on
|
|
83
|
+
failure (FlagNotFound, Network, Timeout, ...); the runtime converts errors
|
|
84
|
+
into default-valued decisions — evaluation APIs never propagate them.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def initialize(self) -> None:
|
|
88
|
+
"""Bring the backend up; raise FireweaveError on fatal config."""
|
|
89
|
+
|
|
90
|
+
def resolve(self, flag_key: str, context: EvaluationContext) -> FlagResolution:
|
|
91
|
+
"""Resolve one flag against a validated, merged context."""
|
|
92
|
+
|
|
93
|
+
def shutdown(self, timeout_ms: int) -> None:
|
|
94
|
+
"""Deterministically flush and release resources within ``timeout_ms``.
|
|
95
|
+
|
|
96
|
+
Idempotent; must never block past the deadline.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# Optional adapter surface (duck-typed, ruling 17 / capabilities matrix):
|
|
101
|
+
#
|
|
102
|
+
# - ``backend_name: str`` — "fireweave" | "posthog" | "inmemory" | "none" | "other"
|
|
103
|
+
# (spec/capabilities.schema.json runtime.backend).
|
|
104
|
+
# - ``runtime_features() -> Dict[str, bool]`` — adapter-dependent runtime
|
|
105
|
+
# capability booleans merged into ``capabilities.get()``.
|
|
106
|
+
# - ``register_target(targeting_key, options) -> RegisterTargetResult`` —
|
|
107
|
+
# durable identity registration (``POST /v1/targets/register``). Returns
|
|
108
|
+
# ``ok=False`` rather than raising — registration sits in login paths.
|
|
109
|
+
# - ``send_exposures(events: list) -> None`` — telemetry sink for flushed
|
|
110
|
+
# exposure events.
|
|
111
|
+
# - ``deliver_signal(signal: Dict) -> None`` — telemetry sink for recorded
|
|
112
|
+
# signals.
|
|
113
|
+
# - ``deliver_release(event: Dict) -> None`` — telemetry sink for release
|
|
114
|
+
# transitions.
|
|
115
|
+
#
|
|
116
|
+
# All sink methods are best-effort: implementations swallow vendor errors —
|
|
117
|
+
# telemetry loss must never affect callers.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Local development adapter — the DEV substrate for a scaffolded harness.
|
|
2
|
+
|
|
3
|
+
Counterpart to :class:`FireweaveRemoteAdapter`: prod evaluates control points
|
|
4
|
+
against fw-server; dev evaluates them here, in-process, with no network and no
|
|
5
|
+
credentials. Because it satisfies the same :class:`BackendAdapter` port, the
|
|
6
|
+
dev branch of a harness runs through the same :class:`FireweaveRuntime` as
|
|
7
|
+
prod — inheriting identical lifecycle gating and context canonicalization.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Dict, Mapping, Optional
|
|
13
|
+
|
|
14
|
+
from ..context import EvaluationContext
|
|
15
|
+
from ..errors import FlagNotFoundError
|
|
16
|
+
from .base import FlagResolution
|
|
17
|
+
|
|
18
|
+
__all__ = ["FireweaveLocalAdapter"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FireweaveLocalAdapter:
|
|
22
|
+
"""In-process boolean overrides for local development.
|
|
23
|
+
|
|
24
|
+
Resolution policy:
|
|
25
|
+
|
|
26
|
+
- a key present in ``dev_flags`` resolves to its mapped value with reason
|
|
27
|
+
``STATIC``;
|
|
28
|
+
- every other key raises :class:`FlagNotFoundError`, which the runtime
|
|
29
|
+
turns into an ERROR decision. :func:`make_fireweave_local_provider`
|
|
30
|
+
rewrites that single outcome to a clean DEFAULT for the OpenFeature path.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
backend_name = "other"
|
|
34
|
+
|
|
35
|
+
def __init__(self, dev_flags: Optional[Mapping[str, bool]] = None) -> None:
|
|
36
|
+
self._dev_flags: Dict[str, bool] = dict(dev_flags or {})
|
|
37
|
+
self._closed = False
|
|
38
|
+
|
|
39
|
+
def initialize(self) -> None:
|
|
40
|
+
self._closed = False
|
|
41
|
+
|
|
42
|
+
def resolve(self, flag_key: str, context: EvaluationContext) -> FlagResolution:
|
|
43
|
+
# A ``dev_flags`` hit reports ``enabled=True`` alongside reason STATIC.
|
|
44
|
+
# Reporting ``enabled=False`` for an override of ``False`` would make
|
|
45
|
+
# the runtime label the decision DISABLED — "switched off upstream" —
|
|
46
|
+
# not what a local override expresses.
|
|
47
|
+
#
|
|
48
|
+
# Values are always boolean, so reading an overridden key as a string
|
|
49
|
+
# or number yields TYPE_MISMATCH rather than silently defaulting.
|
|
50
|
+
del context # unused; kept for BackendAdapter signature parity
|
|
51
|
+
if flag_key not in self._dev_flags:
|
|
52
|
+
raise FlagNotFoundError()
|
|
53
|
+
override = self._dev_flags[flag_key]
|
|
54
|
+
return FlagResolution(
|
|
55
|
+
value=override,
|
|
56
|
+
variant="on" if override else "off",
|
|
57
|
+
enabled=True,
|
|
58
|
+
matched=True,
|
|
59
|
+
fireweave_reason="STATIC",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def shutdown(self, timeout_ms: int) -> None:
|
|
63
|
+
del timeout_ms
|
|
64
|
+
self._closed = True
|
|
65
|
+
|
|
66
|
+
def runtime_features(self) -> Dict[str, bool]:
|
|
67
|
+
return {
|
|
68
|
+
"remoteEvaluation": False,
|
|
69
|
+
"localEvaluation": True,
|
|
70
|
+
"localOnly": True,
|
|
71
|
+
# No exposure sink exists locally; claiming otherwise would make
|
|
72
|
+
# capabilities.get() advertise emission that silently goes nowhere.
|
|
73
|
+
"exposureEmission": False,
|
|
74
|
+
"sideEffectFreeReads": True,
|
|
75
|
+
"groupAnalytics": False,
|
|
76
|
+
}
|