openfeature-provider-flagd 0.2.1__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.
Files changed (85) hide show
  1. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/CHANGELOG.md +14 -0
  2. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/PKG-INFO +1 -1
  3. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/pyproject.toml +2 -2
  4. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/config.py +9 -0
  5. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/provider.py +2 -0
  6. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +1 -1
  7. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py +22 -9
  8. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +2 -4
  9. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.py +1 -1
  10. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.py +1 -1
  11. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.py +1 -1
  12. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.py +1 -1
  13. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/event_steps.py +4 -2
  14. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/provider_steps.py +3 -1
  15. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/testfilter.py +3 -1
  16. openfeature_provider_flagd-0.2.2/tests/test_in_process.py +218 -0
  17. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/.gitignore +0 -0
  18. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/LICENSE +0 -0
  19. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/README.md +0 -0
  20. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/pytest.ini +0 -0
  21. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/__init__.py +0 -0
  22. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/flag_type.py +0 -0
  23. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/__init__.py +0 -0
  24. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +0 -0
  25. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/__init__.py +0 -0
  26. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +0 -0
  27. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py +0 -0
  28. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py +0 -0
  29. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/resolvers/protocol.py +0 -0
  30. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/contrib/provider/flagd/sync_metadata_hook.py +0 -0
  31. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +0 -0
  32. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.pyi +0 -0
  33. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +0 -0
  34. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +0 -0
  35. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.pyi +0 -0
  36. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +0 -0
  37. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.py +0 -0
  38. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.pyi +0 -0
  39. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +0 -0
  40. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.py +0 -0
  41. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.pyi +0 -0
  42. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +0 -0
  43. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/__init__.py +0 -0
  44. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/conftest.py +0 -0
  45. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/__init__.py +0 -0
  46. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/conftest.py +0 -0
  47. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/file/__init__.py +0 -0
  48. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/file/conftest.py +0 -0
  49. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/file/test_flaqd.py +0 -0
  50. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/flagd_container.py +0 -0
  51. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/inprocess/__init__.py +0 -0
  52. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/inprocess/conftest.py +0 -0
  53. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/inprocess/test_flaqd.py +0 -0
  54. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/parsers.py +0 -0
  55. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/paths.py +0 -0
  56. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/__init__.py +0 -0
  57. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/conftest.py +0 -0
  58. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/rpc/test_flaqd.py +0 -0
  59. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/_utils.py +0 -0
  60. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/config_steps.py +0 -0
  61. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/context_steps.py +0 -0
  62. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/e2e/step/flag_step.py +0 -0
  63. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-broken-default.json +0 -0
  64. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-broken-state.json +0 -0
  65. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-broken-targeting.json +0 -0
  66. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-broken-variants.json +0 -0
  67. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-disabled.json +0 -0
  68. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-invalid.not-json +0 -0
  69. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-no-state.json +0 -0
  70. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-wrong-structure.json +0 -0
  71. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag-wrong-variant.json +0 -0
  72. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag.json +0 -0
  73. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/basic-flag.yaml +0 -0
  74. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-fractional-args-wrong-content.json +0 -0
  75. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-fractional-args.json +0 -0
  76. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-fractional-weights-strings.json +0 -0
  77. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-fractional-weights.json +0 -0
  78. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-semver-args.json +0 -0
  79. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-semver-op.json +0 -0
  80. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/flags/invalid-stringcomp-args.json +0 -0
  81. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/test_config.py +0 -0
  82. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/test_errors.py +0 -0
  83. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/test_file_store.py +0 -0
  84. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/test_flagd.py +0 -0
  85. {openfeature_provider_flagd-0.2.1 → openfeature_provider_flagd-0.2.2}/tests/test_targeting.py +0 -0
@@ -1,5 +1,19 @@
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
+
3
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)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openfeature-provider-flagd
3
- Version: 0.2.1
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.1"
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.70.0",
43
+ "grpcio-health-checking==1.71.0",
44
44
  ]
