openfeature-provider-flagd 0.2.6__tar.gz → 0.2.7__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.
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/CHANGELOG.md +17 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/LICENSE +1 -1
- openfeature_provider_flagd-0.2.7/PKG-INFO +208 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/README.md +39 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/pyproject.toml +35 -53
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/provider.py +10 -3
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +82 -27
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +7 -3
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py +70 -38
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py +5 -1
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/protocol.py +7 -3
- openfeature_provider_flagd-0.2.7/src/openfeature/contrib/provider/flagd/resolvers/types.py +7 -0
- openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +72 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.pyi +41 -22
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.py +2 -2
- openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +310 -0
- openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +56 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.pyi +33 -10
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.py +2 -2
- openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +161 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.py +4 -4
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.pyi +25 -19
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.py +2 -2
- openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +311 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.py +4 -4
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.pyi +9 -9
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.py +2 -2
- openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +125 -0
- openfeature_provider_flagd-0.2.7/src/scripts/scripts.py +28 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/flagd_container.py +3 -9
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/provider_steps.py +3 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/test_errors.py +1 -1
- openfeature_provider_flagd-0.2.7/tests/test_grpc_watcher.py +163 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/test_targeting.py +1 -1
- openfeature_provider_flagd-0.2.6/PKG-INFO +0 -370
- openfeature_provider_flagd-0.2.6/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +0 -72
- openfeature_provider_flagd-0.2.6/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +0 -155
- openfeature_provider_flagd-0.2.6/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +0 -50
- openfeature_provider_flagd-0.2.6/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +0 -86
- openfeature_provider_flagd-0.2.6/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +0 -156
- openfeature_provider_flagd-0.2.6/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +0 -70
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/.gitignore +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/pytest.ini +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/config.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/flag_type.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/sync_metadata_hook.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/file/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/file/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/file/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/inprocess/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/inprocess/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/inprocess/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/parsers.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/paths.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/rpc/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/rpc/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/rpc/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/_utils.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/config_steps.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/context_steps.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/event_steps.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/flag_step.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/e2e/testfilter.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-broken-default.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-broken-state.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-broken-targeting.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-broken-variants.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-combined-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-disabled.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-invalid.not-json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-no-state.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-set-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-without-default.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-wrong-structure.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-wrong-variant.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag.yaml +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-flag-metadata-list.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-flag-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-flag-set-metadata-list.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-flag-set-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-fractional-args-wrong-content.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-fractional-args.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-fractional-weights-strings.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-fractional-weights.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-semver-args.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-semver-op.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-stringcomp-args.json +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/test_config.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/test_file_store.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/test_flagd.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/test_in_process.py +0 -0
- {openfeature_provider_flagd-0.2.6 → openfeature_provider_flagd-0.2.7}/tests/test_metadata.py +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.7](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.6...openfeature-provider-flagd/v0.2.7) (2026-01-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🐛 Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **build:** bump protobuf package version to a compatible version ([#327](https://github.com/open-feature/python-sdk-contrib/issues/327)) ([223a903](https://github.com/open-feature/python-sdk-contrib/commit/223a9037cea6c7ac6b1c9bb7a159987e592ce5a4))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ✨ New Features
|
|
12
|
+
|
|
13
|
+
* adjust to flagd metadata toggle ([#287](https://github.com/open-feature/python-sdk-contrib/issues/287)) ([6ac014d](https://github.com/open-feature/python-sdk-contrib/commit/6ac014d1af7a90b09203ae38dfd60bba275bc9a9))
|
|
14
|
+
* **flagd:** Implement dual-mode selector handling (header and body) for in-process mode in python-sdk-contrib ([#320](https://github.com/open-feature/python-sdk-contrib/issues/320)) ([9ad25a4](https://github.com/open-feature/python-sdk-contrib/commit/9ad25a4ddebb91e4383b108d478288a782ede796))
|
|
15
|
+
* **flagd:** support Python 3.14 for flagd ([#307](https://github.com/open-feature/python-sdk-contrib/issues/307)) ([a60b237](https://github.com/open-feature/python-sdk-contrib/commit/a60b23756fce05220201a0d0660be2a06a6807f4))
|
|
16
|
+
* migrate to uv ([#293](https://github.com/open-feature/python-sdk-contrib/issues/293)) ([9590554](https://github.com/open-feature/python-sdk-contrib/commit/9590554be4c8a7f77b0c0f20d6b48076c9870f52))
|
|
17
|
+
* Update python-sdk ([#289](https://github.com/open-feature/python-sdk-contrib/issues/289)) ([f2028f5](https://github.com/open-feature/python-sdk-contrib/commit/f2028f5f81824310a842507a3127a3bb9bc8a984))
|
|
18
|
+
* Use time.monotonic to avoid endless loop when using time machine ([#330](https://github.com/open-feature/python-sdk-contrib/issues/330)) ([a424cdc](https://github.com/open-feature/python-sdk-contrib/commit/a424cdc68cba4dd3da28a9b18ca10ffbaf1e2689))
|
|
19
|
+
|
|
3
20
|
## [0.2.6](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.5...openfeature-provider-flagd/v0.2.6) (2025-07-22)
|
|
4
21
|
|
|
5
22
|
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright
|
|
189
|
+
Copyright OpenFeature Maintainers
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openfeature-provider-flagd
|
|
3
|
+
Version: 0.2.7
|
|
4
|
+
Summary: OpenFeature provider for the flagd flag evaluation engine
|
|
5
|
+
Project-URL: Homepage, https://github.com/open-feature/python-sdk-contrib
|
|
6
|
+
Author-email: OpenFeature <openfeature-core@groups.io>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Requires-Dist: cachebox<6.0.0,>=5.1.0
|
|
13
|
+
Requires-Dist: grpcio>=1.76.0
|
|
14
|
+
Requires-Dist: mmh3<6.0.0,>=5.0.0
|
|
15
|
+
Requires-Dist: openfeature-sdk>=0.8.2
|
|
16
|
+
Requires-Dist: panzi-json-logic>=1.0.1
|
|
17
|
+
Requires-Dist: protobuf<7.0.0,>=6.30.0
|
|
18
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
19
|
+
Requires-Dist: semver<4,>=3
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# flagd Provider for OpenFeature
|
|
23
|
+
|
|
24
|
+
This provider is designed to use flagd's [evaluation protocol](https://github.com/open-feature/schemas/blob/main/protobuf/schema/v1/schema.proto), or locally evaluate flags defined in a flagd [flag definition](https://github.com/open-feature/schemas/blob/main/json/flagd-definitions.json) via the OpenFeature Python SDK.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pip install openfeature-provider-flagd
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Configuration and Usage
|
|
33
|
+
|
|
34
|
+
The flagd provider can operate in two modes: [RPC](#remote-resolver-rpc) (evaluation takes place in flagd, via gRPC calls) or [in-process](#in-process-resolver) (evaluation takes place in-process, with the provider getting a ruleset from a compliant sync-source).
|
|
35
|
+
|
|
36
|
+
### Remote resolver (RPC)
|
|
37
|
+
|
|
38
|
+
This is the default mode of operation of the provider.
|
|
39
|
+
In this mode, `FlagdProvider` communicates with [flagd](https://github.com/open-feature/flagd) via the gRPC protocol.
|
|
40
|
+
Flag evaluations take place remotely at the connected flagd instance.
|
|
41
|
+
|
|
42
|
+
Instantiate a new FlagdProvider instance and configure the OpenFeature SDK to use it:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from openfeature import api
|
|
46
|
+
from openfeature.contrib.provider.flagd import FlagdProvider
|
|
47
|
+
|
|
48
|
+
api.set_provider(FlagdProvider())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### In-process resolver
|
|
52
|
+
|
|
53
|
+
This mode performs flag evaluations locally (in-process). Flag configurations for evaluation are obtained via gRPC protocol using [sync protobuf schema](https://buf.build/open-feature/flagd/file/main:sync/v1/sync_service.proto) service definition.
|
|
54
|
+
|
|
55
|
+
Consider the following example to create a `FlagdProvider` with in-process evaluations,
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from openfeature import api
|
|
59
|
+
from openfeature.contrib.provider.flagd import FlagdProvider
|
|
60
|
+
from openfeature.contrib.provider.flagd.config import ResolverType
|
|
61
|
+
|
|
62
|
+
api.set_provider(FlagdProvider(
|
|
63
|
+
resolver_type=ResolverType.IN_PROCESS,
|
|
64
|
+
))
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
In the above example, in-process handlers attempt to connect to a sync service on address `localhost:8013` to obtain [flag definitions](https://github.com/open-feature/schemas/blob/main/json/flags.json).
|
|
68
|
+
|
|
69
|
+
<!--
|
|
70
|
+
#### Sync-metadata
|
|
71
|
+
|
|
72
|
+
To support the injection of contextual data configured in flagd for in-process evaluation, the provider exposes a `getSyncMetadata` accessor which provides the most recent value returned by the [GetMetadata RPC](https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1#flagd.sync.v1.FlagSyncService.GetMetadata).
|
|
73
|
+
The value is updated with every (re)connection to the sync implementation.
|
|
74
|
+
This can be used to enrich evaluations with such data.
|
|
75
|
+
If the `in-process` mode is not used, and before the provider is ready, the `getSyncMetadata` returns an empty map.
|
|
76
|
+
-->
|
|
77
|
+
### File mode
|
|
78
|
+
|
|
79
|
+
In-process resolvers can also work in an offline mode.
|
|
80
|
+
To enable this mode, you should provide a valid flag configuration file with the option `offlineFlagSourcePath`.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from openfeature import api
|
|
84
|
+
from openfeature.contrib.provider.flagd import FlagdProvider
|
|
85
|
+
from openfeature.contrib.provider.flagd.config import ResolverType
|
|
86
|
+
|
|
87
|
+
api.set_provider(FlagdProvider(
|
|
88
|
+
resolver_type=ResolverType.FILE,
|
|
89
|
+
offline_flag_source_path="my-flag.json",
|
|
90
|
+
))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Provider will attempt to detect file changes using polling.
|
|
94
|
+
Polling happens at 5 second intervals and this is currently unconfigurable.
|
|
95
|
+
This mode is useful for local development, tests and offline applications.
|
|
96
|
+
|
|
97
|
+
### Configuration options
|
|
98
|
+
|
|
99
|
+
The default options can be defined in the FlagdProvider constructor.
|
|
100
|
+
|
|
101
|
+
| Option name | Environment variable name | Type & Values | Default | Compatible resolver |
|
|
102
|
+
|--------------------------|--------------------------------|----------------------------|-------------------------------|---------------------|
|
|
103
|
+
| resolver_type | FLAGD_RESOLVER | enum - `rpc`, `in-process` | rpc | |
|
|
104
|
+
| host | FLAGD_HOST | str | localhost | rpc & in-process |
|
|
105
|
+
| port | FLAGD_PORT | int | 8013 (rpc), 8015 (in-process) | rpc & in-process |
|
|
106
|
+
| tls | FLAGD_TLS | bool | false | rpc & in-process |
|
|
107
|
+
| cert_path | FLAGD_SERVER_CERT_PATH | String | null | rpc & in-process |
|
|
108
|
+
| deadline | FLAGD_DEADLINE_MS | int | 500 | rpc & in-process |
|
|
109
|
+
| stream_deadline_ms | FLAGD_STREAM_DEADLINE_MS | int | 600000 | rpc & in-process |
|
|
110
|
+
| keep_alive_time | FLAGD_KEEP_ALIVE_TIME_MS | int | 0 | rpc & in-process |
|
|
111
|
+
| selector | FLAGD_SOURCE_SELECTOR | str | null | in-process |
|
|
112
|
+
| cache_type | FLAGD_CACHE | enum - `lru`, `disabled` | lru | rpc |
|
|
113
|
+
| max_cache_size | FLAGD_MAX_CACHE_SIZE | int | 1000 | rpc |
|
|
114
|
+
| retry_backoff_ms | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc |
|
|
115
|
+
| offline_flag_source_path | FLAGD_OFFLINE_FLAG_SOURCE_PATH | str | null | in-process |
|
|
116
|
+
|
|
117
|
+
> [!NOTE]
|
|
118
|
+
> The `selector` configuration is only used in **in-process** mode for filtering flag configurations. See [Selector Handling](#selector-handling-in-process-mode-only) for migration guidance.
|
|
119
|
+
|
|
120
|
+
<!-- not implemented
|
|
121
|
+
| target_uri | FLAGD_TARGET_URI | alternative to host/port, supporting custom name resolution | string | null | rpc & in-process |
|
|
122
|
+
| socket_path | FLAGD_SOCKET_PATH | alternative to host port, unix socket | String | null | rpc & in-process |
|
|
123
|
+
| context_enricher | - | sync-metadata to evaluation context mapping function | function | identity function | in-process |
|
|
124
|
+
| offline_pollIntervalMs | FLAGD_OFFLINE_POLL_MS | poll interval for reading offlineFlagSourcePath | int | 5000 | in-process |
|
|
125
|
+
-->
|
|
126
|
+
|
|
127
|
+
> [!NOTE]
|
|
128
|
+
> Some configurations are only applicable for RPC resolver.
|
|
129
|
+
|
|
130
|
+
### Selector Handling (In-Process Mode Only)
|
|
131
|
+
|
|
132
|
+
> [!IMPORTANT]
|
|
133
|
+
> This section only applies to **in-process** resolver mode. RPC mode is not affected by selector handling changes.
|
|
134
|
+
|
|
135
|
+
#### Current Implementation
|
|
136
|
+
|
|
137
|
+
As of this SDK version, the `selector` parameter is passed via **both** gRPC metadata headers (`flagd-selector`) and the request body when using in-process mode. This dual approach ensures maximum compatibility with all flagd versions.
|
|
138
|
+
|
|
139
|
+
**Configuration Example:**
|
|
140
|
+
```python
|
|
141
|
+
from openfeature import api
|
|
142
|
+
from openfeature.contrib.provider.flagd import FlagdProvider
|
|
143
|
+
from openfeature.contrib.provider.flagd.config import ResolverType
|
|
144
|
+
|
|
145
|
+
api.set_provider(FlagdProvider(
|
|
146
|
+
resolver_type=ResolverType.IN_PROCESS,
|
|
147
|
+
selector="my-flag-source", # Passed via both header and request body
|
|
148
|
+
))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The selector is automatically passed via:
|
|
152
|
+
- **gRPC metadata header** (`flagd-selector`) - For flagd v0.11.0+ selector normalization
|
|
153
|
+
- **Request body** - For backward compatibility with older flagd versions
|
|
154
|
+
|
|
155
|
+
#### Backward Compatibility
|
|
156
|
+
|
|
157
|
+
This dual transmission approach ensures the Python SDK works seamlessly with all flagd service versions:
|
|
158
|
+
- **Older flagd versions** read the selector from the request body
|
|
159
|
+
- **Newer flagd versions (v0.11.0+)** prefer the selector from the gRPC metadata header
|
|
160
|
+
- Both approaches are supported simultaneously for maximum compatibility
|
|
161
|
+
|
|
162
|
+
**Related Resources:**
|
|
163
|
+
- Upstream issue: [open-feature/flagd#1814](https://github.com/open-feature/flagd/issues/1814)
|
|
164
|
+
- Selector normalization affects in-process evaluations that filter flag configurations by source
|
|
165
|
+
|
|
166
|
+
<!--
|
|
167
|
+
### Unix socket support
|
|
168
|
+
Unix socket communication with flagd is facilitated by usaging of the linux-native `epoll` library on `linux-x86_64`
|
|
169
|
+
only (ARM support is pending the release of `netty-transport-native-epoll` v5).
|
|
170
|
+
Unix sockets are not supported on other platforms or architectures.
|
|
171
|
+
-->
|
|
172
|
+
|
|
173
|
+
### Reconnection
|
|
174
|
+
|
|
175
|
+
Reconnection is supported by the underlying gRPC connections.
|
|
176
|
+
If the connection to flagd is lost, it will reconnect automatically.
|
|
177
|
+
A failure to connect will result in an [error event](https://openfeature.dev/docs/reference/concepts/events#provider_error) from the provider, though it will attempt to reconnect indefinitely.
|
|
178
|
+
|
|
179
|
+
### Deadlines
|
|
180
|
+
|
|
181
|
+
Deadlines are used to define how long the provider waits to complete initialization or flag evaluations.
|
|
182
|
+
They behave differently based on the resolver type.
|
|
183
|
+
|
|
184
|
+
#### Deadlines with Remote resolver (RPC)
|
|
185
|
+
|
|
186
|
+
If the remote evaluation call is not completed within this deadline, the gRPC call is terminated with the error `DEADLINE_EXCEEDED`
|
|
187
|
+
and the evaluation will default.
|
|
188
|
+
|
|
189
|
+
### TLS
|
|
190
|
+
|
|
191
|
+
TLS is available in situations where flagd is running on another host.
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
You may optionally supply an X.509 certificate in PEM format. Otherwise, the default certificate store will be used.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from openfeature import api
|
|
198
|
+
from openfeature.contrib.provider.flagd import FlagdProvider
|
|
199
|
+
|
|
200
|
+
api.set_provider(FlagdProvider(
|
|
201
|
+
tls=True, # use TLS
|
|
202
|
+
cert_path="etc/cert/ca.crt" # PEM cert
|
|
203
|
+
))
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
Apache 2.0 - See [LICENSE](./LICENSE) for more information.
|
|
@@ -93,6 +93,9 @@ The default options can be defined in the FlagdProvider constructor.
|
|
|
93
93
|
| retry_backoff_ms | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc |
|
|
94
94
|
| offline_flag_source_path | FLAGD_OFFLINE_FLAG_SOURCE_PATH | str | null | in-process |
|
|
95
95
|
|
|
96
|
+
> [!NOTE]
|
|
97
|
+
> The `selector` configuration is only used in **in-process** mode for filtering flag configurations. See [Selector Handling](#selector-handling-in-process-mode-only) for migration guidance.
|
|
98
|
+
|
|
96
99
|
<!-- not implemented
|
|
97
100
|
| target_uri | FLAGD_TARGET_URI | alternative to host/port, supporting custom name resolution | string | null | rpc & in-process |
|
|
98
101
|
| socket_path | FLAGD_SOCKET_PATH | alternative to host port, unix socket | String | null | rpc & in-process |
|
|
@@ -103,6 +106,42 @@ The default options can be defined in the FlagdProvider constructor.
|
|
|
103
106
|
> [!NOTE]
|
|
104
107
|
> Some configurations are only applicable for RPC resolver.
|
|
105
108
|
|
|
109
|
+
### Selector Handling (In-Process Mode Only)
|
|
110
|
+
|
|
111
|
+
> [!IMPORTANT]
|
|
112
|
+
> This section only applies to **in-process** resolver mode. RPC mode is not affected by selector handling changes.
|
|
113
|
+
|
|
114
|
+
#### Current Implementation
|
|
115
|
+
|
|
116
|
+
As of this SDK version, the `selector` parameter is passed via **both** gRPC metadata headers (`flagd-selector`) and the request body when using in-process mode. This dual approach ensures maximum compatibility with all flagd versions.
|
|
117
|
+
|
|
118
|
+
**Configuration Example:**
|
|
119
|
+
```python
|
|
120
|
+
from openfeature import api
|
|
121
|
+
from openfeature.contrib.provider.flagd import FlagdProvider
|
|
122
|
+
from openfeature.contrib.provider.flagd.config import ResolverType
|
|
123
|
+
|
|
124
|
+
api.set_provider(FlagdProvider(
|
|
125
|
+
resolver_type=ResolverType.IN_PROCESS,
|
|
126
|
+
selector="my-flag-source", # Passed via both header and request body
|
|
127
|
+
))
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The selector is automatically passed via:
|
|
131
|
+
- **gRPC metadata header** (`flagd-selector`) - For flagd v0.11.0+ selector normalization
|
|
132
|
+
- **Request body** - For backward compatibility with older flagd versions
|
|
133
|
+
|
|
134
|
+
#### Backward Compatibility
|
|
135
|
+
|
|
136
|
+
This dual transmission approach ensures the Python SDK works seamlessly with all flagd service versions:
|
|
137
|
+
- **Older flagd versions** read the selector from the request body
|
|
138
|
+
- **Newer flagd versions (v0.11.0+)** prefer the selector from the gRPC metadata header
|
|
139
|
+
- Both approaches are supported simultaneously for maximum compatibility
|
|
140
|
+
|
|
141
|
+
**Related Resources:**
|
|
142
|
+
- Upstream issue: [open-feature/flagd#1814](https://github.com/open-feature/flagd/issues/1814)
|
|
143
|
+
- Selector normalization affects in-process evaluations that filter flag configurations by source
|
|
144
|
+
|
|
106
145
|
<!--
|
|
107
146
|
### Unix socket support
|
|
108
147
|
Unix socket communication with flagd is facilitated by usaging of the linux-native `epoll` library on `linux-x86_64`
|
|
@@ -1,83 +1,54 @@
|
|
|
1
|
-
# pyproject.toml
|
|
2
1
|
[build-system]
|
|
2
|
+
# `uv_build` backend doesn't support build hooks yet https://github.com/astral-sh/uv/issues/14561
|
|
3
3
|
requires = ["hatchling"]
|
|
4
4
|
build-backend = "hatchling.build"
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "openfeature-provider-flagd"
|
|
8
|
-
version = "0.2.
|
|
8
|
+
version = "0.2.7"
|
|
9
9
|
description = "OpenFeature provider for the flagd flag evaluation engine"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
authors = [{ name = "OpenFeature", email = "openfeature-core@groups.io" }]
|
|
12
|
-
license =
|
|
12
|
+
license = "Apache-2.0"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
13
14
|
classifiers = [
|
|
14
|
-
"License :: OSI Approved :: Apache Software License",
|
|
15
15
|
"Programming Language :: Python",
|
|
16
16
|
"Programming Language :: Python :: 3",
|
|
17
17
|
]
|
|
18
18
|
keywords = []
|
|
19
19
|
dependencies = [
|
|
20
|
-
"openfeature-sdk>=0.
|
|
21
|
-
"grpcio>=1.
|
|
22
|
-
"protobuf>=
|
|
23
|
-
"mmh3>=
|
|
20
|
+
"openfeature-sdk>=0.8.2",
|
|
21
|
+
"grpcio>=1.76.0",
|
|
22
|
+
"protobuf>=6.30.0,<7.0.0",
|
|
23
|
+
"mmh3>=5.0.0,<6.0.0",
|
|
24
24
|
"panzi-json-logic>=1.0.1",
|
|
25
25
|
"semver>=3,<4",
|
|
26
26
|
"pyyaml>=6.0.1",
|
|
27
|
-
"cachebox"
|
|
27
|
+
"cachebox>=5.1.0,<6.0.0",
|
|
28
28
|
]
|
|
29
29
|
requires-python = ">=3.9"
|
|
30
30
|
|
|
31
31
|
[project.urls]
|
|
32
32
|
Homepage = "https://github.com/open-feature/python-sdk-contrib"
|
|
33
33
|
|
|
34
|
-
[
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"pytest
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"grpcio
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"asserts>=0.13.0,<0.14.0",
|
|
37
|
+
"coverage[toml]>=7.10.0,<8.0.0",
|
|
38
|
+
"grpcio-health-checking>=1.74.0,<2.0.0",
|
|
39
|
+
"mypy>=1.18.0,<2.0.0",
|
|
40
|
+
"pytest>=8.4.0,<9.0.0",
|
|
41
|
+
"pytest-bdd>=8.1.0,<9.0.0",
|
|
42
|
+
"testcontainers>=4.12.0,<5.0.0",
|
|
43
|
+
"types-grpcio>=1.0.0,<2.0.0",
|
|
44
|
+
"types-protobuf>=6.30.0,<7.0.0",
|
|
45
|
+
"types-pyyaml>=6.0.0,<7.0.0",
|
|
44
46
|
]
|
|
45
|
-
pre-install-commands = [
|
|
46
|
-
"hatch build",
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
[tool.hatch.envs.hatch-test.scripts]
|
|
50
|
-
run = "pytest {args:tests}"
|
|
51
|
-
run-cov = "coverage run -m pytest {args:tests}"
|
|
52
|
-
cov-combine = "coverage combine"
|
|
53
|
-
cov-report = [
|
|
54
|
-
"coverage xml",
|
|
55
|
-
"coverage html",
|
|
56
|
-
"coverage report",
|
|
57
|
-
]
|
|
58
|
-
cov = [
|
|
59
|
-
"test-cov",
|
|
60
|
-
"cov-report",
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
[tool.hatch.envs.mypy]
|
|
65
|
-
dependencies = [
|
|
66
|
-
"mypy[faster-cache]>=1.13.0",
|
|
67
|
-
"types-protobuf",
|
|
68
|
-
"types-pyyaml",
|
|
69
|
-
]
|
|
70
|
-
pre-install-commands = [
|
|
71
|
-
"hatch build",
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
[tool.hatch.envs.mypy.scripts]
|
|
75
|
-
run = "mypy"
|
|
76
47
|
|
|
77
48
|
[tool.hatch.build.hooks.protobuf]
|
|
78
49
|
generate_pyi = false
|
|
79
50
|
dependencies = [
|
|
80
|
-
"protobuf
|
|
51
|
+
"protobuf~=6.0",
|
|
81
52
|
"hatch-protobuf",
|
|
82
53
|
"mypy-protobuf~=3.0",
|
|
83
54
|
]
|
|
@@ -117,7 +88,9 @@ files = "src"
|
|
|
117
88
|
python_version = "3.9" # should be identical to the minimum supported version
|
|
118
89
|
namespace_packages = true
|
|
119
90
|
explicit_package_bases = true
|
|
120
|
-
local_partial_types = true
|
|
91
|
+
local_partial_types = true # will become the new default from version 2
|
|
92
|
+
allow_redefinition_new = true # will become the new default from version 2
|
|
93
|
+
fixed_format_cache = true # new caching mechanism
|
|
121
94
|
pretty = true
|
|
122
95
|
|
|
123
96
|
strict = true
|
|
@@ -125,7 +98,6 @@ disallow_any_generics = false
|
|
|
125
98
|
|
|
126
99
|
[[tool.mypy.overrides]]
|
|
127
100
|
module = [
|
|
128
|
-
"grpc.*",
|
|
129
101
|
"json_logic.*",
|
|
130
102
|
]
|
|
131
103
|
ignore_missing_imports = true
|
|
@@ -135,3 +107,13 @@ module = [
|
|
|
135
107
|
"openfeature.schemas.*"
|
|
136
108
|
]
|
|
137
109
|
warn_unused_ignores = false
|
|
110
|
+
|
|
111
|
+
[project.scripts]
|
|
112
|
+
# workaround while UV doesn't support scripts directly in the pyproject.toml
|
|
113
|
+
# see: https://github.com/astral-sh/uv/issues/5903
|
|
114
|
+
cov-report = "scripts.scripts:cov_report"
|
|
115
|
+
cov = "scripts.scripts:cov"
|
|
116
|
+
# don't name it `mypy` otherwise `uv` will override the actual binary
|
|
117
|
+
mypy-check = "scripts.scripts:mypy"
|
|
118
|
+
test = "scripts.scripts:test"
|
|
119
|
+
test-cov = "scripts.scripts:test_cov"
|
|
@@ -28,7 +28,7 @@ import grpc
|
|
|
28
28
|
|
|
29
29
|
from openfeature.evaluation_context import EvaluationContext
|
|
30
30
|
from openfeature.event import ProviderEventDetails
|
|
31
|
-
from openfeature.flag_evaluation import FlagResolutionDetails
|
|
31
|
+
from openfeature.flag_evaluation import FlagResolutionDetails, FlagValueType
|
|
32
32
|
from openfeature.hook import Hook
|
|
33
33
|
from openfeature.provider import AbstractProvider
|
|
34
34
|
from openfeature.provider.metadata import Metadata
|
|
@@ -75,6 +75,9 @@ class FlagdProvider(AbstractProvider):
|
|
|
75
75
|
:param deadline_ms: the maximum to wait before a request times out
|
|
76
76
|
:param timeout: the maximum time to wait before a request times out
|
|
77
77
|
:param retry_backoff_ms: the number of milliseconds to backoff
|
|
78
|
+
:param selector: filter flag configurations by source (in-process mode only)
|
|
79
|
+
Passed via both flagd-selector gRPC metadata header and request body
|
|
80
|
+
for backward compatibility with all flagd versions.
|
|
78
81
|
:param offline_flag_source_path: the path to the flag source file
|
|
79
82
|
:param stream_deadline_ms: the maximum time to wait before a request times out
|
|
80
83
|
:param keep_alive_time: the number of milliseconds to keep alive
|
|
@@ -199,9 +202,13 @@ class FlagdProvider(AbstractProvider):
|
|
|
199
202
|
def resolve_object_details(
|
|
200
203
|
self,
|
|
201
204
|
flag_key: str,
|
|
202
|
-
default_value: typing.Union[
|
|
205
|
+
default_value: typing.Union[
|
|
206
|
+
typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
|
207
|
+
],
|
|
203
208
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
204
|
-
) -> FlagResolutionDetails[
|
|
209
|
+
) -> FlagResolutionDetails[
|
|
210
|
+
typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
|
211
|
+
]:
|
|
205
212
|
return self.resolver.resolve_object_details(
|
|
206
213
|
flag_key, default_value, evaluation_context
|
|
207
214
|
)
|
|
@@ -21,7 +21,7 @@ from openfeature.exception import (
|
|
|
21
21
|
ProviderNotReadyError,
|
|
22
22
|
TypeMismatchError,
|
|
23
23
|
)
|
|
24
|
-
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
|
24
|
+
from openfeature.flag_evaluation import FlagResolutionDetails, FlagValueType, Reason
|
|
25
25
|
from openfeature.schemas.protobuf.flagd.evaluation.v1 import (
|
|
26
26
|
evaluation_pb2,
|
|
27
27
|
evaluation_pb2_grpc,
|
|
@@ -29,6 +29,7 @@ from openfeature.schemas.protobuf.flagd.evaluation.v1 import (
|
|
|
29
29
|
|
|
30
30
|
from ..config import CacheType, Config
|
|
31
31
|
from ..flag_type import FlagType
|
|
32
|
+
from .types import GrpcMultiCallableArgs
|
|
32
33
|
|
|
33
34
|
if typing.TYPE_CHECKING:
|
|
34
35
|
from google.protobuf.message import Message
|
|
@@ -71,8 +72,6 @@ class GrpcResolver:
|
|
|
71
72
|
self.thread: typing.Optional[threading.Thread] = None
|
|
72
73
|
self.timer: typing.Optional[threading.Timer] = None
|
|
73
74
|
|
|
74
|
-
self.start_time = time.time()
|
|
75
|
-
|
|
76
75
|
def _generate_channel(self, config: Config) -> grpc.Channel:
|
|
77
76
|
target = f"{config.host}:{config.port}"
|
|
78
77
|
# Create the channel with the service config
|
|
@@ -121,15 +120,16 @@ class GrpcResolver:
|
|
|
121
120
|
),
|
|
122
121
|
]
|
|
123
122
|
if config.tls:
|
|
124
|
-
|
|
125
|
-
"options": options,
|
|
126
|
-
"credentials": grpc.ssl_channel_credentials(),
|
|
127
|
-
}
|
|
123
|
+
credentials = grpc.ssl_channel_credentials()
|
|
128
124
|
if config.cert_path:
|
|
129
125
|
with open(config.cert_path, "rb") as f:
|
|
130
|
-
|
|
126
|
+
credentials = grpc.ssl_channel_credentials(f.read())
|
|
131
127
|
|
|
132
|
-
channel = grpc.secure_channel(
|
|
128
|
+
channel = grpc.secure_channel(
|
|
129
|
+
target,
|
|
130
|
+
credentials=credentials,
|
|
131
|
+
options=options,
|
|
132
|
+
)
|
|
133
133
|
|
|
134
134
|
else:
|
|
135
135
|
channel = grpc.insecure_channel(
|
|
@@ -161,8 +161,8 @@ class GrpcResolver:
|
|
|
161
161
|
)
|
|
162
162
|
self.monitor_thread.start()
|
|
163
163
|
## block until ready or deadline reached
|
|
164
|
-
timeout = self.deadline + time.
|
|
165
|
-
while not self.connected and time.
|
|
164
|
+
timeout = self.deadline + time.monotonic()
|
|
165
|
+
while not self.connected and time.monotonic() < timeout:
|
|
166
166
|
time.sleep(0.05)
|
|
167
167
|
logger.debug("Finished blocking gRPC state initialization")
|
|
168
168
|
|
|
@@ -199,7 +199,6 @@ class GrpcResolver:
|
|
|
199
199
|
message="gRPC sync disconnected, reconnecting",
|
|
200
200
|
)
|
|
201
201
|
)
|
|
202
|
-
self.start_time = time.time()
|
|
203
202
|
# adding a timer, so we can emit the error event after time
|
|
204
203
|
self.timer = threading.Timer(self.retry_grace_period, self.emit_error)
|
|
205
204
|
|
|
@@ -220,20 +219,16 @@ class GrpcResolver:
|
|
|
220
219
|
|
|
221
220
|
def listen(self) -> None:
|
|
222
221
|
logger.debug("gRPC starting listener thread")
|
|
223
|
-
call_args =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
else {}
|
|
227
|
-
)
|
|
222
|
+
call_args: GrpcMultiCallableArgs = {"wait_for_ready": True}
|
|
223
|
+
if self.streamline_deadline_seconds > 0:
|
|
224
|
+
call_args["timeout"] = self.streamline_deadline_seconds
|
|
228
225
|
request = evaluation_pb2.EventStreamRequest()
|
|
229
226
|
|
|
230
227
|
# defining a never ending loop to recreate the stream
|
|
231
228
|
while self.active:
|
|
232
229
|
try:
|
|
233
230
|
logger.debug("Setting up gRPC sync flags connection")
|
|
234
|
-
for message in self.stub.EventStream(
|
|
235
|
-
request, wait_for_ready=True, **call_args
|
|
236
|
-
):
|
|
231
|
+
for message in self.stub.EventStream(request, **call_args):
|
|
237
232
|
if message.type == "provider_ready":
|
|
238
233
|
self.emit_provider_ready(
|
|
239
234
|
ProviderEventDetails(
|
|
@@ -300,25 +295,81 @@ class GrpcResolver:
|
|
|
300
295
|
def resolve_object_details(
|
|
301
296
|
self,
|
|
302
297
|
key: str,
|
|
303
|
-
default_value: typing.Union[
|
|
298
|
+
default_value: typing.Union[
|
|
299
|
+
typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
|
300
|
+
],
|
|
304
301
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
305
|
-
) -> FlagResolutionDetails[
|
|
302
|
+
) -> FlagResolutionDetails[
|
|
303
|
+
typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
|
304
|
+
]:
|
|
306
305
|
return self._resolve(key, FlagType.OBJECT, default_value, evaluation_context)
|
|
307
306
|
|
|
307
|
+
@typing.overload
|
|
308
|
+
def _resolve(
|
|
309
|
+
self,
|
|
310
|
+
flag_key: str,
|
|
311
|
+
flag_type: FlagType,
|
|
312
|
+
default_value: bool,
|
|
313
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
314
|
+
) -> FlagResolutionDetails[bool]: ...
|
|
315
|
+
|
|
316
|
+
@typing.overload
|
|
317
|
+
def _resolve(
|
|
318
|
+
self,
|
|
319
|
+
flag_key: str,
|
|
320
|
+
flag_type: FlagType,
|
|
321
|
+
default_value: int,
|
|
322
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
323
|
+
) -> FlagResolutionDetails[int]: ...
|
|
324
|
+
|
|
325
|
+
@typing.overload
|
|
326
|
+
def _resolve(
|
|
327
|
+
self,
|
|
328
|
+
flag_key: str,
|
|
329
|
+
flag_type: FlagType,
|
|
330
|
+
default_value: float,
|
|
331
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
332
|
+
) -> FlagResolutionDetails[float]: ...
|
|
333
|
+
|
|
334
|
+
@typing.overload
|
|
335
|
+
def _resolve(
|
|
336
|
+
self,
|
|
337
|
+
flag_key: str,
|
|
338
|
+
flag_type: FlagType,
|
|
339
|
+
default_value: str,
|
|
340
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
341
|
+
) -> FlagResolutionDetails[str]: ...
|
|
342
|
+
|
|
343
|
+
@typing.overload
|
|
344
|
+
def _resolve(
|
|
345
|
+
self,
|
|
346
|
+
flag_key: str,
|
|
347
|
+
flag_type: FlagType,
|
|
348
|
+
default_value: typing.Union[
|
|
349
|
+
typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
|
|
350
|
+
],
|
|
351
|
+
evaluation_context: typing.Optional[EvaluationContext],
|
|
352
|
+
) -> FlagResolutionDetails[
|
|
353
|
+
typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
|
|
354
|
+
]: ...
|
|
355
|
+
|
|
308
356
|
def _resolve( # noqa: PLR0915 C901
|
|
309
357
|
self,
|
|
310
358
|
flag_key: str,
|
|
311
359
|
flag_type: FlagType,
|
|
312
|
-
default_value:
|
|
360
|
+
default_value: FlagValueType,
|
|
313
361
|
evaluation_context: typing.Optional[EvaluationContext],
|
|
314
|
-
) -> FlagResolutionDetails[
|
|
362
|
+
) -> FlagResolutionDetails[FlagValueType]:
|
|
315
363
|
if self.cache is not None and flag_key in self.cache:
|
|
316
|
-
cached_flag: FlagResolutionDetails[
|
|
364
|
+
cached_flag: FlagResolutionDetails[FlagValueType] = self.cache[flag_key]
|
|
317
365
|
cached_flag.reason = Reason.CACHED
|
|
318
366
|
return cached_flag
|
|
319
367
|
|
|
320
368
|
context = self._convert_context(evaluation_context)
|
|
321
|
-
call_args = {
|
|
369
|
+
call_args: GrpcMultiCallableArgs = {
|
|
370
|
+
"timeout": self.deadline,
|
|
371
|
+
"wait_for_ready": True,
|
|
372
|
+
}
|
|
322
373
|
try:
|
|
323
374
|
request: Message
|
|
324
375
|
if flag_type == FlagType.BOOLEAN:
|
|
@@ -387,7 +438,11 @@ class GrpcResolver:
|
|
|
387
438
|
if evaluation_context:
|
|
388
439
|
try:
|
|
389
440
|
s["targetingKey"] = evaluation_context.targeting_key
|
|
390
|
-
s.update(
|
|
441
|
+
s.update(
|
|
442
|
+
typing.cast(
|
|
443
|
+
"typing.Mapping[str, typing.Any]", evaluation_context.attributes
|
|
444
|
+
)
|
|
445
|
+
)
|
|
391
446
|
except ValueError as exc:
|
|
392
447
|
message = (
|
|
393
448
|
"could not serialize evaluation context to google.protobuf.Struct"
|