openfeature-provider-flagd 0.2.4__tar.gz → 0.2.6__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.4 → openfeature_provider_flagd-0.2.6}/.gitignore +3 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/CHANGELOG.md +16 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/PKG-INFO +2 -2
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/pyproject.toml +4 -3
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +42 -17
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py +1 -1
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +11 -14
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py +4 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +3 -3
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.py +1 -1
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +3 -3
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.py +1 -1
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.py +3 -3
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.py +1 -1
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.py +3 -3
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.py +1 -1
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/flagd_container.py +2 -2
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/context_steps.py +12 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/flag_step.py +20 -1
- openfeature_provider_flagd-0.2.6/tests/flags/basic-flag-without-default.json +12 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_errors.py +32 -2
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_file_store.py +1 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_in_process.py +2 -2
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/LICENSE +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/README.md +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/pytest.ini +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/config.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/flag_type.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/provider.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/resolvers/protocol.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/contrib/provider/flagd/sync_metadata_hook.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/file/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/file/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/file/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/inprocess/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/inprocess/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/inprocess/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/parsers.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/paths.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/rpc/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/rpc/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/rpc/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/_utils.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/config_steps.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/event_steps.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/provider_steps.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/testfilter.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-broken-default.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-broken-state.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-broken-targeting.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-broken-variants.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-combined-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-disabled.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-invalid.not-json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-no-state.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-set-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-wrong-structure.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag-wrong-variant.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag.yaml +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-flag-metadata-list.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-flag-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-flag-set-metadata-list.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-flag-set-metadata.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-fractional-args-wrong-content.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-fractional-args.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-fractional-weights-strings.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-fractional-weights.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-semver-args.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-semver-op.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/invalid-stringcomp-args.json +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_config.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_flagd.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_metadata.py +0 -0
- {openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_targeting.py +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [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
|
+
|
|
5
|
+
|
|
6
|
+
### 🐛 Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **flagd:** Add upperbound to protobuf version to reduce potential conflict with other version ([5ad2896](https://github.com/open-feature/python-sdk-contrib/commit/5ad289683a0d9128a53d142d2804f9fffb9dd36f))
|
|
9
|
+
* **flagd:** adjust flagd to updated error codes ([#285](https://github.com/open-feature/python-sdk-contrib/issues/285)) ([64d755b](https://github.com/open-feature/python-sdk-contrib/commit/64d755b869a076216a91b2d409b1ba5d627ebe08))
|
|
10
|
+
* **flagd:** fix protobuf version for file generation to lowest 5.x version ([#280](https://github.com/open-feature/python-sdk-contrib/issues/280)) ([5ad2896](https://github.com/open-feature/python-sdk-contrib/commit/5ad289683a0d9128a53d142d2804f9fffb9dd36f))
|
|
11
|
+
|
|
12
|
+
## [0.2.5](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.4...openfeature-provider-flagd/v0.2.5) (2025-07-08)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **flagd:** reduce loglevel of expected logs ([#277](https://github.com/open-feature/python-sdk-contrib/issues/277)) ([cca865f](https://github.com/open-feature/python-sdk-contrib/commit/cca865f490cc699ecf038c934dfccacad9bda8d2))
|
|
18
|
+
|
|
3
19
|
## [0.2.4](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.3...openfeature-provider-flagd/v0.2.4) (2025-06-10)
|
|
4
20
|
|
|
5
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openfeature-provider-flagd
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
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>
|
|
@@ -215,7 +215,7 @@ Requires-Dist: grpcio>=1.68.1
|
|
|
215
215
|
Requires-Dist: mmh3>=4.1.0
|
|
216
216
|
Requires-Dist: openfeature-sdk>=0.6.0
|
|
217
217
|
Requires-Dist: panzi-json-logic>=1.0.1
|
|
218
|
-
Requires-Dist: protobuf>=
|
|
218
|
+
Requires-Dist: protobuf>=5.26.1
|
|
219
219
|
Requires-Dist: pyyaml>=6.0.1
|
|
220
220
|
Requires-Dist: semver<4,>=3
|
|
221
221
|
Description-Content-Type: text/markdown
|
|
@@ -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.6"
|
|
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" }]
|
|
@@ -19,7 +19,7 @@ keywords = []
|
|
|
19
19
|
dependencies = [
|
|
20
20
|
"openfeature-sdk>=0.6.0",
|
|
21
21
|
"grpcio>=1.68.1",
|
|
22
|
-
"protobuf>=
|
|
22
|
+
"protobuf>=5.26.1",
|
|
23
23
|
"mmh3>=4.1.0",
|
|
24
24
|
"panzi-json-logic>=1.0.1",
|
|
25
25
|
"semver>=3,<4",
|
|
@@ -40,7 +40,7 @@ dependencies = [
|
|
|
40
40
|
"pytest-bdd",
|
|
41
41
|
"testcontainers",
|
|
42
42
|
"asserts",
|
|
43
|
-
"grpcio-health-checking
|
|
43
|
+
"grpcio-health-checking",
|
|
44
44
|
]
|
|
45
45
|
pre-install-commands = [
|
|
46
46
|
"hatch build",
|
|
@@ -77,6 +77,7 @@ run = "mypy"
|
|
|
77
77
|
[tool.hatch.build.hooks.protobuf]
|
|
78
78
|
generate_pyi = false
|
|
79
79
|
dependencies = [
|
|
80
|
+
"protobuf==5.26.1",
|
|
80
81
|
"hatch-protobuf",
|
|
81
82
|
"mypy-protobuf~=3.0",
|
|
82
83
|
]
|
|
@@ -5,13 +5,13 @@ from openfeature.contrib.provider.flagd.resolvers.process.connector.file_watcher
|
|
|
5
5
|
)
|
|
6
6
|
from openfeature.evaluation_context import EvaluationContext
|
|
7
7
|
from openfeature.event import ProviderEventDetails
|
|
8
|
-
from openfeature.exception import FlagNotFoundError, ParseError
|
|
8
|
+
from openfeature.exception import ErrorCode, FlagNotFoundError, GeneralError, ParseError
|
|
9
9
|
from openfeature.flag_evaluation import FlagResolutionDetails, Reason
|
|
10
10
|
|
|
11
11
|
from ..config import Config
|
|
12
12
|
from .process.connector import FlagStateConnector
|
|
13
13
|
from .process.connector.grpc_watcher import GrpcWatcher
|
|
14
|
-
from .process.flags import FlagStore
|
|
14
|
+
from .process.flags import Flag, FlagStore
|
|
15
15
|
from .process.targeting import targeting
|
|
16
16
|
|
|
17
17
|
T = typing.TypeVar("T")
|
|
@@ -128,26 +128,30 @@ class InProcessResolver:
|
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
if not flag.targeting:
|
|
131
|
-
|
|
132
|
-
return FlagResolutionDetails(
|
|
133
|
-
value, variant=variant, flag_metadata=metadata, reason=Reason.STATIC
|
|
134
|
-
)
|
|
131
|
+
return _default_resolve(flag, metadata, Reason.STATIC)
|
|
135
132
|
|
|
136
|
-
|
|
133
|
+
try:
|
|
134
|
+
variant = targeting(flag.key, flag.targeting, evaluation_context)
|
|
135
|
+
if variant is None:
|
|
136
|
+
return _default_resolve(flag, metadata, Reason.DEFAULT)
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
variant,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
# convert to string to support shorthand (boolean in python is with capital T hence the special case)
|
|
139
|
+
if isinstance(variant, bool):
|
|
140
|
+
variant = str(variant).lower()
|
|
141
|
+
elif not isinstance(variant, str):
|
|
142
|
+
variant = str(variant)
|
|
143
|
+
|
|
144
|
+
if variant not in flag.variants:
|
|
145
|
+
raise GeneralError(
|
|
146
|
+
f"Resolved variant {variant} not in variants config."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
except ReferenceError:
|
|
150
|
+
raise ParseError(f"Invalid targeting {targeting}") from ReferenceError
|
|
147
151
|
|
|
148
152
|
variant, value = flag.get_variant(variant)
|
|
149
153
|
if value is None:
|
|
150
|
-
raise
|
|
154
|
+
raise GeneralError(f"Resolved variant {variant} not in variants config.")
|
|
151
155
|
|
|
152
156
|
return FlagResolutionDetails(
|
|
153
157
|
value,
|
|
@@ -155,3 +159,24 @@ class InProcessResolver:
|
|
|
155
159
|
reason=Reason.TARGETING_MATCH,
|
|
156
160
|
flag_metadata=metadata,
|
|
157
161
|
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _default_resolve(
|
|
165
|
+
flag: Flag,
|
|
166
|
+
metadata: typing.Mapping[str, typing.Union[float, int, str, bool]],
|
|
167
|
+
reason: Reason,
|
|
168
|
+
) -> FlagResolutionDetails:
|
|
169
|
+
variant, value = flag.default
|
|
170
|
+
if variant is None:
|
|
171
|
+
return FlagResolutionDetails(
|
|
172
|
+
value,
|
|
173
|
+
variant=variant,
|
|
174
|
+
reason=Reason.ERROR,
|
|
175
|
+
error_code=ErrorCode.FLAG_NOT_FOUND,
|
|
176
|
+
flag_metadata=metadata,
|
|
177
|
+
)
|
|
178
|
+
if variant not in flag.variants:
|
|
179
|
+
raise GeneralError(f"Resolved variant {variant} not in variants config.")
|
|
180
|
+
return FlagResolutionDetails(
|
|
181
|
+
value, variant=variant, flag_metadata=metadata, reason=reason
|
|
182
|
+
)
|
|
@@ -258,7 +258,7 @@ class GrpcWatcher(FlagStateConnector):
|
|
|
258
258
|
logger.debug("Terminating gRPC sync thread")
|
|
259
259
|
return
|
|
260
260
|
except grpc.RpcError as e: # noqa: PERF203
|
|
261
|
-
logger.
|
|
261
|
+
logger.debug(f"SyncFlags stream error, {e.code()=} {e.details()=}")
|
|
262
262
|
except json.JSONDecodeError:
|
|
263
263
|
logger.exception(
|
|
264
264
|
f"Could not parse JSON flag data from SyncFlags endpoint: {flag_str=}"
|
|
@@ -72,30 +72,22 @@ class Flag:
|
|
|
72
72
|
key: str
|
|
73
73
|
state: str
|
|
74
74
|
variants: typing.Mapping[str, typing.Any]
|
|
75
|
-
default_variant: typing.Union[bool, str]
|
|
75
|
+
default_variant: typing.Optional[typing.Union[bool, str]] = None
|
|
76
76
|
targeting: typing.Optional[dict] = None
|
|
77
77
|
metadata: typing.Optional[
|
|
78
78
|
typing.Mapping[str, typing.Union[float, int, str, bool]]
|
|
79
79
|
] = None
|
|
80
80
|
|
|
81
81
|
def __post_init__(self) -> None:
|
|
82
|
-
if not self.state or not
|
|
82
|
+
if not self.state or not (self.state == "ENABLED" or self.state == "DISABLED"):
|
|
83
83
|
raise ParseError("Incorrect 'state' value provided in flag config")
|
|
84
84
|
|
|
85
85
|
if not self.variants or not isinstance(self.variants, dict):
|
|
86
86
|
raise ParseError("Incorrect 'variants' value provided in flag config")
|
|
87
87
|
|
|
88
|
-
if
|
|
89
|
-
self.default_variant, (str, bool)
|
|
90
|
-
):
|
|
88
|
+
if self.default_variant and not isinstance(self.default_variant, (str, bool)):
|
|
91
89
|
raise ParseError("Incorrect 'defaultVariant' value provided in flag config")
|
|
92
90
|
|
|
93
|
-
if self.targeting and not isinstance(self.targeting, dict):
|
|
94
|
-
raise ParseError("Incorrect 'targeting' value provided in flag config")
|
|
95
|
-
|
|
96
|
-
if self.default_variant not in self.variants:
|
|
97
|
-
raise ParseError("Default variant does not match set of variants")
|
|
98
|
-
|
|
99
91
|
if self.metadata:
|
|
100
92
|
if not isinstance(self.metadata, dict):
|
|
101
93
|
raise ParseError("Flag metadata is not a valid json object")
|
|
@@ -106,6 +98,8 @@ class Flag:
|
|
|
106
98
|
def from_dict(cls, key: str, data: dict) -> "Flag":
|
|
107
99
|
if "defaultVariant" in data:
|
|
108
100
|
data["default_variant"] = data["defaultVariant"]
|
|
101
|
+
if data["default_variant"] == "":
|
|
102
|
+
data["default_variant"] = None
|
|
109
103
|
del data["defaultVariant"]
|
|
110
104
|
|
|
111
105
|
data.pop("source", None)
|
|
@@ -119,13 +113,16 @@ class Flag:
|
|
|
119
113
|
raise ParseError from err
|
|
120
114
|
|
|
121
115
|
@property
|
|
122
|
-
def default(self) -> tuple[str, typing.Any]:
|
|
116
|
+
def default(self) -> tuple[typing.Optional[str], typing.Any]:
|
|
123
117
|
return self.get_variant(self.default_variant)
|
|
124
118
|
|
|
125
119
|
def get_variant(
|
|
126
|
-
self, variant_key: typing.Union[str, bool]
|
|
127
|
-
) -> tuple[str, typing.Any]:
|
|
120
|
+
self, variant_key: typing.Union[str, bool, None]
|
|
121
|
+
) -> tuple[typing.Optional[str], typing.Any]:
|
|
128
122
|
if isinstance(variant_key, bool):
|
|
129
123
|
variant_key = str(variant_key).lower()
|
|
130
124
|
|
|
125
|
+
if not variant_key:
|
|
126
|
+
return None, None
|
|
127
|
+
|
|
131
128
|
return variant_key, self.variants.get(variant_key)
|
|
@@ -5,6 +5,7 @@ from json_logic import builtins, jsonLogic
|
|
|
5
5
|
from json_logic.types import JsonValue
|
|
6
6
|
|
|
7
7
|
from openfeature.evaluation_context import EvaluationContext
|
|
8
|
+
from openfeature.exception import ParseError
|
|
8
9
|
|
|
9
10
|
from .custom_ops import (
|
|
10
11
|
ends_with,
|
|
@@ -27,6 +28,9 @@ def targeting(
|
|
|
27
28
|
targeting: dict,
|
|
28
29
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
29
30
|
) -> JsonValue:
|
|
31
|
+
if not isinstance(targeting, dict):
|
|
32
|
+
raise ParseError(f"Invalid 'targeting' value in flag: {targeting}")
|
|
33
|
+
|
|
30
34
|
json_logic_context = evaluation_context.attributes if evaluation_context else {}
|
|
31
35
|
json_logic_context["$flagd"] = {"flagKey": key, "timestamp": int(time.time())}
|
|
32
36
|
json_logic_context["targetingKey"] = (
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
4
|
# source: openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto
|
|
5
|
-
# Protobuf Python Version:
|
|
5
|
+
# Protobuf Python Version: 5.29.0
|
|
6
6
|
"""Generated protocol buffer code."""
|
|
7
7
|
from google.protobuf import descriptor as _descriptor
|
|
8
8
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -11,8 +11,8 @@ from google.protobuf import symbol_database as _symbol_database
|
|
|
11
11
|
from google.protobuf.internal import builder as _builder
|
|
12
12
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
13
|
_runtime_version.Domain.PUBLIC,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
5,
|
|
15
|
+
29,
|
|
16
16
|
0,
|
|
17
17
|
'',
|
|
18
18
|
'openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto'
|
|
@@ -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.2'
|
|
9
9
|
GRPC_VERSION = grpc.__version__
|
|
10
10
|
_version_not_supported = False
|
|
11
11
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
4
|
# source: openfeature/schemas/protobuf/flagd/sync/v1/sync.proto
|
|
5
|
-
# Protobuf Python Version:
|
|
5
|
+
# Protobuf Python Version: 5.29.0
|
|
6
6
|
"""Generated protocol buffer code."""
|
|
7
7
|
from google.protobuf import descriptor as _descriptor
|
|
8
8
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -11,8 +11,8 @@ from google.protobuf import symbol_database as _symbol_database
|
|
|
11
11
|
from google.protobuf.internal import builder as _builder
|
|
12
12
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
13
|
_runtime_version.Domain.PUBLIC,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
5,
|
|
15
|
+
29,
|
|
16
16
|
0,
|
|
17
17
|
'',
|
|
18
18
|
'openfeature/schemas/protobuf/flagd/sync/v1/sync.proto'
|
|
@@ -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.2'
|
|
9
9
|
GRPC_VERSION = grpc.__version__
|
|
10
10
|
_version_not_supported = False
|
|
11
11
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
4
|
# source: openfeature/schemas/protobuf/schema/v1/schema.proto
|
|
5
|
-
# Protobuf Python Version:
|
|
5
|
+
# Protobuf Python Version: 5.29.0
|
|
6
6
|
"""Generated protocol buffer code."""
|
|
7
7
|
from google.protobuf import descriptor as _descriptor
|
|
8
8
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -11,8 +11,8 @@ from google.protobuf import symbol_database as _symbol_database
|
|
|
11
11
|
from google.protobuf.internal import builder as _builder
|
|
12
12
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
13
|
_runtime_version.Domain.PUBLIC,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
5,
|
|
15
|
+
29,
|
|
16
16
|
0,
|
|
17
17
|
'',
|
|
18
18
|
'openfeature/schemas/protobuf/schema/v1/schema.proto'
|
|
@@ -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.2'
|
|
9
9
|
GRPC_VERSION = grpc.__version__
|
|
10
10
|
_version_not_supported = False
|
|
11
11
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
4
|
# source: openfeature/schemas/protobuf/sync/v1/sync_service.proto
|
|
5
|
-
# Protobuf Python Version:
|
|
5
|
+
# Protobuf Python Version: 5.29.0
|
|
6
6
|
"""Generated protocol buffer code."""
|
|
7
7
|
from google.protobuf import descriptor as _descriptor
|
|
8
8
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -11,8 +11,8 @@ from google.protobuf import symbol_database as _symbol_database
|
|
|
11
11
|
from google.protobuf.internal import builder as _builder
|
|
12
12
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
13
|
_runtime_version.Domain.PUBLIC,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
5,
|
|
15
|
+
29,
|
|
16
16
|
0,
|
|
17
17
|
'',
|
|
18
18
|
'openfeature/schemas/protobuf/sync/v1/sync_service.proto'
|
|
@@ -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.2'
|
|
9
9
|
GRPC_VERSION = grpc.__version__
|
|
10
10
|
_version_not_supported = False
|
|
11
11
|
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/flagd_container.py
RENAMED
|
@@ -48,7 +48,7 @@ class FlagdContainer(DockerContainer):
|
|
|
48
48
|
return self
|
|
49
49
|
|
|
50
50
|
@wait_container_is_ready(ConnectionError)
|
|
51
|
-
def _checker(self, host: str, port:
|
|
51
|
+
def _checker(self, host: str, port: int) -> None:
|
|
52
52
|
# First we wait for Flagd to say it's listening
|
|
53
53
|
wait_for_logs(
|
|
54
54
|
self,
|
|
@@ -58,7 +58,7 @@ class FlagdContainer(DockerContainer):
|
|
|
58
58
|
|
|
59
59
|
time.sleep(1)
|
|
60
60
|
# Second we use the GRPC health check endpoint
|
|
61
|
-
with grpc.insecure_channel(host + ":" + port) as channel:
|
|
61
|
+
with grpc.insecure_channel(host + ":" + str(port)) as channel:
|
|
62
62
|
health_stub = health_pb2_grpc.HealthStub(channel)
|
|
63
63
|
|
|
64
64
|
def health_check_call(stub: health_pb2_grpc.HealthStub):
|
|
@@ -36,6 +36,18 @@ def update_context(
|
|
|
36
36
|
evaluation_context.attributes[key] = type_cast[type_info](value)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
@given(
|
|
40
|
+
parsers.cfparse(
|
|
41
|
+
'a context containing a key "{key}", with type "{type_info}" and with value ""'
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
def update_context_without_value(
|
|
45
|
+
evaluation_context: EvaluationContext, key: str, type_info: str
|
|
46
|
+
):
|
|
47
|
+
"""a context containing a key and value."""
|
|
48
|
+
update_context(evaluation_context, key, type_info, "")
|
|
49
|
+
|
|
50
|
+
|
|
39
51
|
@when(
|
|
40
52
|
parsers.cfparse(
|
|
41
53
|
'context contains keys {fields:s} with values "{svalue}", "{svalue2}", {ivalue:d}, "{bvalue:bool}"',
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/flag_step.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import requests
|
|
2
|
-
from asserts import assert_equal
|
|
2
|
+
from asserts import assert_equal, assert_is_none
|
|
3
3
|
from pytest_bdd import given, parsers, then, when
|
|
4
4
|
|
|
5
5
|
from openfeature.client import OpenFeatureClient
|
|
@@ -96,6 +96,25 @@ def resolve_details_reason(
|
|
|
96
96
|
assert_equal(details.reason, Reason(reason))
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
@then(
|
|
100
|
+
parsers.cfparse('the error-code should be "{error_code}"'),
|
|
101
|
+
)
|
|
102
|
+
def resolve_details_error_code(
|
|
103
|
+
details: FlagEvaluationDetails[JsonPrimitive],
|
|
104
|
+
error_code: str,
|
|
105
|
+
):
|
|
106
|
+
assert_equal(details.error_code, error_code)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@then(
|
|
110
|
+
parsers.cfparse('the error-code should be ""'),
|
|
111
|
+
)
|
|
112
|
+
def resolve_details_empty_error_code(
|
|
113
|
+
details: FlagEvaluationDetails[JsonPrimitive],
|
|
114
|
+
):
|
|
115
|
+
assert_is_none(details.error_code)
|
|
116
|
+
|
|
117
|
+
|
|
99
118
|
@then(parsers.cfparse("the resolved metadata should contain"))
|
|
100
119
|
def metadata_contains(details: FlagEvaluationDetails[JsonPrimitive], datatable):
|
|
101
120
|
assert_equal(len(details.flag_metadata), len(datatable) - 1) # skip table header
|
|
@@ -23,11 +23,9 @@ def create_client(provider: FlagdProvider):
|
|
|
23
23
|
"not-a-flag.json",
|
|
24
24
|
"basic-flag-wrong-structure.json",
|
|
25
25
|
"basic-flag-invalid.not-json",
|
|
26
|
-
"basic-flag-wrong-variant.json",
|
|
27
26
|
"basic-flag-broken-state.json",
|
|
28
27
|
"basic-flag-broken-variants.json",
|
|
29
28
|
"basic-flag-broken-default.json",
|
|
30
|
-
"basic-flag-broken-targeting.json",
|
|
31
29
|
],
|
|
32
30
|
)
|
|
33
31
|
def test_file_load_errors(file_name: str):
|
|
@@ -46,6 +44,38 @@ def test_file_load_errors(file_name: str):
|
|
|
46
44
|
assert res.error_code == ErrorCode.FLAG_NOT_FOUND
|
|
47
45
|
|
|
48
46
|
|
|
47
|
+
def test_non_existent_variant():
|
|
48
|
+
path = os.path.abspath(os.path.join(os.path.dirname(__file__), "./flags/"))
|
|
49
|
+
client = create_client(
|
|
50
|
+
FlagdProvider(
|
|
51
|
+
resolver_type=ResolverType.IN_PROCESS,
|
|
52
|
+
offline_flag_source_path=f"{path}/basic-flag-wrong-variant.json",
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
res = client.get_boolean_details("basic-flag", False)
|
|
57
|
+
|
|
58
|
+
assert res.value is False
|
|
59
|
+
assert res.reason == Reason.ERROR
|
|
60
|
+
assert res.error_code == ErrorCode.GENERAL
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_broken_targeting():
|
|
64
|
+
path = os.path.abspath(os.path.join(os.path.dirname(__file__), "./flags/"))
|
|
65
|
+
client = create_client(
|
|
66
|
+
FlagdProvider(
|
|
67
|
+
resolver_type=ResolverType.IN_PROCESS,
|
|
68
|
+
offline_flag_source_path=f"{path}/basic-flag-broken-targeting.json",
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
res = client.get_boolean_details("basic-flag", False)
|
|
73
|
+
|
|
74
|
+
assert res.value is False
|
|
75
|
+
assert res.reason == Reason.ERROR
|
|
76
|
+
assert res.error_code == ErrorCode.PARSE_ERROR
|
|
77
|
+
|
|
78
|
+
|
|
49
79
|
@pytest.mark.parametrize(
|
|
50
80
|
"file_name",
|
|
51
81
|
[
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_in_process.py
RENAMED
|
@@ -6,7 +6,7 @@ from openfeature.contrib.provider.flagd.config import Config
|
|
|
6
6
|
from openfeature.contrib.provider.flagd.resolvers.in_process import InProcessResolver
|
|
7
7
|
from openfeature.contrib.provider.flagd.resolvers.process.flags import Flag, FlagStore
|
|
8
8
|
from openfeature.evaluation_context import EvaluationContext
|
|
9
|
-
from openfeature.exception import FlagNotFoundError,
|
|
9
|
+
from openfeature.exception import FlagNotFoundError, GeneralError
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def targeting():
|
|
@@ -79,7 +79,7 @@ def test_resolve_boolean_details_invalid_variant(resolver, flag):
|
|
|
79
79
|
|
|
80
80
|
resolver.flag_store.get_flag = Mock(return_value=flag)
|
|
81
81
|
|
|
82
|
-
with pytest.raises(
|
|
82
|
+
with pytest.raises(GeneralError):
|
|
83
83
|
resolver.resolve_boolean_details("flag", False)
|
|
84
84
|
|
|
85
85
|
|
|
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.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/file/__init__.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/file/conftest.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/file/test_flaqd.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.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/rpc/__init__.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/rpc/conftest.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/rpc/test_flaqd.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/_utils.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/config_steps.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/step/event_steps.py
RENAMED
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/e2e/testfilter.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/flags/basic-flag.json
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_metadata.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.4 → openfeature_provider_flagd-0.2.6}/tests/test_targeting.py
RENAMED
|
File without changes
|