45
45
  pre-install-commands = [
46
46
  "hatch build",
@@ -100,6 +100,7 @@ class Config:
100
100
  cert_path: typing.Optional[str] = None,
101
101
  default_authority: typing.Optional[str] = None,
102
102
  channel_credentials: typing.Optional[grpc.ChannelCredentials] = None,
103
+ sync_metadata_disabled: typing.Optional[bool] = None,
103
104
  ):
104
105
  self.host = env_or_default(ENV_VAR_HOST, DEFAULT_HOST) if host is None else host
105
106
 
@@ -248,3 +249,11 @@ class Config:
248
249
  )
249
250
 
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
@@ -64,6 +64,7 @@ class FlagdProvider(AbstractProvider):
64
64
  cert_path: typing.Optional[str] = None,
65
65
  default_authority: typing.Optional[str] = None,
66
66
  channel_credentials: typing.Optional[grpc.ChannelCredentials] = None,
67
+ sync_metadata_disabled: typing.Optional[bool] = None,
67
68
  ):
68
69
  """
69
70
  Create an instance of the FlagdProvider
@@ -106,6 +107,7 @@ class FlagdProvider(AbstractProvider):
106
107
  cert_path=cert_path,
107
108
  default_authority=default_authority,
108
109
  channel_credentials=channel_credentials,
110
+ sync_metadata_disabled=sync_metadata_disabled,
109
111
  )
110
112
  self.enriched_context: dict = {}
111
113
 
@@ -121,7 +121,7 @@ class InProcessResolver:
121
121
  )
122
122
 
123
123
  variant, value = flag.get_variant(variant)
124
- if not value:
124
+ if value is None:
125
125
  raise ParseError(f"Resolved variant {variant} not in variants config.")
126
126
 
127
127
  return FlagResolutionDetails(
@@ -6,6 +6,7 @@ import typing
6
6
 
7
7
  import grpc
8
8
  from google.protobuf.json_format import MessageToDict
9
+ from google.protobuf.struct_pb2 import Struct
9
10
 
10
11
  from openfeature.evaluation_context import EvaluationContext
11
12
  from openfeature.event import ProviderEventDetails
@@ -162,24 +163,36 @@ class GrpcWatcher(FlagStateConnector):
162
163
  self.active = False
163
164
  self.channel.close()
164
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
+
165
175
  def listen(self) -> None:
166
176
  call_args = (
167
177
  {"timeout": self.streamline_deadline_seconds}
168
178
  if self.streamline_deadline_seconds > 0
169
179
  else {}
170
180
  )
171
- request_args = {}
172
- if self.selector is not None:
173
- request_args["selector"] = self.selector
174
- if self.provider_id is not None:
175
- request_args["provider_id"] = self.provider_id
181
+ request_args = self._create_request_args()
176
182
 
177
183
  while self.active:
178
184
  try:
179
- context_values_request = sync_pb2.GetMetadataRequest()
180
- context_values_response: sync_pb2.GetMetadataResponse = (
181
- self.stub.GetMetadata(context_values_request, wait_for_ready=True)
182
- )
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
+
183
196
  context_values = MessageToDict(context_values_response)
184
197
 
185
198
  request = sync_pb2.SyncFlagsRequest(**request_args)
@@ -72,10 +72,8 @@ class Flag:
72
72
  data["default_variant"] = data["defaultVariant"]
73
73
  del data["defaultVariant"]
74
74
 
75
- if "source" in data:
76
- del data["source"]
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
@@ -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.70.0'
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.70.0'
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.70.0'
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.70.0'
8
+ GRPC_GENERATED_VERSION = '1.71.0'
9
9
  GRPC_VERSION = grpc.__version__
10
10
  _version_not_supported = False
11
11
 
@@ -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
- logging.warning((event_type, event))
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
- logging.warning(("handler added", event_type))
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
- logging.debug("container was not running anymore")
138
+ logger.debug("container was not running anymore")
137
139
 
138
140
  # Teardown code
139
141
  request.addfinalizer(fin)
@@ -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
- logging.debug(f"Item: {item.nodeid}, Tags: {all_tags}")
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"]