openfeature-provider-flagd 0.2.2__tar.gz → 0.2.3__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.2 → openfeature_provider_flagd-0.2.3}/CHANGELOG.md +20 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/PKG-INFO +1 -1
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/pyproject.toml +1 -1
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/provider.py +10 -10
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +29 -3
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +15 -4
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +39 -1
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/rpc/conftest.py +1 -1
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/event_steps.py +1 -1
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/flag_step.py +13 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/provider_steps.py +17 -6
- openfeature_provider_flagd-0.2.3/tests/flags/basic-flag-combined-metadata.json +29 -0
- openfeature_provider_flagd-0.2.3/tests/flags/basic-flag-metadata.json +19 -0
- openfeature_provider_flagd-0.2.3/tests/flags/basic-flag-set-metadata.json +19 -0
- openfeature_provider_flagd-0.2.3/tests/flags/invalid-flag-metadata-list.json +14 -0
- openfeature_provider_flagd-0.2.3/tests/flags/invalid-flag-metadata.json +24 -0
- openfeature_provider_flagd-0.2.3/tests/flags/invalid-flag-set-metadata-list.json +14 -0
- openfeature_provider_flagd-0.2.3/tests/flags/invalid-flag-set-metadata.json +21 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/test_errors.py +1 -1
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/test_file_store.py +33 -0
- openfeature_provider_flagd-0.2.3/tests/test_metadata.py +148 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/.gitignore +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/LICENSE +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/README.md +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/pytest.ini +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/config.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/flag_type.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/resolvers/protocol.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/contrib/provider/flagd/sync_metadata_hook.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.pyi +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/file/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/file/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/file/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/flagd_container.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/inprocess/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/inprocess/conftest.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/inprocess/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/parsers.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/paths.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/rpc/__init__.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/rpc/test_flaqd.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/_utils.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/config_steps.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/context_steps.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/testfilter.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-broken-default.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-broken-state.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-broken-targeting.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-broken-variants.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-disabled.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-invalid.not-json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-no-state.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-wrong-structure.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag-wrong-variant.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag.yaml +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/invalid-fractional-args-wrong-content.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/invalid-fractional-args.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/invalid-fractional-weights-strings.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/invalid-fractional-weights.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/invalid-semver-args.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/invalid-semver-op.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/invalid-stringcomp-args.json +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/test_config.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/test_flagd.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/test_in_process.py +0 -0
- {openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/test_targeting.py +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.3](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.2...openfeature-provider-flagd/v0.2.3) (2025-04-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🐛 Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **flagd:** fix parameter name inconsistency with SDK version 0.8.1 ([#232](https://github.com/open-feature/python-sdk-contrib/issues/232)) ([55ee420](https://github.com/open-feature/python-sdk-contrib/commit/55ee42087bd9a948a130b08671395138baa33621))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ✨ New Features
|
|
12
|
+
|
|
13
|
+
* add support for flagd flag metadata ([#215](https://github.com/open-feature/python-sdk-contrib/issues/215)) ([6dc72c0](https://github.com/open-feature/python-sdk-contrib/commit/6dc72c0e16b01cd40e6c103884a2d457e95871d1))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### 🧹 Chore
|
|
17
|
+
|
|
18
|
+
* **deps:** update dependency providers/openfeature-provider-flagd/openfeature/test-harness to v2.7.3 ([#226](https://github.com/open-feature/python-sdk-contrib/issues/226)) ([9a0971c](https://github.com/open-feature/python-sdk-contrib/commit/9a0971c1fd67998259903a3ea4b42772413fc259))
|
|
19
|
+
* **deps:** update providers/openfeature-provider-flagd/openfeature/spec digest to 130df3e ([#222](https://github.com/open-feature/python-sdk-contrib/issues/222)) ([fa7f429](https://github.com/open-feature/python-sdk-contrib/commit/fa7f4293e060a3d56b204db851c19a668076e7a7))
|
|
20
|
+
* **deps:** update providers/openfeature-provider-flagd/openfeature/spec digest to 27e4461 ([#223](https://github.com/open-feature/python-sdk-contrib/issues/223)) ([9bf2e42](https://github.com/open-feature/python-sdk-contrib/commit/9bf2e421e52922516afa2e5b8648b52a035a038d))
|
|
21
|
+
* **deps:** update providers/openfeature-provider-flagd/openfeature/spec digest to aad6193 ([#194](https://github.com/open-feature/python-sdk-contrib/issues/194)) ([277ad0e](https://github.com/open-feature/python-sdk-contrib/commit/277ad0e744764bbc4eb5c5ebb773287bc2607ac3))
|
|
22
|
+
|
|
3
23
|
## [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
24
|
|
|
5
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openfeature-provider-flagd
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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.3"
|
|
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" }]
|
|
@@ -158,52 +158,52 @@ class FlagdProvider(AbstractProvider):
|
|
|
158
158
|
|
|
159
159
|
def resolve_boolean_details(
|
|
160
160
|
self,
|
|
161
|
-
|
|
161
|
+
flag_key: str,
|
|
162
162
|
default_value: bool,
|
|
163
163
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
164
164
|
) -> FlagResolutionDetails[bool]:
|
|
165
165
|
return self.resolver.resolve_boolean_details(
|
|
166
|
-
|
|
166
|
+
flag_key, default_value, evaluation_context
|
|
167
167
|
)
|
|
168
168
|
|
|
169
169
|
def resolve_string_details(
|
|
170
170
|
self,
|
|
171
|
-
|
|
171
|
+
flag_key: str,
|
|
172
172
|
default_value: str,
|
|
173
173
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
174
174
|
) -> FlagResolutionDetails[str]:
|
|
175
175
|
return self.resolver.resolve_string_details(
|
|
176
|
-
|
|
176
|
+
flag_key, default_value, evaluation_context
|
|
177
177
|
)
|
|
178
178
|
|
|
179
179
|
def resolve_float_details(
|
|
180
180
|
self,
|
|
181
|
-
|
|
181
|
+
flag_key: str,
|
|
182
182
|
default_value: float,
|
|
183
183
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
184
184
|
) -> FlagResolutionDetails[float]:
|
|
185
185
|
return self.resolver.resolve_float_details(
|
|
186
|
-
|
|
186
|
+
flag_key, default_value, evaluation_context
|
|
187
187
|
)
|
|
188
188
|
|
|
189
189
|
def resolve_integer_details(
|
|
190
190
|
self,
|
|
191
|
-
|
|
191
|
+
flag_key: str,
|
|
192
192
|
default_value: int,
|
|
193
193
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
194
194
|
) -> FlagResolutionDetails[int]:
|
|
195
195
|
return self.resolver.resolve_integer_details(
|
|
196
|
-
|
|
196
|
+
flag_key, default_value, evaluation_context
|
|
197
197
|
)
|
|
198
198
|
|
|
199
199
|
def resolve_object_details(
|
|
200
200
|
self,
|
|
201
|
-
|
|
201
|
+
flag_key: str,
|
|
202
202
|
default_value: typing.Union[dict, list],
|
|
203
203
|
evaluation_context: typing.Optional[EvaluationContext] = None,
|
|
204
204
|
) -> FlagResolutionDetails[typing.Union[dict, list]]:
|
|
205
205
|
return self.resolver.resolve_object_details(
|
|
206
|
-
|
|
206
|
+
flag_key, default_value, evaluation_context
|
|
207
207
|
)
|
|
208
208
|
|
|
209
209
|
def emit_provider_ready_with_context(
|
|
@@ -17,6 +17,23 @@ from .process.targeting import targeting
|
|
|
17
17
|
T = typing.TypeVar("T")
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _merge_metadata(
|
|
21
|
+
flag_metadata: typing.Optional[
|
|
22
|
+
typing.Mapping[str, typing.Union[float, int, str, bool]]
|
|
23
|
+
],
|
|
24
|
+
flag_set_metadata: typing.Optional[
|
|
25
|
+
typing.Mapping[str, typing.Union[float, int, str, bool]]
|
|
26
|
+
],
|
|
27
|
+
) -> typing.Mapping[str, typing.Union[float, int, str, bool]]:
|
|
28
|
+
metadata = {} if flag_set_metadata is None else dict(flag_set_metadata)
|
|
29
|
+
|
|
30
|
+
if flag_metadata is not None:
|
|
31
|
+
for key, value in flag_metadata.items():
|
|
32
|
+
metadata[key] = value
|
|
33
|
+
|
|
34
|
+
return metadata
|
|
35
|
+
|
|
36
|
+
|
|
20
37
|
class InProcessResolver:
|
|
21
38
|
def __init__(
|
|
22
39
|
self,
|
|
@@ -103,18 +120,26 @@ class InProcessResolver:
|
|
|
103
120
|
if not flag:
|
|
104
121
|
raise FlagNotFoundError(f"Flag with key {key} not present in flag store.")
|
|
105
122
|
|
|
123
|
+
metadata = _merge_metadata(flag.metadata, self.flag_store.flag_set_metadata)
|
|
124
|
+
|
|
106
125
|
if flag.state == "DISABLED":
|
|
107
|
-
return FlagResolutionDetails(
|
|
126
|
+
return FlagResolutionDetails(
|
|
127
|
+
default_value, flag_metadata=metadata, reason=Reason.DISABLED
|
|
128
|
+
)
|
|
108
129
|
|
|
109
130
|
if not flag.targeting:
|
|
110
131
|
variant, value = flag.default
|
|
111
|
-
return FlagResolutionDetails(
|
|
132
|
+
return FlagResolutionDetails(
|
|
133
|
+
value, variant=variant, flag_metadata=metadata, reason=Reason.STATIC
|
|
134
|
+
)
|
|
112
135
|
|
|
113
136
|
variant = targeting(flag.key, flag.targeting, evaluation_context)
|
|
114
137
|
|
|
115
138
|
if variant is None:
|
|
116
139
|
variant, value = flag.default
|
|
117
|
-
return FlagResolutionDetails(
|
|
140
|
+
return FlagResolutionDetails(
|
|
141
|
+
value, variant=variant, flag_metadata=metadata, reason=Reason.DEFAULT
|
|
142
|
+
)
|
|
118
143
|
if not isinstance(variant, (str, bool)):
|
|
119
144
|
raise ParseError(
|
|
120
145
|
"Parsed JSONLogic targeting did not return a string or bool"
|
|
@@ -128,4 +153,5 @@ class InProcessResolver:
|
|
|
128
153
|
value,
|
|
129
154
|
variant=variant,
|
|
130
155
|
reason=Reason.TARGETING_MATCH,
|
|
156
|
+
flag_metadata=metadata,
|
|
131
157
|
)
|
|
@@ -14,7 +14,7 @@ from openfeature.contrib.provider.flagd.resolvers.process.connector import (
|
|
|
14
14
|
from openfeature.contrib.provider.flagd.resolvers.process.flags import FlagStore
|
|
15
15
|
from openfeature.evaluation_context import EvaluationContext
|
|
16
16
|
from openfeature.event import ProviderEventDetails
|
|
17
|
-
from openfeature.exception import ParseError, ProviderNotReadyError
|
|
17
|
+
from openfeature.exception import ErrorCode, ParseError, ProviderNotReadyError
|
|
18
18
|
|
|
19
19
|
logger = logging.getLogger("openfeature.contrib")
|
|
20
20
|
|
|
@@ -76,8 +76,15 @@ class FileWatcher(FlagStateConnector):
|
|
|
76
76
|
self.handle_error("Could not parse JSON flag data from file")
|
|
77
77
|
except yaml.error.YAMLError:
|
|
78
78
|
self.handle_error("Could not parse YAML flag data from file")
|
|
79
|
-
except ParseError:
|
|
80
|
-
self.handle_error(
|
|
79
|
+
except ParseError as e:
|
|
80
|
+
self.handle_error(
|
|
81
|
+
"Could not parse flag data using flagd syntax: "
|
|
82
|
+
+ (
|
|
83
|
+
"no error message provided"
|
|
84
|
+
if e is None or e.error_message is None
|
|
85
|
+
else e.error_message
|
|
86
|
+
)
|
|
87
|
+
)
|
|
81
88
|
except Exception:
|
|
82
89
|
self.handle_error("Could not read flags from file")
|
|
83
90
|
|
|
@@ -104,4 +111,8 @@ class FileWatcher(FlagStateConnector):
|
|
|
104
111
|
def handle_error(self, error_message: str) -> None:
|
|
105
112
|
logger.exception(error_message)
|
|
106
113
|
self.should_emit_ready_on_success = True
|
|
107
|
-
self.emit_provider_error(
|
|
114
|
+
self.emit_provider_error(
|
|
115
|
+
ProviderEventDetails(
|
|
116
|
+
message=error_message, error_code=ErrorCode.PARSE_ERROR
|
|
117
|
+
)
|
|
118
|
+
)
|
|
@@ -7,6 +7,21 @@ from openfeature.event import ProviderEventDetails
|
|
|
7
7
|
from openfeature.exception import ParseError
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def _validate_metadata(key: str, value: typing.Union[float, int, str, bool]) -> None:
|
|
11
|
+
if key is None:
|
|
12
|
+
raise ParseError("Metadata key must be set")
|
|
13
|
+
elif not isinstance(key, str):
|
|
14
|
+
raise ParseError(f"Metadata key {key} must be of type str, but is {type(key)}")
|
|
15
|
+
elif not key:
|
|
16
|
+
raise ParseError("key must not be empty")
|
|
17
|
+
if value is None:
|
|
18
|
+
raise ParseError(f"Metadata value for key {key} must be set")
|
|
19
|
+
elif not isinstance(value, (float, int, str, bool)):
|
|
20
|
+
raise ParseError(
|
|
21
|
+
f"Metadata value {value} for key {key} must be of type float, int, str or bool, but is {type(value)}"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
10
25
|
class FlagStore:
|
|
11
26
|
def __init__(
|
|
12
27
|
self,
|
|
@@ -16,12 +31,16 @@ class FlagStore:
|
|
|
16
31
|
):
|
|
17
32
|
self.emit_provider_configuration_changed = emit_provider_configuration_changed
|
|
18
33
|
self.flags: typing.Mapping[str, Flag] = {}
|
|
34
|
+
self.flag_set_metadata: typing.Mapping[
|
|
35
|
+
str, typing.Union[float, int, str, bool]
|
|
36
|
+
] = {}
|
|
19
37
|
|
|
20
38
|
def get_flag(self, key: str) -> typing.Optional["Flag"]:
|
|
21
39
|
return self.flags.get(key)
|
|
22
40
|
|
|
23
41
|
def update(self, flags_data: dict) -> None:
|
|
24
42
|
flags = flags_data.get("flags", {})
|
|
43
|
+
metadata = flags_data.get("metadata", {})
|
|
25
44
|
evaluators: typing.Optional[dict] = flags_data.get("$evaluators")
|
|
26
45
|
if evaluators:
|
|
27
46
|
transposed = json.dumps(flags)
|
|
@@ -33,10 +52,18 @@ class FlagStore:
|
|
|
33
52
|
|
|
34
53
|
if not isinstance(flags, dict):
|
|
35
54
|
raise ParseError("`flags` key of configuration must be a dictionary")
|
|
55
|
+
if not isinstance(metadata, dict):
|
|
56
|
+
raise ParseError("`metadata` key of configuration must be a dictionary")
|
|
57
|
+
for key, value in metadata.items():
|
|
58
|
+
_validate_metadata(key, value)
|
|
59
|
+
|
|
36
60
|
self.flags = {key: Flag.from_dict(key, data) for key, data in flags.items()}
|
|
61
|
+
self.flag_set_metadata = metadata
|
|
37
62
|
|
|
38
63
|
self.emit_provider_configuration_changed(
|
|
39
|
-
ProviderEventDetails(
|
|
64
|
+
ProviderEventDetails(
|
|
65
|
+
flags_changed=list(self.flags.keys()), metadata=metadata
|
|
66
|
+
)
|
|
40
67
|
)
|
|
41
68
|
|
|
42
69
|
|
|
@@ -47,6 +74,9 @@ class Flag:
|
|
|
47
74
|
variants: typing.Mapping[str, typing.Any]
|
|
48
75
|
default_variant: typing.Union[bool, str]
|
|
49
76
|
targeting: typing.Optional[dict] = None
|
|
77
|
+
metadata: typing.Optional[
|
|
78
|
+
typing.Mapping[str, typing.Union[float, int, str, bool]]
|
|
79
|
+
] = None
|
|
50
80
|
|
|
51
81
|
def __post_init__(self) -> None:
|
|
52
82
|
if not self.state or not isinstance(self.state, str):
|
|
@@ -66,6 +96,12 @@ class Flag:
|
|
|
66
96
|
if self.default_variant not in self.variants:
|
|
67
97
|
raise ParseError("Default variant does not match set of variants")
|
|
68
98
|
|
|
99
|
+
if self.metadata:
|
|
100
|
+
if not isinstance(self.metadata, dict):
|
|
101
|
+
raise ParseError("Flag metadata is not a valid json object")
|
|
102
|
+
for key, value in self.metadata.items():
|
|
103
|
+
_validate_metadata(key, value)
|
|
104
|
+
|
|
69
105
|
@classmethod
|
|
70
106
|
def from_dict(cls, key: str, data: dict) -> "Flag":
|
|
71
107
|
if "defaultVariant" in data:
|
|
@@ -77,6 +113,8 @@ class Flag:
|
|
|
77
113
|
try:
|
|
78
114
|
flag = cls(key=key, **data)
|
|
79
115
|
return flag
|
|
116
|
+
except ParseError as parseError:
|
|
117
|
+
raise parseError
|
|
80
118
|
except Exception as err:
|
|
81
119
|
raise ParseError from err
|
|
82
120
|
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/rpc/conftest.py
RENAMED
|
@@ -4,7 +4,7 @@ from openfeature.contrib.provider.flagd.config import ResolverType
|
|
|
4
4
|
from tests.e2e.testfilter import TestFilter
|
|
5
5
|
|
|
6
6
|
resolver = ResolverType.RPC
|
|
7
|
-
feature_list = ["~targetURI", "~unixsocket", "~sync"]
|
|
7
|
+
feature_list = ["~targetURI", "~unixsocket", "~sync", "~metadata"]
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def pytest_collection_modifyitems(config, items):
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/event_steps.py
RENAMED
|
@@ -44,7 +44,7 @@ def add_event_handler(client: OpenFeatureClient, event_type: str, event_handles:
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def assert_handlers(handles, event_type: str, max_wait: int = 2):
|
|
47
|
-
poll_interval =
|
|
47
|
+
poll_interval = 0.2
|
|
48
48
|
while max_wait > 0:
|
|
49
49
|
found = any(h["type"] == event_type for h in handles)
|
|
50
50
|
if not found:
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/flag_step.py
RENAMED
|
@@ -94,3 +94,16 @@ def resolve_details_reason(
|
|
|
94
94
|
reason: str,
|
|
95
95
|
):
|
|
96
96
|
assert_equal(details.reason, Reason(reason))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@then(parsers.cfparse("the resolved metadata should contain"))
|
|
100
|
+
def metadata_contains(details: FlagEvaluationDetails[JsonPrimitive], datatable):
|
|
101
|
+
assert_equal(len(details.flag_metadata), len(datatable) - 1) # skip table header
|
|
102
|
+
for i in range(1, len(datatable)):
|
|
103
|
+
key, metadata_type, expected = datatable[i]
|
|
104
|
+
assert_equal(details.flag_metadata[key], type_cast[metadata_type](expected))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@then("the resolved metadata is empty")
|
|
108
|
+
def empty_metadata(details: FlagEvaluationDetails[JsonPrimitive]):
|
|
109
|
+
assert_equal(len(details.flag_metadata), 0)
|
|
@@ -31,6 +31,7 @@ class TestProviderType(Enum):
|
|
|
31
31
|
UNSTABLE = "unstable"
|
|
32
32
|
SSL = "ssl"
|
|
33
33
|
SOCKET = "socket"
|
|
34
|
+
METADATA = "metadata"
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
@given("a provider is registered", target_fixture="client")
|
|
@@ -43,7 +44,7 @@ def setup_provider_old(
|
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
def get_default_options_for_provider(
|
|
46
|
-
provider_type: str, resolver_type: ResolverType, container
|
|
47
|
+
provider_type: str, resolver_type: ResolverType, container, option_values: dict
|
|
47
48
|
) -> tuple[dict, bool]:
|
|
48
49
|
launchpad = "default"
|
|
49
50
|
t = TestProviderType(provider_type)
|
|
@@ -68,11 +69,20 @@ def get_default_options_for_provider(
|
|
|
68
69
|
launchpad = "ssl"
|
|
69
70
|
elif t == TestProviderType.SOCKET:
|
|
70
71
|
return options, True
|
|
72
|
+
elif t == TestProviderType.METADATA:
|
|
73
|
+
launchpad = "metadata"
|
|
71
74
|
|
|
72
75
|
if resolver_type == ResolverType.FILE:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
if "selector" in option_values:
|
|
77
|
+
path = option_values["selector"]
|
|
78
|
+
path = path.replace("rawflags/", "")
|
|
79
|
+
options["offline_flag_source_path"] = os.path.join(
|
|
80
|
+
Path(__file__).parents[3], "openfeature", "test-harness", "flags", path
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
options["offline_flag_source_path"] = os.path.join(
|
|
84
|
+
container.flagDir.name, "allFlags.json"
|
|
85
|
+
)
|
|
76
86
|
|
|
77
87
|
requests.post(
|
|
78
88
|
f"{container.get_launchpad_url()}/start?config={launchpad}", timeout=1
|
|
@@ -91,7 +101,7 @@ def setup_provider(
|
|
|
91
101
|
option_values: dict,
|
|
92
102
|
) -> OpenFeatureClient:
|
|
93
103
|
default_options, wait = get_default_options_for_provider(
|
|
94
|
-
provider_type, resolver_type, container
|
|
104
|
+
provider_type, resolver_type, container, option_values
|
|
95
105
|
)
|
|
96
106
|
|
|
97
107
|
combined_options = {**default_options, **option_values}
|
|
@@ -120,7 +130,8 @@ def flagd_restart(
|
|
|
120
130
|
resolver_type: ResolverType,
|
|
121
131
|
):
|
|
122
132
|
requests.post(
|
|
123
|
-
f"{container.get_launchpad_url()}/restart?seconds={seconds}",
|
|
133
|
+
f"{container.get_launchpad_url()}/restart?seconds={seconds}",
|
|
134
|
+
timeout=float(seconds) + 2,
|
|
124
135
|
)
|
|
125
136
|
pass
|
|
126
137
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"flags": {
|
|
3
|
+
"basic-flag": {
|
|
4
|
+
"state": "ENABLED",
|
|
5
|
+
"variants": {
|
|
6
|
+
"true": true,
|
|
7
|
+
"false": false
|
|
8
|
+
},
|
|
9
|
+
"defaultVariant": "false",
|
|
10
|
+
"targeting": {},
|
|
11
|
+
"metadata": {
|
|
12
|
+
"string": "a",
|
|
13
|
+
"integer": 1,
|
|
14
|
+
"float": 1.2,
|
|
15
|
+
"bool": true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"metadata": {
|
|
20
|
+
"string": "b",
|
|
21
|
+
"integer": 2,
|
|
22
|
+
"float": 2.2,
|
|
23
|
+
"bool": false,
|
|
24
|
+
"flag-set-string": "c",
|
|
25
|
+
"flag-set-integer": 3,
|
|
26
|
+
"flag-set-float": 3.2,
|
|
27
|
+
"flag-set-bool": false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"flags": {
|
|
3
|
+
"basic-flag": {
|
|
4
|
+
"state": "ENABLED",
|
|
5
|
+
"variants": {
|
|
6
|
+
"true": true,
|
|
7
|
+
"false": false
|
|
8
|
+
},
|
|
9
|
+
"defaultVariant": "false",
|
|
10
|
+
"targeting": {},
|
|
11
|
+
"metadata": {
|
|
12
|
+
"string": "a",
|
|
13
|
+
"integer": 1,
|
|
14
|
+
"float": 1.2,
|
|
15
|
+
"bool": true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"flags": {
|
|
3
|
+
"basic-flag": {
|
|
4
|
+
"state": "ENABLED",
|
|
5
|
+
"variants": {
|
|
6
|
+
"true": true,
|
|
7
|
+
"false": false
|
|
8
|
+
},
|
|
9
|
+
"defaultVariant": "false",
|
|
10
|
+
"targeting": {}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"metadata": {
|
|
14
|
+
"string": "a",
|
|
15
|
+
"integer": 1,
|
|
16
|
+
"float": 1.2,
|
|
17
|
+
"bool": true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"flags": {
|
|
3
|
+
"basic-flag": {
|
|
4
|
+
"state": "ENABLED",
|
|
5
|
+
"variants": {
|
|
6
|
+
"true": true,
|
|
7
|
+
"false": false
|
|
8
|
+
},
|
|
9
|
+
"defaultVariant": "false",
|
|
10
|
+
"targeting": {},
|
|
11
|
+
"metadata": {
|
|
12
|
+
"string": {
|
|
13
|
+
"a": "a"
|
|
14
|
+
},
|
|
15
|
+
"integer": 1,
|
|
16
|
+
"float": 1.2,
|
|
17
|
+
"bool": true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"metadata": {
|
|
22
|
+
"bool": true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"flags": {
|
|
3
|
+
"basic-flag": {
|
|
4
|
+
"state": "ENABLED",
|
|
5
|
+
"variants": {
|
|
6
|
+
"true": true,
|
|
7
|
+
"false": false
|
|
8
|
+
},
|
|
9
|
+
"defaultVariant": "false",
|
|
10
|
+
"targeting": {}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"metadata": {
|
|
14
|
+
"string": {
|
|
15
|
+
"a": "a"
|
|
16
|
+
},
|
|
17
|
+
"integer": 1,
|
|
18
|
+
"float": 1.2,
|
|
19
|
+
"bool": true
|
|
20
|
+
}
|
|
21
|
+
}
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/test_file_store.py
RENAMED
|
@@ -44,3 +44,36 @@ def test_file_load(file_name: str):
|
|
|
44
44
|
|
|
45
45
|
assert flag is not None
|
|
46
46
|
assert isinstance(flag, Flag)
|
|
47
|
+
|
|
48
|
+
flag_set_metadata = flag_store.flag_set_metadata
|
|
49
|
+
|
|
50
|
+
assert flag_set_metadata is not None
|
|
51
|
+
assert isinstance(flag_set_metadata, dict)
|
|
52
|
+
assert len(flag_set_metadata) == 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_file_load_metadata():
|
|
56
|
+
emit_provider_configuration_changed = Mock()
|
|
57
|
+
emit_provider_ready = Mock()
|
|
58
|
+
emit_provider_error = Mock()
|
|
59
|
+
flag_store = FlagStore(emit_provider_configuration_changed)
|
|
60
|
+
path = os.path.abspath(os.path.join(os.path.dirname(__file__), "./flags/"))
|
|
61
|
+
file_watcher = FileWatcher(
|
|
62
|
+
Config(
|
|
63
|
+
offline_flag_source_path=f"{path}/basic-flag-set-metadata.json",
|
|
64
|
+
),
|
|
65
|
+
flag_store,
|
|
66
|
+
emit_provider_ready,
|
|
67
|
+
emit_provider_error,
|
|
68
|
+
)
|
|
69
|
+
file_watcher.initialize(None)
|
|
70
|
+
|
|
71
|
+
flag_set_metadata = flag_store.flag_set_metadata
|
|
72
|
+
|
|
73
|
+
assert flag_set_metadata is not None
|
|
74
|
+
assert isinstance(flag_set_metadata, dict)
|
|
75
|
+
assert len(flag_set_metadata) == 4
|
|
76
|
+
assert flag_set_metadata["string"] == "a"
|
|
77
|
+
assert flag_set_metadata["integer"] == 1
|
|
78
|
+
assert flag_set_metadata["float"] == 1.2
|
|
79
|
+
assert flag_set_metadata["bool"]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from time import sleep
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from openfeature import api
|
|
8
|
+
from openfeature.contrib.provider.flagd import FlagdProvider
|
|
9
|
+
from openfeature.contrib.provider.flagd.config import ResolverType
|
|
10
|
+
from openfeature.contrib.provider.flagd.resolvers.process.flags import (
|
|
11
|
+
_validate_metadata,
|
|
12
|
+
)
|
|
13
|
+
from openfeature.event import EventDetails, ProviderEvent
|
|
14
|
+
from openfeature.exception import ErrorCode, ParseError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_client(file_name):
|
|
18
|
+
path = os.path.abspath(os.path.join(os.path.dirname(__file__), "./flags/"))
|
|
19
|
+
provider = FlagdProvider(
|
|
20
|
+
resolver_type=ResolverType.FILE,
|
|
21
|
+
offline_flag_source_path=f"{path}/{file_name}",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
api.set_provider(provider)
|
|
25
|
+
return api.get_client()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_should_load_flag_set_metadata():
|
|
29
|
+
client = create_client("basic-flag-set-metadata.json")
|
|
30
|
+
res = client.get_boolean_details("basic-flag", False)
|
|
31
|
+
|
|
32
|
+
assert res.flag_metadata is not None
|
|
33
|
+
assert isinstance(res.flag_metadata, dict)
|
|
34
|
+
assert len(res.flag_metadata) == 4
|
|
35
|
+
assert res.flag_metadata["string"] == "a"
|
|
36
|
+
assert res.flag_metadata["integer"] == 1
|
|
37
|
+
assert res.flag_metadata["float"] == 1.2
|
|
38
|
+
assert res.flag_metadata["bool"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_should_load_flag_metadata():
|
|
42
|
+
client = create_client("basic-flag-metadata.json")
|
|
43
|
+
res = client.get_boolean_details("basic-flag", False)
|
|
44
|
+
|
|
45
|
+
assert res.flag_metadata is not None
|
|
46
|
+
assert isinstance(res.flag_metadata, dict)
|
|
47
|
+
assert len(res.flag_metadata) == 4
|
|
48
|
+
assert res.flag_metadata["string"] == "a"
|
|
49
|
+
assert res.flag_metadata["integer"] == 1
|
|
50
|
+
assert res.flag_metadata["float"] == 1.2
|
|
51
|
+
assert res.flag_metadata["bool"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_should_load_flag_combined_metadata():
|
|
55
|
+
client = create_client("basic-flag-combined-metadata.json")
|
|
56
|
+
res = client.get_boolean_details("basic-flag", False)
|
|
57
|
+
|
|
58
|
+
assert res.flag_metadata is not None
|
|
59
|
+
assert isinstance(res.flag_metadata, dict)
|
|
60
|
+
assert len(res.flag_metadata) == 8
|
|
61
|
+
assert res.flag_metadata["string"] == "a"
|
|
62
|
+
assert res.flag_metadata["integer"] == 1
|
|
63
|
+
assert res.flag_metadata["float"] == 1.2
|
|
64
|
+
assert res.flag_metadata["bool"]
|
|
65
|
+
assert res.flag_metadata["flag-set-string"] == "c"
|
|
66
|
+
assert res.flag_metadata["flag-set-integer"] == 3
|
|
67
|
+
assert res.flag_metadata["flag-set-float"] == 3.2
|
|
68
|
+
assert not res.flag_metadata["flag-set-bool"]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Channel:
|
|
72
|
+
parse_error_received = False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def create_error_handler():
|
|
76
|
+
channel = Channel()
|
|
77
|
+
|
|
78
|
+
def error_handler(details: EventDetails):
|
|
79
|
+
nonlocal channel
|
|
80
|
+
if details.error_code == ErrorCode.PARSE_ERROR:
|
|
81
|
+
channel.parse_error_received = True
|
|
82
|
+
|
|
83
|
+
return error_handler, channel
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.parametrize(
|
|
87
|
+
"file_name",
|
|
88
|
+
[
|
|
89
|
+
"invalid-flag-set-metadata.json",
|
|
90
|
+
"invalid-flag-set-metadata-list.json",
|
|
91
|
+
"invalid-flag-metadata.json",
|
|
92
|
+
"invalid-flag-metadata-list.json",
|
|
93
|
+
],
|
|
94
|
+
)
|
|
95
|
+
def test_invalid_flag_set_metadata(file_name):
|
|
96
|
+
error_handler, channel = create_error_handler()
|
|
97
|
+
|
|
98
|
+
client = create_client(file_name)
|
|
99
|
+
client.add_handler(ProviderEvent.PROVIDER_ERROR, error_handler)
|
|
100
|
+
|
|
101
|
+
# keep the test thread alive
|
|
102
|
+
max_timeout = 2
|
|
103
|
+
start = time.time()
|
|
104
|
+
while not channel.parse_error_received:
|
|
105
|
+
now = time.time()
|
|
106
|
+
if now - start > max_timeout:
|
|
107
|
+
raise AssertionError()
|
|
108
|
+
sleep(0.01)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_validate_metadata_with_none_key():
|
|
112
|
+
try:
|
|
113
|
+
_validate_metadata(None, "a")
|
|
114
|
+
except ParseError:
|
|
115
|
+
return
|
|
116
|
+
raise AssertionError()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_validate_metadata_with_empty_key():
|
|
120
|
+
try:
|
|
121
|
+
_validate_metadata("", "a")
|
|
122
|
+
except ParseError:
|
|
123
|
+
return
|
|
124
|
+
raise AssertionError()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_validate_metadata_with_non_string_key():
|
|
128
|
+
try:
|
|
129
|
+
_validate_metadata(1, "a")
|
|
130
|
+
except ParseError:
|
|
131
|
+
return
|
|
132
|
+
raise AssertionError()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_validate_metadata_with_non_string_value():
|
|
136
|
+
try:
|
|
137
|
+
_validate_metadata("a", [])
|
|
138
|
+
except ParseError:
|
|
139
|
+
return
|
|
140
|
+
raise AssertionError()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_validate_metadata_with_none_value():
|
|
144
|
+
try:
|
|
145
|
+
_validate_metadata("a", None)
|
|
146
|
+
except ParseError:
|
|
147
|
+
return
|
|
148
|
+
raise AssertionError()
|
|
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
|
|
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.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/file/__init__.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/file/conftest.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/file/test_flaqd.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/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.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/rpc/__init__.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/rpc/test_flaqd.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/_utils.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/e2e/step/config_steps.py
RENAMED
|
File without changes
|
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/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
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/flags/basic-flag.json
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/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.2 → openfeature_provider_flagd-0.2.3}/tests/test_in_process.py
RENAMED
|
File without changes
|
{openfeature_provider_flagd-0.2.2 → openfeature_provider_flagd-0.2.3}/tests/test_targeting.py
RENAMED
|
File without changes
|