openfeature-provider-flagd 0.1.3__py3-none-any.whl → 0.1.4__py3-none-any.whl

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.
@@ -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 = (
@@ -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>
@@ -1,7 +1,7 @@
1
1
  openfeature/contrib/provider/flagd/__init__.py,sha256=WlrcPaCH31dEG1IvrvpeuhAaQ8Ni8LEzDpNM_x-qKOA,65
2
- openfeature/contrib/provider/flagd/defaults.py,sha256=OeTt08wp_GUzpUetEJQvhSBGk9u4gTzACILQVqicbUw,102
2
+ openfeature/contrib/provider/flagd/config.py,sha256=8Oguwoe4V6FffdNNBfhWev2G8UjKsNYCuc_j3SGEb0Y,987
3
3
  openfeature/contrib/provider/flagd/flag_type.py,sha256=rZYfmqQEmtqVVTb8e-d8Wt8ZCnHtf7xPSmYxyU8w0R0,158
4
- openfeature/contrib/provider/flagd/provider.py,sha256=2CZxQNJfcP8S5UJgDfwp6Q-znHRRaBpP7W7sGNS5lqk,6779
4
+ openfeature/contrib/provider/flagd/provider.py,sha256=0hXxolWFUVue-6OgrC1kXa64B-bbmvc6ZtqAyexDHIc,7093
5
5
  openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2.py,sha256=W0Vgg8z7nEg_HTlAJkO6pu5nBcR8Ls3yoIykboavLRI,7704
6
6
  openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2_grpc.py,sha256=GTKQXbUfRrquUG5o11N7ymhFnOuzh9_hcToZ9-cjOtA,13339
7
7
  openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2.py,sha256=zUu_-V6DhHH5ZekWVsxKjGkUwkJrRrhqyXj8oX-Jo_g,3205
@@ -10,7 +10,7 @@ openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2.py,sha256=onIastnV
10
10
  openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2_grpc.py,sha256=nllthJYfwhDBZHH_eGfSHesNbO6rJEZ0vXKyTFFy4AA,12393
11
11
  openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2.py,sha256=uKBjB_lpfHN3r8wsjmqnQEBLtN9gw8zPmvWfWKsOYAE,2954
12
12
  openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2_grpc.py,sha256=v5MSyyIOpE68EYWjsbdAq677gnD4jdNeLhMAB7EveNQ,4424
13
- openfeature_provider_flagd-0.1.3.dist-info/METADATA,sha256=HCF4wFfmAKKkyOoHg_I3-1IHvAtbvnWhYkSbZUI1PRo,14518
14
- openfeature_provider_flagd-0.1.3.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
15
- openfeature_provider_flagd-0.1.3.dist-info/licenses/LICENSE,sha256=h8jwqxShIeVkc8vOo9ynxGYW16f4fVPxLhZKZs0H5U8,11350
16
- openfeature_provider_flagd-0.1.3.dist-info/RECORD,,
13
+ openfeature_provider_flagd-0.1.4.dist-info/METADATA,sha256=wtW6iavradbR6Y1d66M-SFcsMv8zjwQ5ylY4udCd8fs,14518
14
+ openfeature_provider_flagd-0.1.4.dist-info/WHEEL,sha256=uNdcs2TADwSd5pVaP0Z_kcjcvvTUklh2S7bxZMF8Uj0,87
15
+ openfeature_provider_flagd-0.1.4.dist-info/licenses/LICENSE,sha256=h8jwqxShIeVkc8vOo9ynxGYW16f4fVPxLhZKZs0H5U8,11350
16
+ openfeature_provider_flagd-0.1.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.21.1
2
+ Generator: hatchling 1.22.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,5 +0,0 @@
1
- class Defaults:
2
- HOST = "localhost"
3
- PORT = 8013
4
- SCHEMA = "http"
5
- TIMEOUT = 2 # seconds