openfeature-provider-flagd 0.2.0__tar.gz → 0.2.2__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.0 → openfeature_provider_flagd-0.2.2}/CHANGELOG.md +22 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/PKG-INFO +1 -1
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/pyproject.toml +2 -2
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/config.py +30 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/provider.py +29 -1
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +5 -2
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +2 -2
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +3 -2
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py +45 -6
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +2 -4
- openfeature_provider_flagd-0.2.2/src/openfeature/contrib/provider/flagd/sync_metadata_hook.py +14 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.py +1 -1
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.py +1 -1
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.py +1 -1
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.py +1 -1
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/file/conftest.py +1 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/event_steps.py +4 -2
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/provider_steps.py +3 -1
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/testfilter.py +3 -1
- openfeature_provider_flagd-0.2.2/tests/test_in_process.py +218 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/.gitignore +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/LICENSE +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/README.md +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/pytest.ini +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/flag_type.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/protocol.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/file/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/file/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/flagd_container.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/inprocess/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/inprocess/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/inprocess/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/parsers.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/paths.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/_utils.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/config_steps.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/context_steps.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/flag_step.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-broken-default.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-broken-state.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-broken-targeting.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-broken-variants.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-disabled.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-invalid.not-json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-no-state.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-wrong-structure.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-wrong-variant.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag.yaml +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-fractional-args-wrong-content.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-fractional-args.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-fractional-weights-strings.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-fractional-weights.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-semver-args.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-semver-op.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-stringcomp-args.json +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/test_config.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/test_errors.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/test_file_store.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/test_flagd.py +0 -0
- {openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/test_targeting.py +0 -0
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.2](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.1...openfeature-provider-flagd/v0.2.2) (2025-03-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🐛 Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **flagd:** handle falsy target values correctly ([#214](https://github.com/open-feature/python-sdk-contrib/issues/214)) ([fafd099](https://github.com/open-feature/python-sdk-contrib/commit/fafd099f07365a7d0032e8215477b51bfe90c01a))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### 🧹 Chore
|
|
12
|
+
|
|
13
|
+
* **deps:** update dependency grpcio-health-checking to v1.71.0 ([#209](https://github.com/open-feature/python-sdk-contrib/issues/209)) ([345e793](https://github.com/open-feature/python-sdk-contrib/commit/345e7934b9de3879d3aff45c8213ece1a98e3711))
|
|
14
|
+
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.11.0 ([#212](https://github.com/open-feature/python-sdk-contrib/issues/212)) ([1b9b5f1](https://github.com/open-feature/python-sdk-contrib/commit/1b9b5f128a7fe08ffbf84cbc7de2986f95dc01f5))
|
|
15
|
+
* **flagd:** Add sync metadata disabled ([#211](https://github.com/open-feature/python-sdk-contrib/issues/211)) ([2f85057](https://github.com/open-feature/python-sdk-contrib/commit/2f850574943cc92d55d198c8ccd91e80583a2ee6))
|
|
16
|
+
|
|
17
|
+
## [0.2.1](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.0...openfeature-provider-flagd/v0.2.1) (2025-03-10)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### ✨ New Features
|
|
21
|
+
|
|
22
|
+
* **flagd:** Add features to customize auth to Sync API servers ([#203](https://github.com/open-feature/python-sdk-contrib/issues/203)) ([5151e94](https://github.com/open-feature/python-sdk-contrib/commit/5151e941d229101bdbcc5b40f570f69d77ddda7b))
|
|
23
|
+
* **flagd:** Context value hydration ([#195](https://github.com/open-feature/python-sdk-contrib/issues/195)) ([4fa619b](https://github.com/open-feature/python-sdk-contrib/commit/4fa619b93faf1d1f62a9ead99f33baa21c04e267))
|
|
24
|
+
|
|
3
25
|
## [0.2.0](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.1.5...openfeature-provider-flagd/v0.2.0) (2025-02-18)
|
|
4
26
|
|
|
5
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openfeature-provider-flagd
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: OpenFeature provider for the flagd flag evaluation engine
|
|
5
5
|
Project-URL: Homepage, https://github.com/open-feature/python-sdk-contrib
|
|
6
6
|
Author-email: OpenFeature <openfeature-core@groups.io>
|
|
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "openfeature-provider-flagd"
|
|
8
|
-
version = "0.2.
|
|
8
|
+
version = "0.2.2"
|
|
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" }]
|
|
@@ -40,7 +40,7 @@ dependencies = [
|
|
|
40
40
|
"pytest-bdd",
|
|
41
41
|
"testcontainers",
|
|
42
42
|
"asserts",
|
|
43
|
-
"grpcio-health-checking==1.
|
|
43
|
+
"grpcio-health-checking==1.71.0",
|
|
44
44
|
]
|
|
45
45
|
pre-install-commands = [
|
|
46
46
|
"hatch build",
|
|
@@ -3,6 +3,8 @@ import os
|
|
|
3
3
|
import typing
|
|
4
4
|
from enum import Enum
|
|
5
5
|
|
|
6
|
+
import grpc
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class ResolverType(Enum):
|
|
8
10
|
RPC = "rpc"
|
|
@@ -45,9 +47,11 @@ ENV_VAR_RETRY_BACKOFF_MS = "FLAGD_RETRY_BACKOFF_MS"
|
|
|
45
47
|
ENV_VAR_RETRY_BACKOFF_MAX_MS = "FLAGD_RETRY_BACKOFF_MAX_MS"
|
|
46
48
|
ENV_VAR_RETRY_GRACE_PERIOD_SECONDS = "FLAGD_RETRY_GRACE_PERIOD"
|
|
47
49
|
ENV_VAR_SELECTOR = "FLAGD_SOURCE_SELECTOR"
|
|
50
|
+
ENV_VAR_PROVIDER_ID = "FLAGD_SOURCE_PROVIDER_ID"
|
|
48
51
|
ENV_VAR_STREAM_DEADLINE_MS = "FLAGD_STREAM_DEADLINE_MS"
|
|
49
52
|
ENV_VAR_TLS = "FLAGD_TLS"
|
|
50
53
|
ENV_VAR_TLS_CERT = "FLAGD_SERVER_CERT_PATH"
|
|
54
|
+
ENV_VAR_DEFAULT_AUTHORITY = "FLAGD_DEFAULT_AUTHORITY"
|
|
51
55
|
|
|
52
56
|
T = typing.TypeVar("T")
|
|
53
57
|
|
|
@@ -81,6 +85,7 @@ class Config:
|
|
|
81
85
|
port: typing.Optional[int] = None,
|
|
82
86
|
tls: typing.Optional[bool] = None,
|
|
83
87
|
selector: typing.Optional[str] = None,
|
|
88
|
+
provider_id: typing.Optional[str] = None,
|
|
84
89
|
resolver: typing.Optional[ResolverType] = None,
|
|
85
90
|
offline_flag_source_path: typing.Optional[str] = None,
|
|
86
91
|
offline_poll_interval_ms: typing.Optional[int] = None,
|
|
@@ -93,6 +98,9 @@ class Config:
|
|
|
93
98
|
cache: typing.Optional[CacheType] = None,
|
|
94
99
|
max_cache_size: typing.Optional[int] = None,
|
|
95
100
|
cert_path: typing.Optional[str] = None,
|
|
101
|
+
default_authority: typing.Optional[str] = None,
|
|
102
|
+
channel_credentials: typing.Optional[grpc.ChannelCredentials] = None,
|
|
103
|
+
sync_metadata_disabled: typing.Optional[bool] = None,
|
|
96
104
|
):
|
|
97
105
|
self.host = env_or_default(ENV_VAR_HOST, DEFAULT_HOST) if host is None else host
|
|
98
106
|
|
|
@@ -227,3 +235,25 @@ class Config:
|
|
|
227
235
|
self.selector = (
|
|
228
236
|
env_or_default(ENV_VAR_SELECTOR, None) if selector is None else selector
|
|
229
237
|
)
|
|
238
|
+
|
|
239
|
+
self.provider_id = (
|
|
240
|
+
env_or_default(ENV_VAR_PROVIDER_ID, None)
|
|
241
|
+
if provider_id is None
|
|
242
|
+
else provider_id
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
self.default_authority = (
|
|
246
|
+
env_or_default(ENV_VAR_DEFAULT_AUTHORITY, None)
|
|
247
|
+
if default_authority is None
|
|
248
|
+
else default_authority
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
self.channel_credentials = channel_credentials
|
|
252
|
+
|
|
253
|
+
# TODO: remove the metadata call entirely after https://github.com/open-feature/flagd/issues/1584
|
|
254
|
+
# This is a temporary stop-gap solutions to support servers that don't implement sync.GetMetadata
|
|
255
|
+
# (see: https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1#flagd.sync.v1.FlagSyncService.GetMetadata).
|
|
256
|
+
# Using this option disables call to sync.GetMetadata
|
|
257
|
+
# Disabling will prevent static context from flagd being used in evaluations.
|
|
258
|
+
# GetMetadata and this option will be removed.
|
|
259
|
+
self.sync_metadata_disabled = sync_metadata_disabled
|
|
@@ -24,13 +24,18 @@
|
|
|
24
24
|
import typing
|
|
25
25
|
import warnings
|
|
26
26
|
|
|
27
|
+
import grpc
|
|
28
|
+
|
|
27
29
|
from openfeature.evaluation_context import EvaluationContext
|
|
30
|
+
from openfeature.event import ProviderEventDetails
|
|
28
31
|
from openfeature.flag_evaluation import FlagResolutionDetails
|
|
32
|
+
from openfeature.hook import Hook
|
|
29
33
|
from openfeature.provider import AbstractProvider
|
|
30
34
|
from openfeature.provider.metadata import Metadata
|
|
31
35
|
|
|
32
36
|
from .config import CacheType, Config, ResolverType
|
|
33
37
|
from .resolvers import AbstractResolver, GrpcResolver, InProcessResolver
|
|
38
|
+
from .sync_metadata_hook import SyncMetadataHook
|
|
34
39
|
|
|
35
40
|
T = typing.TypeVar("T")
|
|
36
41
|
|
|
@@ -47,6 +52,7 @@ class FlagdProvider(AbstractProvider):
|
|
|
47
52
|
timeout: typing.Optional[int] = None,
|
|
48
53
|
retry_backoff_ms: typing.Optional[int] = None,
|
|
49
54
|
selector: typing.Optional[str] = None,
|
|
55
|
+
provider_id: typing.Optional[str] = None,
|
|
50
56
|
resolver_type: typing.Optional[ResolverType] = None,
|
|
51
57
|
offline_flag_source_path: typing.Optional[str] = None,
|
|
52
58
|
stream_deadline_ms: typing.Optional[int] = None,
|
|
@@ -56,6 +62,9 @@ class FlagdProvider(AbstractProvider):
|
|
|
56
62
|
retry_backoff_max_ms: typing.Optional[int] = None,
|
|
57
63
|
retry_grace_period: typing.Optional[int] = None,
|
|
58
64
|
cert_path: typing.Optional[str] = None,
|
|
65
|
+
default_authority: typing.Optional[str] = None,
|
|
66
|
+
channel_credentials: typing.Optional[grpc.ChannelCredentials] = None,
|
|
67
|
+
sync_metadata_disabled: typing.Optional[bool] = None,
|
|
59
68
|
):
|
|
60
69
|
"""
|
|
61
70
|
Create an instance of the FlagdProvider
|
|
@@ -88,6 +97,7 @@ class FlagdProvider(AbstractProvider):
|
|
|
88
97
|
retry_backoff_max_ms=retry_backoff_max_ms,
|
|
89
98
|
retry_grace_period=retry_grace_period,
|
|
90
99
|
selector=selector,
|
|
100
|
+
provider_id=provider_id,
|
|
91
101
|
resolver=resolver_type,
|
|
92
102
|
offline_flag_source_path=offline_flag_source_path,
|
|
93
103
|
stream_deadline_ms=stream_deadline_ms,
|
|
@@ -95,9 +105,20 @@ class FlagdProvider(AbstractProvider):
|
|
|
95
105
|
cache=cache,
|
|
96
106
|
max_cache_size=max_cache_size,
|
|
97
107
|
cert_path=cert_path,
|
|
108
|
+
default_authority=default_authority,
|
|
109
|
+
channel_credentials=channel_credentials,
|
|
110
|
+
sync_metadata_disabled=sync_metadata_disabled,
|
|
98
111
|
)
|
|
112
|
+
self.enriched_context: dict = {}
|
|
99
113
|
|
|
100
114
|
self.resolver = self.setup_resolver()
|
|
115
|
+
self.hooks: list[Hook] = [SyncMetadataHook(self.get_enriched_context)]
|
|
116
|
+
|
|
117
|
+
def get_enriched_context(self) -> EvaluationContext:
|
|
118
|
+
return EvaluationContext(attributes=self.enriched_context)
|
|
119
|
+
|
|
120
|
+
def get_provider_hooks(self) -> list[Hook]:
|
|
121
|
+
return self.hooks
|
|
101
122
|
|
|
102
123
|
def setup_resolver(self) -> AbstractResolver:
|
|
103
124
|
if self.config.resolver == ResolverType.RPC:
|
|
@@ -114,7 +135,7 @@ class FlagdProvider(AbstractProvider):
|
|
|
114
135
|
):
|
|
115
136
|
return InProcessResolver(
|
|
116
137
|
self.config,
|
|
117
|
-
self.
|
|
138
|
+
self.emit_provider_ready_with_context,
|
|
118
139
|
self.emit_provider_error,
|
|
119
140
|
self.emit_provider_stale,
|
|
120
141
|
self.emit_provider_configuration_changed,
|
|
@@ -184,3 +205,10 @@ class FlagdProvider(AbstractProvider):
|
|
|
184
205
|
return self.resolver.resolve_object_details(
|
|
185
206
|
key, default_value, evaluation_context
|
|
186
207
|
)
|
|
208
|
+
|
|
209
|
+
def emit_provider_ready_with_context(
|
|
210
|
+
self, details: ProviderEventDetails, context: dict
|
|
211
|
+
) -> None:
|
|
212
|
+
self.enriched_context = context
|
|
213
|
+
self.emit_provider_ready(details)
|
|
214
|
+
pass
|
|
@@ -137,7 +137,10 @@ class GrpcResolver:
|
|
|
137
137
|
|
|
138
138
|
def _state_change_callback(self, new_state: ChannelConnectivity) -> None:
|
|
139
139
|
logger.debug(f"gRPC state change: {new_state}")
|
|
140
|
-
if
|
|
140
|
+
if (
|
|
141
|
+
new_state == grpc.ChannelConnectivity.READY
|
|
142
|
+
or new_state == grpc.ChannelConnectivity.IDLE
|
|
143
|
+
):
|
|
141
144
|
if not self.thread or not self.thread.is_alive():
|
|
142
145
|
self.thread = threading.Thread(
|
|
143
146
|
target=self.listen,
|
|
@@ -276,7 +279,7 @@ class GrpcResolver:
|
|
|
276
279
|
return cached_flag
|
|
277
280
|
|
|
278
281
|
context = self._convert_context(evaluation_context)
|
|
279
|
-
call_args = {"timeout": self.deadline}
|
|
282
|
+
call_args = {"timeout": self.deadline, "wait_for_ready": True}
|
|
280
283
|
try:
|
|
281
284
|
request: Message
|
|
282
285
|
if flag_type == FlagType.BOOLEAN:
|
|
@@ -21,7 +21,7 @@ class InProcessResolver:
|
|
|
21
21
|
def __init__(
|
|
22
22
|
self,
|
|
23
23
|
config: Config,
|
|
24
|
-
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
|
|
24
|
+
emit_provider_ready: typing.Callable[[ProviderEventDetails, dict], None],
|
|
25
25
|
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
|
|
26
26
|
emit_provider_stale: typing.Callable[[ProviderEventDetails], None],
|
|
27
27
|
emit_provider_configuration_changed: typing.Callable[
|
|
@@ -121,7 +121,7 @@ class InProcessResolver:
|
|
|
121
121
|
)
|
|
122
122
|
|
|
123
123
|
variant, value = flag.get_variant(variant)
|
|
124
|
-
if
|
|
124
|
+
if value is None:
|
|
125
125
|
raise ParseError(f"Resolved variant {variant} not in variants config.")
|
|
126
126
|
|
|
127
127
|
return FlagResolutionDetails(
|
|
@@ -24,7 +24,7 @@ class FileWatcher(FlagStateConnector):
|
|
|
24
24
|
self,
|
|
25
25
|
config: Config,
|
|
26
26
|
flag_store: FlagStore,
|
|
27
|
-
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
|
|
27
|
+
emit_provider_ready: typing.Callable[[ProviderEventDetails, dict], None],
|
|
28
28
|
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
|
|
29
29
|
):
|
|
30
30
|
if config.offline_flag_source_path is None:
|
|
@@ -94,7 +94,8 @@ class FileWatcher(FlagStateConnector):
|
|
|
94
94
|
self.emit_provider_ready(
|
|
95
95
|
ProviderEventDetails(
|
|
96
96
|
message="Reloading file contents recovered from error state"
|
|
97
|
-
)
|
|
97
|
+
),
|
|
98
|
+
{},
|
|
98
99
|
)
|
|
99
100
|
self.should_emit_ready_on_success = False
|
|
100
101
|
|
|
@@ -5,6 +5,8 @@ import time
|
|
|
5
5
|
import typing
|
|
6
6
|
|
|
7
7
|
import grpc
|
|
8
|
+
from google.protobuf.json_format import MessageToDict
|
|
9
|
+
from google.protobuf.struct_pb2 import Struct
|
|
8
10
|
|
|
9
11
|
from openfeature.evaluation_context import EvaluationContext
|
|
10
12
|
from openfeature.event import ProviderEventDetails
|
|
@@ -26,7 +28,7 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
26
28
|
self,
|
|
27
29
|
config: Config,
|
|
28
30
|
flag_store: FlagStore,
|
|
29
|
-
emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
|
|
31
|
+
emit_provider_ready: typing.Callable[[ProviderEventDetails, dict], None],
|
|
30
32
|
emit_provider_error: typing.Callable[[ProviderEventDetails], None],
|
|
31
33
|
emit_provider_stale: typing.Callable[[ProviderEventDetails], None],
|
|
32
34
|
):
|
|
@@ -41,6 +43,7 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
41
43
|
self.streamline_deadline_seconds = config.stream_deadline_ms * 0.001
|
|
42
44
|
self.deadline = config.deadline_ms * 0.001
|
|
43
45
|
self.selector = config.selector
|
|
46
|
+
self.provider_id = config.provider_id
|
|
44
47
|
self.emit_provider_ready = emit_provider_ready
|
|
45
48
|
self.emit_provider_error = emit_provider_error
|
|
46
49
|
self.emit_provider_stale = emit_provider_stale
|
|
@@ -54,13 +57,23 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
54
57
|
def _generate_channel(self, config: Config) -> grpc.Channel:
|
|
55
58
|
target = f"{config.host}:{config.port}"
|
|
56
59
|
# Create the channel with the service config
|
|
57
|
-
options = [
|
|
60
|
+
options: list[tuple[str, typing.Any]] = [
|
|
58
61
|
("grpc.keepalive_time_ms", config.keep_alive_time),
|
|
59
62
|
("grpc.initial_reconnect_backoff_ms", config.retry_backoff_ms),
|
|
60
63
|
("grpc.max_reconnect_backoff_ms", config.retry_backoff_max_ms),
|
|
61
64
|
("grpc.min_reconnect_backoff_ms", config.stream_deadline_ms),
|
|
62
65
|
]
|
|
63
|
-
if config.
|
|
66
|
+
if config.default_authority is not None:
|
|
67
|
+
options.append(("grpc.default_authority", config.default_authority))
|
|
68
|
+
|
|
69
|
+
if config.channel_credentials is not None:
|
|
70
|
+
channel_args = {
|
|
71
|
+
"options": options,
|
|
72
|
+
"credentials": config.channel_credentials,
|
|
73
|
+
}
|
|
74
|
+
channel = grpc.secure_channel(target, **channel_args)
|
|
75
|
+
|
|
76
|
+
elif config.tls:
|
|
64
77
|
channel_args = {
|
|
65
78
|
"options": options,
|
|
66
79
|
"credentials": grpc.ssl_channel_credentials(),
|
|
@@ -106,7 +119,10 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
106
119
|
|
|
107
120
|
def _state_change_callback(self, new_state: grpc.ChannelConnectivity) -> None:
|
|
108
121
|
logger.debug(f"gRPC state change: {new_state}")
|
|
109
|
-
if
|
|
122
|
+
if (
|
|
123
|
+
new_state == grpc.ChannelConnectivity.READY
|
|
124
|
+
or new_state == grpc.ChannelConnectivity.IDLE
|
|
125
|
+
):
|
|
110
126
|
if not self.thread or not self.thread.is_alive():
|
|
111
127
|
self.thread = threading.Thread(
|
|
112
128
|
target=self.listen,
|
|
@@ -147,16 +163,38 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
147
163
|
self.active = False
|
|
148
164
|
self.channel.close()
|
|
149
165
|
|
|
166
|
+
def _create_request_args(self) -> dict:
|
|
167
|
+
request_args = {}
|
|
168
|
+
if self.selector is not None:
|
|
169
|
+
request_args["selector"] = self.selector
|
|
170
|
+
if self.provider_id is not None:
|
|
171
|
+
request_args["provider_id"] = self.provider_id
|
|
172
|
+
|
|
173
|
+
return request_args
|
|
174
|
+
|
|
150
175
|
def listen(self) -> None:
|
|
151
176
|
call_args = (
|
|
152
177
|
{"timeout": self.streamline_deadline_seconds}
|
|
153
178
|
if self.streamline_deadline_seconds > 0
|
|
154
179
|
else {}
|
|
155
180
|
)
|
|
156
|
-
request_args =
|
|
181
|
+
request_args = self._create_request_args()
|
|
157
182
|
|
|
158
183
|
while self.active:
|
|
159
184
|
try:
|
|
185
|
+
context_values_response: sync_pb2.GetMetadataResponse
|
|
186
|
+
if self.config.sync_metadata_disabled:
|
|
187
|
+
context_values_response = sync_pb2.GetMetadataResponse(
|
|
188
|
+
metadata=Struct()
|
|
189
|
+
)
|
|
190
|
+
else:
|
|
191
|
+
context_values_request = sync_pb2.GetMetadataRequest()
|
|
192
|
+
context_values_response = self.stub.GetMetadata(
|
|
193
|
+
context_values_request, wait_for_ready=True
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
context_values = MessageToDict(context_values_response)
|
|
197
|
+
|
|
160
198
|
request = sync_pb2.SyncFlagsRequest(**request_args)
|
|
161
199
|
|
|
162
200
|
logger.debug("Setting up gRPC sync flags connection")
|
|
@@ -173,7 +211,8 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
173
211
|
self.emit_provider_ready(
|
|
174
212
|
ProviderEventDetails(
|
|
175
213
|
message="gRPC sync connection established"
|
|
176
|
-
)
|
|
214
|
+
),
|
|
215
|
+
context_values["metadata"],
|
|
177
216
|
)
|
|
178
217
|
self.connected = True
|
|
179
218
|
|
|
@@ -72,10 +72,8 @@ class Flag:
|
|
|
72
72
|
data["default_variant"] = data["defaultVariant"]
|
|
73
73
|
del data["defaultVariant"]
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if "selector" in data:
|
|
78
|
-
del data["selector"]
|
|
75
|
+
data.pop("source", None)
|
|
76
|
+
data.pop("selector", None)
|
|
79
77
|
try:
|
|
80
78
|
flag = cls(key=key, **data)
|
|
81
79
|
return flag
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from openfeature.evaluation_context import EvaluationContext
|
|
4
|
+
from openfeature.hook import Hook, HookContext, HookHints
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SyncMetadataHook(Hook):
|
|
8
|
+
def __init__(self, context_supplier: typing.Callable[[], EvaluationContext]):
|
|
9
|
+
self.context_supplier = context_supplier
|
|
10
|
+
|
|
11
|
+
def before(
|
|
12
|
+
self, hook_context: HookContext, hints: HookHints
|
|
13
|
+
) -> typing.Optional[EvaluationContext]:
|
|
14
|
+
return self.context_supplier()
|
|
@@ -5,7 +5,7 @@ import warnings
|
|
|
5
5
|
|
|
6
6
|
from openfeature.schemas.protobuf.flagd.evaluation.v1 import evaluation_pb2 as openfeature_dot_schemas_dot_protobuf_dot_flagd_dot_evaluation_dot_v1_dot_evaluation__pb2
|
|
7
7
|
|
|
8
|
-
GRPC_GENERATED_VERSION = '1.
|
|
8
|
+
GRPC_GENERATED_VERSION = '1.71.0'
|
|
9
9
|
GRPC_VERSION = grpc.__version__
|
|
10
10
|
_version_not_supported = False
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@ import warnings
|
|
|
5
5
|
|
|
6
6
|
from openfeature.schemas.protobuf.flagd.sync.v1 import sync_pb2 as openfeature_dot_schemas_dot_protobuf_dot_flagd_dot_sync_dot_v1_dot_sync__pb2
|
|
7
7
|
|
|
8
|
-
GRPC_GENERATED_VERSION = '1.
|
|
8
|
+
GRPC_GENERATED_VERSION = '1.71.0'
|
|
9
9
|
GRPC_VERSION = grpc.__version__
|
|
10
10
|
_version_not_supported = False
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@ import warnings
|
|
|
5
5
|
|
|
6
6
|
from openfeature.schemas.protobuf.schema.v1 import schema_pb2 as openfeature_dot_schemas_dot_protobuf_dot_schema_dot_v1_dot_schema__pb2
|
|
7
7
|
|
|
8
|
-
GRPC_GENERATED_VERSION = '1.
|
|
8
|
+
GRPC_GENERATED_VERSION = '1.71.0'
|
|
9
9
|
GRPC_VERSION = grpc.__version__
|
|
10
10
|
_version_not_supported = False
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@ import warnings
|
|
|
5
5
|
|
|
6
6
|
from openfeature.schemas.protobuf.sync.v1 import sync_service_pb2 as openfeature_dot_schemas_dot_protobuf_dot_sync_dot_v1_dot_sync__service__pb2
|
|
7
7
|
|
|
8
|
-
GRPC_GENERATED_VERSION = '1.
|
|
8
|
+
GRPC_GENERATED_VERSION = '1.71.0'
|
|
9
9
|
GRPC_VERSION = grpc.__version__
|
|
10
10
|
_version_not_supported = False
|
|
11
11
|
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/event_steps.py
RENAMED
|
@@ -8,6 +8,8 @@ from pytest_bdd import given, parsers, then, when
|
|
|
8
8
|
from openfeature.client import OpenFeatureClient
|
|
9
9
|
from openfeature.event import ProviderEvent
|
|
10
10
|
|
|
11
|
+
logger = logging.getLogger("openfeature.contrib.tests")
|
|
12
|
+
|
|
11
13
|
events = {
|
|
12
14
|
"ready": ProviderEvent.PROVIDER_READY,
|
|
13
15
|
"error": ProviderEvent.PROVIDER_ERROR,
|
|
@@ -28,7 +30,7 @@ def event_handles() -> list:
|
|
|
28
30
|
)
|
|
29
31
|
def add_event_handler(client: OpenFeatureClient, event_type: str, event_handles: list):
|
|
30
32
|
def handler(event):
|
|
31
|
-
|
|
33
|
+
logger.warning((event_type, event))
|
|
32
34
|
event_handles.append(
|
|
33
35
|
{
|
|
34
36
|
"type": event_type,
|
|
@@ -38,7 +40,7 @@ def add_event_handler(client: OpenFeatureClient, event_type: str, event_handles:
|
|
|
38
40
|
|
|
39
41
|
client.add_handler(events[event_type], handler)
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
logger.warning(("handler added", event_type))
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
def assert_handlers(handles, event_type: str, max_wait: int = 2):
|
|
@@ -22,6 +22,8 @@ KEY_FLAGS = "flags"
|
|
|
22
22
|
|
|
23
23
|
MERGED_FILE = "merged_file"
|
|
24
24
|
|
|
25
|
+
logger = logging.getLogger("openfeature.contrib.tests")
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
class TestProviderType(Enum):
|
|
27
29
|
UNAVAILABLE = "unavailable"
|
|
@@ -133,7 +135,7 @@ def container(request):
|
|
|
133
135
|
try:
|
|
134
136
|
container.stop()
|
|
135
137
|
except: # noqa: E722 - we want to ensure all containers are stopped, even if we do have an exception here
|
|
136
|
-
|
|
138
|
+
logger.debug("container was not running anymore")
|
|
137
139
|
|
|
138
140
|
# Teardown code
|
|
139
141
|
request.addfinalizer(fin)
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/testfilter.py
RENAMED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
|
+
logger = logging.getLogger("openfeature.contrib.tests")
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class TestFilter:
|
|
6
8
|
def __init__(self, config, feature_list=None, resolver=None, base_path=None):
|
|
@@ -40,7 +42,7 @@ class TestFilter:
|
|
|
40
42
|
all_tags = self._get_item_tags(item)
|
|
41
43
|
|
|
42
44
|
# Debug: Print collected tags for each item
|
|
43
|
-
|
|
45
|
+
logger.debug(f"Item: {item.nodeid}, Tags: {all_tags}")
|
|
44
46
|
|
|
45
47
|
# Include-only logic: Skip items that do not match include_tags
|
|
46
48
|
if (
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from unittest.mock import Mock, create_autospec
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from openfeature.contrib.provider.flagd.config import Config
|
|
6
|
+
from openfeature.contrib.provider.flagd.resolvers.in_process import InProcessResolver
|
|
7
|
+
from openfeature.contrib.provider.flagd.resolvers.process.flags import Flag, FlagStore
|
|
8
|
+
from openfeature.evaluation_context import EvaluationContext
|
|
9
|
+
from openfeature.exception import FlagNotFoundError, ParseError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def targeting():
|
|
13
|
+
return {
|
|
14
|
+
"if": [
|
|
15
|
+
{"==": [{"var": "targetingKey"}, "target_variant"]},
|
|
16
|
+
"target_variant",
|
|
17
|
+
None,
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def context(targeting_key):
|
|
23
|
+
return EvaluationContext(targeting_key=targeting_key)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def config():
|
|
28
|
+
return create_autospec(Config)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def flag_store():
|
|
33
|
+
return create_autospec(FlagStore)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def flag():
|
|
38
|
+
return Flag(
|
|
39
|
+
key="flag",
|
|
40
|
+
state="ENABLED",
|
|
41
|
+
variants={"default_variant": False, "target_variant": True},
|
|
42
|
+
default_variant="default_variant",
|
|
43
|
+
targeting=targeting(),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def resolver(config):
|
|
49
|
+
config.offline_flag_source_path = "flag.json"
|
|
50
|
+
config.deadline_ms = 100
|
|
51
|
+
return InProcessResolver(
|
|
52
|
+
config=config,
|
|
53
|
+
emit_provider_ready=Mock(),
|
|
54
|
+
emit_provider_error=Mock(),
|
|
55
|
+
emit_provider_stale=Mock(),
|
|
56
|
+
emit_provider_configuration_changed=Mock(),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_resolve_boolean_details_flag_not_found(resolver):
|
|
61
|
+
resolver.flag_store.get_flag = Mock(return_value=None)
|
|
62
|
+
with pytest.raises(FlagNotFoundError):
|
|
63
|
+
resolver.resolve_boolean_details("nonexistent_flag", False)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_resolve_boolean_details_disabled_flag(flag, resolver):
|
|
67
|
+
flag.state = "DISABLED"
|
|
68
|
+
resolver.flag_store.get_flag = Mock(return_value=flag)
|
|
69
|
+
|
|
70
|
+
result = resolver.resolve_boolean_details("disabled_flag", False)
|
|
71
|
+
|
|
72
|
+
assert result.reason == "DISABLED"
|
|
73
|
+
assert result.variant is None
|
|
74
|
+
assert not result.value
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_resolve_boolean_details_invalid_variant(resolver, flag):
|
|
78
|
+
flag.targeting = {"var": ["targetingKey", "invalid_variant"]}
|
|
79
|
+
|
|
80
|
+
resolver.flag_store.get_flag = Mock(return_value=flag)
|
|
81
|
+
|
|
82
|
+
with pytest.raises(ParseError):
|
|
83
|
+
resolver.resolve_boolean_details("flag", False)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.parametrize(
|
|
87
|
+
"input_config, resolve_config, expected",
|
|
88
|
+
[
|
|
89
|
+
(
|
|
90
|
+
{
|
|
91
|
+
"variants": {"default_variant": False, "target_variant": True},
|
|
92
|
+
"targeting": None,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"context": None,
|
|
96
|
+
"method": "resolve_boolean_details",
|
|
97
|
+
"default_value": False,
|
|
98
|
+
},
|
|
99
|
+
{"reason": "STATIC", "variant": "default_variant", "value": False},
|
|
100
|
+
),
|
|
101
|
+
(
|
|
102
|
+
{
|
|
103
|
+
"variants": {"default_variant": False, "target_variant": True},
|
|
104
|
+
"targeting": targeting(),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"context": context("no_target_variant"),
|
|
108
|
+
"method": "resolve_boolean_details",
|
|
109
|
+
"default_value": False,
|
|
110
|
+
},
|
|
111
|
+
{"reason": "DEFAULT", "variant": "default_variant", "value": False},
|
|
112
|
+
),
|
|
113
|
+
(
|
|
114
|
+
{
|
|
115
|
+
"variants": {"default_variant": False, "target_variant": True},
|
|
116
|
+
"targeting": targeting(),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"context": context("target_variant"),
|
|
120
|
+
"method": "resolve_boolean_details",
|
|
121
|
+
"default_value": False,
|
|
122
|
+
},
|
|
123
|
+
{"reason": "TARGETING_MATCH", "variant": "target_variant", "value": True},
|
|
124
|
+
),
|
|
125
|
+
(
|
|
126
|
+
{
|
|
127
|
+
"variants": {"default_variant": "default", "target_variant": "target"},
|
|
128
|
+
"targeting": targeting(),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"context": context("target_variant"),
|
|
132
|
+
"method": "resolve_string_details",
|
|
133
|
+
"default_value": "placeholder",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"reason": "TARGETING_MATCH",
|
|
137
|
+
"variant": "target_variant",
|
|
138
|
+
"value": "target",
|
|
139
|
+
},
|
|
140
|
+
),
|
|
141
|
+
(
|
|
142
|
+
{
|
|
143
|
+
"variants": {"default_variant": 1.0, "target_variant": 2.0},
|
|
144
|
+
"targeting": targeting(),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"context": context("target_variant"),
|
|
148
|
+
"method": "resolve_float_details",
|
|
149
|
+
"default_value": 0.0,
|
|
150
|
+
},
|
|
151
|
+
{"reason": "TARGETING_MATCH", "variant": "target_variant", "value": 2.0},
|
|
152
|
+
),
|
|
153
|
+
(
|
|
154
|
+
{
|
|
155
|
+
"variants": {"default_variant": True, "target_variant": False},
|
|
156
|
+
"targeting": targeting(),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"context": context("target_variant"),
|
|
160
|
+
"method": "resolve_boolean_details",
|
|
161
|
+
"default_value": True,
|
|
162
|
+
},
|
|
163
|
+
{"reason": "TARGETING_MATCH", "variant": "target_variant", "value": False},
|
|
164
|
+
),
|
|
165
|
+
(
|
|
166
|
+
{
|
|
167
|
+
"variants": {"default_variant": 10, "target_variant": 0},
|
|
168
|
+
"targeting": targeting(),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"context": context("target_variant"),
|
|
172
|
+
"method": "resolve_integer_details",
|
|
173
|
+
"default_value": 1,
|
|
174
|
+
},
|
|
175
|
+
{"reason": "TARGETING_MATCH", "variant": "target_variant", "value": 0},
|
|
176
|
+
),
|
|
177
|
+
(
|
|
178
|
+
{
|
|
179
|
+
"variants": {"default_variant": {}, "target_variant": {}},
|
|
180
|
+
"targeting": targeting(),
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"context": context("target_variant"),
|
|
184
|
+
"method": "resolve_object_details",
|
|
185
|
+
"default_value": {},
|
|
186
|
+
},
|
|
187
|
+
{"reason": "TARGETING_MATCH", "variant": "target_variant", "value": {}},
|
|
188
|
+
),
|
|
189
|
+
],
|
|
190
|
+
ids=[
|
|
191
|
+
"static_flag",
|
|
192
|
+
"boolean_default_fallback",
|
|
193
|
+
"boolean_targeting_match",
|
|
194
|
+
"string_targeting_match",
|
|
195
|
+
"float_targeting_match",
|
|
196
|
+
"boolean_falsy_target",
|
|
197
|
+
"integer_falsy_target",
|
|
198
|
+
"object_falsy_target",
|
|
199
|
+
],
|
|
200
|
+
)
|
|
201
|
+
def test_resolver_details(
|
|
202
|
+
resolver,
|
|
203
|
+
flag,
|
|
204
|
+
input_config,
|
|
205
|
+
resolve_config,
|
|
206
|
+
expected,
|
|
207
|
+
):
|
|
208
|
+
flag.variants = input_config["variants"]
|
|
209
|
+
flag.targeting = input_config["targeting"]
|
|
210
|
+
resolver.flag_store.get_flag = Mock(return_value=flag)
|
|
211
|
+
|
|
212
|
+
result = getattr(resolver, resolve_config["method"])(
|
|
213
|
+
"flag", resolve_config["default_value"], resolve_config["context"]
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
assert result.reason == expected["reason"]
|
|
217
|
+
assert result.variant == expected["variant"]
|
|
218
|
+
assert result.value == expected["value"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/file/__init__.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/file/test_flaqd.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/flagd_container.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/__init__.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/conftest.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/test_flaqd.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/_utils.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/config_steps.py
RENAMED
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/flag_step.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag.json
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/test_file_store.py
RENAMED
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.0 → openfeature_provider_flagd-0.2.2}/tests/test_targeting.py
RENAMED
|
File without changes
|