openfeature-provider-flagd 0.1.3__tar.gz → 0.1.4__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 (25) hide show
  1. openfeature_provider_flagd-0.1.4/CHANGELOG.md +53 -0
  2. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/PKG-INFO +2 -2
  3. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/pyproject.toml +7 -1
  4. openfeature_provider_flagd-0.1.4/src/openfeature/contrib/provider/flagd/config.py +35 -0
  5. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/provider.py +52 -60
  6. openfeature_provider_flagd-0.1.4/tests/test_config.py +29 -0
  7. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/tests/test_flagd.py +8 -0
  8. openfeature_provider_flagd-0.1.3/CHANGELOG.md +0 -28
  9. openfeature_provider_flagd-0.1.3/src/openfeature/contrib/provider/flagd/defaults.py +0 -5
  10. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/.gitignore +0 -0
  11. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/LICENSE +0 -0
  12. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/README.md +0 -0
  13. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/scripts/gen_protos.sh +0 -0
  14. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/__init__.py +0 -0
  15. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/flag_type.py +0 -0
  16. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2.py +0 -0
  17. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2_grpc.py +0 -0
  18. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2.py +0 -0
  19. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2_grpc.py +0 -0
  20. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2.py +0 -0
  21. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2_grpc.py +0 -0
  22. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2.py +0 -0
  23. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2_grpc.py +0 -0
  24. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/tests/__init__.py +0 -0
  25. {openfeature_provider_flagd-0.1.3 → openfeature_provider_flagd-0.1.4}/tests/conftest.py +0 -0
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ ## [0.1.4](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.1.3...openfeature-provider-flagd/v0.1.4) (2024-03-26)
4
+
5
+
6
+ ### 🐛 Bug Fixes
7
+
8
+ * include targetingKey in flagd serialized evaluation context ([#58](https://github.com/open-feature/python-sdk-contrib/issues/58)) ([ddd79a4](https://github.com/open-feature/python-sdk-contrib/commit/ddd79a49b765aa0679a2c1938447c61b37b6d0fe))
9
+ * respect timeout setting in grpc method calls ([#60](https://github.com/open-feature/python-sdk-contrib/issues/60)) ([0149cf7](https://github.com/open-feature/python-sdk-contrib/commit/0149cf7ced8116f54a9b220549834a1970460bd9))
10
+ * return proper metadata object in FlagdProvider ([#59](https://github.com/open-feature/python-sdk-contrib/issues/59)) ([6508234](https://github.com/open-feature/python-sdk-contrib/commit/6508234486ba0b650e849cbee22505988233131a))
11
+
12
+
13
+ ### ✨ New Features
14
+
15
+ * implement environment-variable based config ([#62](https://github.com/open-feature/python-sdk-contrib/issues/62)) ([a8b78b2](https://github.com/open-feature/python-sdk-contrib/commit/a8b78b28fe44ca712b00db04ac1a23a9c9bc6d9b))
16
+ * replace schema with tls argument in FlagdProvider constructor ([#61](https://github.com/open-feature/python-sdk-contrib/issues/61)) ([7a7210f](https://github.com/open-feature/python-sdk-contrib/commit/7a7210f6f63a9cba886f4d512c01ebac39d910a9))
17
+
18
+
19
+ ### 🧹 Chore
20
+
21
+ * exclude generated protobuf files from coverage report ([#51](https://github.com/open-feature/python-sdk-contrib/issues/51)) ([660a0cb](https://github.com/open-feature/python-sdk-contrib/commit/660a0cbc9bb932ac0dd9cb09f1d75177b161601b))
22
+
23
+
24
+ ### 🔄 Refactoring
25
+
26
+ * add mypy and fix typing issues ([#72](https://github.com/open-feature/python-sdk-contrib/issues/72)) ([b405925](https://github.com/open-feature/python-sdk-contrib/commit/b4059255045cdb7054a35bc338207e23c42ce068))
27
+
28
+ ## [0.1.3](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.1.2...openfeature-provider-flagd/v0.1.3) (2024-02-23)
29
+
30
+
31
+ ### 🐛 Bug Fixes
32
+
33
+ * include proto file in build for openfeature-provider-flagd ([#45](https://github.com/open-feature/python-sdk-contrib/issues/45)) ([7783cc8](https://github.com/open-feature/python-sdk-contrib/commit/7783cc8e7fb8fe0f9b812938efcd1f4c07e3ff68))
34
+
35
+ ## [0.1.2](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd-v0.1.1...openfeature-provider-flagd/v0.1.2) (2024-02-22)
36
+
37
+
38
+ ### 🐛 Bug Fixes
39
+
40
+ * remove mention of local eval in readme ([41df80e](https://github.com/open-feature/python-sdk-contrib/commit/41df80e1b3044356e3b228a484f3a13c92068d91))
41
+ * remove setup from flagd tests ([#39](https://github.com/open-feature/python-sdk-contrib/issues/39)) ([85661ff](https://github.com/open-feature/python-sdk-contrib/commit/85661ff170b378d37b0a3d5d0a955dad3417f538))
42
+
43
+
44
+ ### 🧹 Chore
45
+
46
+ * **main:** release providers/flagd 0.1.1 ([#40](https://github.com/open-feature/python-sdk-contrib/issues/40)) ([d42ee1e](https://github.com/open-feature/python-sdk-contrib/commit/d42ee1e531249e0023456dbe46db2f4f0c52a5c5))
47
+
48
+ ## [0.1.1](https://github.com/open-feature/python-sdk-contrib/compare/providers/flagd-v0.1.0...providers/flagd/v0.1.1) (2024-02-22)
49
+
50
+
51
+ ### 🐛 Bug Fixes
52
+
53
+ * remove setup from flagd tests ([#39](https://github.com/open-feature/python-sdk-contrib/issues/39)) ([85661ff](https://github.com/open-feature/python-sdk-contrib/commit/85661ff170b378d37b0a3d5d0a955dad3417f538))
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: openfeature-provider-flagd
3
- Version: 0.1.3
3
+ Version: 0.1.4
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.1.3"
8
+ version = "0.1.4"
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" }]
@@ -56,3 +56,9 @@ exclude = [
56
56
 
57
57
  [tool.hatch.build.targets.wheel]
58
58
  packages = ["src/openfeature"]
59
+
60
+ [tool.coverage.run]
61
+ omit = [
62
+ # exclude generated files
63
+ "src/openfeature/contrib/provider/flagd/proto/*",
64
+ ]
@@ -0,0 +1,35 @@
1
+ import os
2
+ import typing
3
+
4
+ T = typing.TypeVar("T")
5
+
6
+
7
+ def str_to_bool(val: str) -> bool:
8
+ return val.lower() == "true"
9
+
10
+
11
+ def env_or_default(
12
+ env_var: str, default: T, cast: typing.Optional[typing.Callable[[str], T]] = None
13
+ ) -> typing.Union[str, T]:
14
+ val = os.environ.get(env_var)
15
+ if val is None:
16
+ return default
17
+ return val if cast is None else cast(val)
18
+
19
+
20
+ class Config:
21
+ def __init__(
22
+ self,
23
+ host: typing.Optional[str] = None,
24
+ port: typing.Optional[int] = None,
25
+ tls: typing.Optional[bool] = None,
26
+ timeout: typing.Optional[int] = None,
27
+ ):
28
+ self.host = env_or_default("FLAGD_HOST", "localhost") if host is None else host
29
+ self.port = (
30
+ env_or_default("FLAGD_PORT", 8013, cast=int) if port is None else port
31
+ )
32
+ self.tls = (
33
+ env_or_default("FLAGD_TLS", False, cast=str_to_bool) if tls is None else tls
34
+ )
35
+ self.timeout = 5 if timeout is None else timeout
@@ -22,7 +22,6 @@
22
22
  """
23
23
 
24
24
  import typing
25
- from numbers import Number
26
25
 
27
26
  import grpc
28
27
  from google.protobuf.struct_pb2 import Struct
@@ -35,137 +34,128 @@ from openfeature.exception import (
35
34
  ParseError,
36
35
  TypeMismatchError,
37
36
  )
38
- from openfeature.flag_evaluation import FlagEvaluationDetails
37
+ from openfeature.flag_evaluation import FlagResolutionDetails
38
+ from openfeature.provider.metadata import Metadata
39
39
  from openfeature.provider.provider import AbstractProvider
40
40
 
41
- from .defaults import Defaults
41
+ from .config import Config
42
42
  from .flag_type import FlagType
43
43
  from .proto.schema.v1 import schema_pb2, schema_pb2_grpc
44
44
 
45
+ T = typing.TypeVar("T")
46
+
45
47
 
46
48
  class FlagdProvider(AbstractProvider):
47
49
  """Flagd OpenFeature Provider"""
48
50
 
49
51
  def __init__(
50
52
  self,
51
- name: str = "flagd",
52
- schema: str = Defaults.SCHEMA,
53
- host: str = Defaults.HOST,
54
- port: int = Defaults.PORT,
55
- timeout: int = Defaults.TIMEOUT,
53
+ host: typing.Optional[str] = None,
54
+ port: typing.Optional[int] = None,
55
+ tls: typing.Optional[bool] = None,
56
+ timeout: typing.Optional[int] = None,
56
57
  ):
57
58
  """
58
59
  Create an instance of the FlagdProvider
59
60
 
60
- :param name: the name of the provider to be stored in metadata
61
- :param schema: the schema for the transport protocol, e.g. 'http', 'https'
62
61
  :param host: the host to make requests to
63
62
  :param port: the port the flagd service is available on
63
+ :param tls: enable/disable secure TLS connectivity
64
64
  :param timeout: the maximum to wait before a request times out
65
65
  """
66
- self.provider_name = name
67
- self.schema = schema
68
- self.host = host
69
- self.port = port
70
- self.timeout = timeout
71
-
72
- channel_factory = (
73
- grpc.insecure_channel if schema == "http" else grpc.secure_channel
66
+ self.config = Config(
67
+ host=host,
68
+ port=port,
69
+ tls=tls,
70
+ timeout=timeout,
74
71
  )
75
- self.channel = channel_factory(f"{self.host}:{self.port}")
72
+
73
+ channel_factory = grpc.secure_channel if tls else grpc.insecure_channel
74
+ self.channel = channel_factory(f"{self.config.host}:{self.config.port}")
76
75
  self.stub = schema_pb2_grpc.ServiceStub(self.channel)
77
76
 
78
- def shutdown(self):
77
+ def shutdown(self) -> None:
79
78
  self.channel.close()
80
79
 
81
- def get_metadata(self):
80
+ def get_metadata(self) -> Metadata:
82
81
  """Returns provider metadata"""
83
- return {
84
- "name": self.get_name(),
85
- "schema": self.schema,
86
- "host": self.host,
87
- "port": self.port,
88
- "timeout": self.timeout,
89
- }
90
-
91
- def get_name(self) -> str:
92
- """Returns provider name"""
93
- return self.provider_name
82
+ return Metadata(name="FlagdProvider")
94
83
 
95
84
  def resolve_boolean_details(
96
85
  self,
97
86
  key: str,
98
87
  default_value: bool,
99
- evaluation_context: EvaluationContext = None,
100
- ):
88
+ evaluation_context: typing.Optional[EvaluationContext] = None,
89
+ ) -> FlagResolutionDetails[bool]:
101
90
  return self._resolve(key, FlagType.BOOLEAN, default_value, evaluation_context)
102
91
 
103
92
  def resolve_string_details(
104
93
  self,
105
94
  key: str,
106
95
  default_value: str,
107
- evaluation_context: EvaluationContext = None,
108
- ):
96
+ evaluation_context: typing.Optional[EvaluationContext] = None,
97
+ ) -> FlagResolutionDetails[str]:
109
98
  return self._resolve(key, FlagType.STRING, default_value, evaluation_context)
110
99
 
111
100
  def resolve_float_details(
112
101
  self,
113
102
  key: str,
114
- default_value: Number,
115
- evaluation_context: EvaluationContext = None,
116
- ):
103
+ default_value: float,
104
+ evaluation_context: typing.Optional[EvaluationContext] = None,
105
+ ) -> FlagResolutionDetails[float]:
117
106
  return self._resolve(key, FlagType.FLOAT, default_value, evaluation_context)
118
107
 
119
108
  def resolve_integer_details(
120
109
  self,
121
110
  key: str,
122
- default_value: Number,
123
- evaluation_context: EvaluationContext = None,
124
- ):
111
+ default_value: int,
112
+ evaluation_context: typing.Optional[EvaluationContext] = None,
113
+ ) -> FlagResolutionDetails[int]:
125
114
  return self._resolve(key, FlagType.INTEGER, default_value, evaluation_context)
126
115
 
127
116
  def resolve_object_details(
128
117
  self,
129
118
  key: str,
130
119
  default_value: typing.Union[dict, list],
131
- evaluation_context: EvaluationContext = None,
132
- ):
120
+ evaluation_context: typing.Optional[EvaluationContext] = None,
121
+ ) -> FlagResolutionDetails[typing.Union[dict, list]]:
133
122
  return self._resolve(key, FlagType.OBJECT, default_value, evaluation_context)
134
123
 
135
124
  def _resolve(
136
125
  self,
137
126
  flag_key: str,
138
127
  flag_type: FlagType,
139
- default_value: typing.Any,
140
- evaluation_context: EvaluationContext,
141
- ):
128
+ default_value: T,
129
+ evaluation_context: typing.Optional[EvaluationContext],
130
+ ) -> FlagResolutionDetails[T]:
142
131
  context = self._convert_context(evaluation_context)
132
+ call_args = {"timeout": self.config.timeout}
143
133
  try:
144
134
  if flag_type == FlagType.BOOLEAN:
145
- request = schema_pb2.ResolveBooleanRequest(
135
+ request = schema_pb2.ResolveBooleanRequest( # type:ignore[attr-defined]
146
136
  flag_key=flag_key, context=context
147
137
  )
148
- response = self.stub.ResolveBoolean(request)
138
+ response = self.stub.ResolveBoolean(request, **call_args)
149
139
  elif flag_type == FlagType.STRING:
150
- request = schema_pb2.ResolveStringRequest(
140
+ request = schema_pb2.ResolveStringRequest( # type:ignore[attr-defined]
151
141
  flag_key=flag_key, context=context
152
142
  )
153
- response = self.stub.ResolveString(request)
143
+ response = self.stub.ResolveString(request, **call_args)
154
144
  elif flag_type == FlagType.OBJECT:
155
- request = schema_pb2.ResolveObjectRequest(
145
+ request = schema_pb2.ResolveObjectRequest( # type:ignore[attr-defined]
156
146
  flag_key=flag_key, context=context
157
147
  )
158
- response = self.stub.ResolveObject(request)
148
+ response = self.stub.ResolveObject(request, **call_args)
159
149
  elif flag_type == FlagType.FLOAT:
160
- request = schema_pb2.ResolveFloatRequest(
150
+ request = schema_pb2.ResolveFloatRequest( # type:ignore[attr-defined]
161
151
  flag_key=flag_key, context=context
162
152
  )
163
- response = self.stub.ResolveFloat(request)
153
+ response = self.stub.ResolveFloat(request, **call_args)
164
154
  elif flag_type == FlagType.INTEGER:
165
- request = schema_pb2.ResolveIntRequest(
155
+ request = schema_pb2.ResolveIntRequest( # type:ignore[attr-defined]
166
156
  flag_key=flag_key, context=context
167
157
  )
168
- response = self.stub.ResolveInt(request)
158
+ response = self.stub.ResolveInt(request, **call_args)
169
159
  else:
170
160
  raise ValueError(f"Unknown flag type: {flag_type}")
171
161
 
@@ -182,17 +172,19 @@ class FlagdProvider(AbstractProvider):
182
172
  raise GeneralError(message) from e
183
173
 
184
174
  # Got a valid flag and valid type. Return it.
185
- return FlagEvaluationDetails(
186
- flag_key=flag_key,
175
+ return FlagResolutionDetails(
187
176
  value=response.value,
188
177
  reason=response.reason,
189
178
  variant=response.variant,
190
179
  )
191
180
 
192
- def _convert_context(self, evaluation_context: EvaluationContext):
181
+ def _convert_context(
182
+ self, evaluation_context: typing.Optional[EvaluationContext]
183
+ ) -> Struct:
193
184
  s = Struct()
194
185
  if evaluation_context:
195
186
  try:
187
+ s["targetingKey"] = evaluation_context.targeting_key
196
188
  s.update(evaluation_context.attributes)
197
189
  except ValueError as exc:
198
190
  message = (
@@ -0,0 +1,29 @@
1
+ from openfeature.contrib.provider.flagd.config import Config
2
+
3
+
4
+ def test_return_default_values():
5
+ config = Config()
6
+ assert config.host == "localhost"
7
+ assert config.port == 8013
8
+ assert config.tls is False
9
+ assert config.timeout == 5
10
+
11
+
12
+ def test_overrides_defaults_with_environment(monkeypatch):
13
+ monkeypatch.setenv("FLAGD_HOST", "flagd")
14
+ monkeypatch.setenv("FLAGD_PORT", "1234")
15
+ monkeypatch.setenv("FLAGD_TLS", "true")
16
+
17
+ config = Config()
18
+ assert config.host == "flagd"
19
+ assert config.port == 1234
20
+ assert config.tls is True
21
+
22
+
23
+ def test_uses_arguments_over_environments_and_defaults(monkeypatch):
24
+ monkeypatch.setenv("FLAGD_HOST", "flagd")
25
+
26
+ config = Config(host="flagd2", port=12345, tls=True)
27
+ assert config.host == "flagd2"
28
+ assert config.port == 12345
29
+ assert config.tls is True
@@ -1,5 +1,7 @@
1
1
  from numbers import Number
2
2
 
3
+ from openfeature.contrib.provider.flagd import FlagdProvider
4
+
3
5
 
4
6
  def test_should_get_boolean_flag_from_flagd(flagd_provider_client):
5
7
  # Given
@@ -69,3 +71,9 @@ def test_should_get_object_flag_from_flagd(flagd_provider_client):
69
71
  assert flag is not None
70
72
  assert flag.value == return_value
71
73
  assert isinstance(flag.value, dict)
74
+
75
+
76
+ def test_get_metadata_returns_metadata_object_with_name():
77
+ provider = FlagdProvider()
78
+ metadata = provider.get_metadata()
79
+ assert metadata.name == "FlagdProvider"
@@ -1,28 +0,0 @@
1
- # Changelog
2
-
3
- ## [0.1.3](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.1.2...openfeature-provider-flagd/v0.1.3) (2024-02-23)
4
-
5
-
6
- ### 🐛 Bug Fixes
7
-
8
- * include proto file in build for openfeature-provider-flagd ([#45](https://github.com/open-feature/python-sdk-contrib/issues/45)) ([7783cc8](https://github.com/open-feature/python-sdk-contrib/commit/7783cc8e7fb8fe0f9b812938efcd1f4c07e3ff68))
9
-
10
- ## [0.1.2](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd-v0.1.1...openfeature-provider-flagd/v0.1.2) (2024-02-22)
11
-
12
-
13
- ### 🐛 Bug Fixes
14
-
15
- * remove mention of local eval in readme ([41df80e](https://github.com/open-feature/python-sdk-contrib/commit/41df80e1b3044356e3b228a484f3a13c92068d91))
16
- * remove setup from flagd tests ([#39](https://github.com/open-feature/python-sdk-contrib/issues/39)) ([85661ff](https://github.com/open-feature/python-sdk-contrib/commit/85661ff170b378d37b0a3d5d0a955dad3417f538))
17
-
18
-
19
- ### 🧹 Chore
20
-
21
- * **main:** release providers/flagd 0.1.1 ([#40](https://github.com/open-feature/python-sdk-contrib/issues/40)) ([d42ee1e](https://github.com/open-feature/python-sdk-contrib/commit/d42ee1e531249e0023456dbe46db2f4f0c52a5c5))
22
-
23
- ## [0.1.1](https://github.com/open-feature/python-sdk-contrib/compare/providers/flagd-v0.1.0...providers/flagd/v0.1.1) (2024-02-22)
24
-
25
-
26
- ### 🐛 Bug Fixes
27
-
28
- * remove setup from flagd tests ([#39](https://github.com/open-feature/python-sdk-contrib/issues/39)) ([85661ff](https://github.com/open-feature/python-sdk-contrib/commit/85661ff170b378d37b0a3d5d0a955dad3417f538))
@@ -1,5 +0,0 @@
1
- class Defaults:
2
- HOST = "localhost"
3
- PORT = 8013
4
- SCHEMA = "http"
5
- TIMEOUT = 2 # seconds