openfeature-provider-flagd 0.1.4__py3-none-any.whl → 0.2.0__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.
Files changed (42) hide show
  1. openfeature/.gitignore +2 -0
  2. openfeature/contrib/provider/flagd/config.py +202 -8
  3. openfeature/contrib/provider/flagd/provider.py +89 -97
  4. openfeature/contrib/provider/flagd/resolvers/__init__.py +5 -0
  5. openfeature/contrib/provider/flagd/resolvers/grpc.py +354 -0
  6. openfeature/contrib/provider/flagd/resolvers/in_process.py +131 -0
  7. openfeature/contrib/provider/flagd/resolvers/process/connector/__init__.py +11 -0
  8. openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +106 -0
  9. openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py +192 -0
  10. openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py +165 -0
  11. openfeature/contrib/provider/flagd/resolvers/process/flags.py +95 -0
  12. openfeature/contrib/provider/flagd/resolvers/process/targeting.py +35 -0
  13. openfeature/contrib/provider/flagd/resolvers/protocol.py +47 -0
  14. openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +72 -0
  15. openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.pyi +450 -0
  16. openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.py +358 -0
  17. openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +155 -0
  18. openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +50 -0
  19. openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.pyi +148 -0
  20. openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.py +186 -0
  21. openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +86 -0
  22. openfeature/schemas/protobuf/schema/v1/schema_pb2.py +72 -0
  23. openfeature/schemas/protobuf/schema/v1/schema_pb2.pyi +451 -0
  24. openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.py +358 -0
  25. openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +156 -0
  26. openfeature/schemas/protobuf/sync/v1/sync_service_pb2.py +47 -0
  27. openfeature/schemas/protobuf/sync/v1/sync_service_pb2.pyi +174 -0
  28. openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.py +143 -0
  29. openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +70 -0
  30. {openfeature_provider_flagd-0.1.4.dist-info → openfeature_provider_flagd-0.2.0.dist-info}/METADATA +132 -14
  31. openfeature_provider_flagd-0.2.0.dist-info/RECORD +35 -0
  32. {openfeature_provider_flagd-0.1.4.dist-info → openfeature_provider_flagd-0.2.0.dist-info}/WHEEL +1 -1
  33. {openfeature_provider_flagd-0.1.4.dist-info → openfeature_provider_flagd-0.2.0.dist-info}/licenses/LICENSE +1 -1
  34. openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2.py +0 -62
  35. openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2_grpc.py +0 -267
  36. openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2.py +0 -40
  37. openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2_grpc.py +0 -135
  38. openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2.py +0 -62
  39. openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2_grpc.py +0 -267
  40. openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2.py +0 -37
  41. openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2_grpc.py +0 -102
  42. openfeature_provider_flagd-0.1.4.dist-info/RECORD +0 -16
openfeature/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+
2
+ schemas
@@ -1,5 +1,53 @@
1
+ import dataclasses
1
2
  import os
2
3
  import typing
4
+ from enum import Enum
5
+
6
+
7
+ class ResolverType(Enum):
8
+ RPC = "rpc"
9
+ IN_PROCESS = "in-process"
10
+ FILE = "file"
11
+
12
+
13
+ class CacheType(Enum):
14
+ LRU = "lru"
15
+ DISABLED = "disabled"
16
+
17
+
18
+ DEFAULT_CACHE = CacheType.LRU
19
+ DEFAULT_CACHE_SIZE = 1000
20
+ DEFAULT_DEADLINE = 500
21
+ DEFAULT_HOST = "localhost"
22
+ DEFAULT_KEEP_ALIVE = 0
23
+ DEFAULT_OFFLINE_SOURCE_PATH: typing.Optional[str] = None
24
+ DEFAULT_OFFLINE_POLL_MS = 5000
25
+ DEFAULT_PORT_IN_PROCESS = 8015
26
+ DEFAULT_PORT_RPC = 8013
27
+ DEFAULT_RESOLVER_TYPE = ResolverType.RPC
28
+ DEFAULT_RETRY_BACKOFF = 1000
29
+ DEFAULT_RETRY_BACKOFF_MAX = 120000
30
+ DEFAULT_RETRY_GRACE_PERIOD_SECONDS = 5
31
+ DEFAULT_STREAM_DEADLINE = 600000
32
+ DEFAULT_TLS = False
33
+ DEFAULT_TLS_CERT: typing.Optional[str] = None
34
+
35
+ ENV_VAR_CACHE_SIZE = "FLAGD_MAX_CACHE_SIZE"
36
+ ENV_VAR_CACHE_TYPE = "FLAGD_CACHE"
37
+ ENV_VAR_DEADLINE_MS = "FLAGD_DEADLINE_MS"
38
+ ENV_VAR_HOST = "FLAGD_HOST"
39
+ ENV_VAR_KEEP_ALIVE_TIME_MS = "FLAGD_KEEP_ALIVE_TIME_MS"
40
+ ENV_VAR_OFFLINE_FLAG_SOURCE_PATH = "FLAGD_OFFLINE_FLAG_SOURCE_PATH"
41
+ ENV_VAR_OFFLINE_POLL_MS = "FLAGD_OFFLINE_POLL_MS"
42
+ ENV_VAR_PORT = "FLAGD_PORT"
43
+ ENV_VAR_RESOLVER_TYPE = "FLAGD_RESOLVER"
44
+ ENV_VAR_RETRY_BACKOFF_MS = "FLAGD_RETRY_BACKOFF_MS"
45
+ ENV_VAR_RETRY_BACKOFF_MAX_MS = "FLAGD_RETRY_BACKOFF_MAX_MS"
46
+ ENV_VAR_RETRY_GRACE_PERIOD_SECONDS = "FLAGD_RETRY_GRACE_PERIOD"
47
+ ENV_VAR_SELECTOR = "FLAGD_SOURCE_SELECTOR"
48
+ ENV_VAR_STREAM_DEADLINE_MS = "FLAGD_STREAM_DEADLINE_MS"
49
+ ENV_VAR_TLS = "FLAGD_TLS"
50
+ ENV_VAR_TLS_CERT = "FLAGD_SERVER_CERT_PATH"
3
51
 
4
52
  T = typing.TypeVar("T")
5
53
 
@@ -8,6 +56,14 @@ def str_to_bool(val: str) -> bool:
8
56
  return val.lower() == "true"
9
57
 
10
58
 
59
+ def convert_resolver_type(val: typing.Union[str, ResolverType]) -> ResolverType:
60
+ if isinstance(val, str):
61
+ v = val.lower()
62
+ return ResolverType(v)
63
+ else:
64
+ return ResolverType(val)
65
+
66
+
11
67
  def env_or_default(
12
68
  env_var: str, default: T, cast: typing.Optional[typing.Callable[[str], T]] = None
13
69
  ) -> typing.Union[str, T]:
@@ -17,19 +73,157 @@ def env_or_default(
17
73
  return val if cast is None else cast(val)
18
74
 
19
75
 
76
+ @dataclasses.dataclass
20
77
  class Config:
21
- def __init__(
78
+ def __init__( # noqa: PLR0913
22
79
  self,
23
80
  host: typing.Optional[str] = None,
24
81
  port: typing.Optional[int] = None,
25
82
  tls: typing.Optional[bool] = None,
26
- timeout: typing.Optional[int] = None,
83
+ selector: typing.Optional[str] = None,
84
+ resolver: typing.Optional[ResolverType] = None,
85
+ offline_flag_source_path: typing.Optional[str] = None,
86
+ offline_poll_interval_ms: typing.Optional[int] = None,
87
+ retry_backoff_ms: typing.Optional[int] = None,
88
+ retry_backoff_max_ms: typing.Optional[int] = None,
89
+ retry_grace_period: typing.Optional[int] = None,
90
+ deadline_ms: typing.Optional[int] = None,
91
+ stream_deadline_ms: typing.Optional[int] = None,
92
+ keep_alive_time: typing.Optional[int] = None,
93
+ cache: typing.Optional[CacheType] = None,
94
+ max_cache_size: typing.Optional[int] = None,
95
+ cert_path: typing.Optional[str] = None,
27
96
  ):
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
- )
97
+ self.host = env_or_default(ENV_VAR_HOST, DEFAULT_HOST) if host is None else host
98
+
32
99
  self.tls = (
33
- env_or_default("FLAGD_TLS", False, cast=str_to_bool) if tls is None else tls
100
+ env_or_default(ENV_VAR_TLS, DEFAULT_TLS, cast=str_to_bool)
101
+ if tls is None
102
+ else tls
103
+ )
104
+
105
+ self.retry_backoff_ms: int = (
106
+ int(
107
+ env_or_default(
108
+ ENV_VAR_RETRY_BACKOFF_MS, DEFAULT_RETRY_BACKOFF, cast=int
109
+ )
110
+ )
111
+ if retry_backoff_ms is None
112
+ else retry_backoff_ms
113
+ )
114
+ self.retry_backoff_max_ms: int = (
115
+ int(
116
+ env_or_default(
117
+ ENV_VAR_RETRY_BACKOFF_MAX_MS, DEFAULT_RETRY_BACKOFF_MAX, cast=int
118
+ )
119
+ )
120
+ if retry_backoff_max_ms is None
121
+ else retry_backoff_max_ms
122
+ )
123
+
124
+ self.retry_grace_period: int = (
125
+ int(
126
+ env_or_default(
127
+ ENV_VAR_RETRY_GRACE_PERIOD_SECONDS,
128
+ DEFAULT_RETRY_GRACE_PERIOD_SECONDS,
129
+ cast=int,
130
+ )
131
+ )
132
+ if retry_grace_period is None
133
+ else retry_grace_period
134
+ )
135
+
136
+ self.resolver = (
137
+ env_or_default(
138
+ ENV_VAR_RESOLVER_TYPE, DEFAULT_RESOLVER_TYPE, cast=convert_resolver_type
139
+ )
140
+ if resolver is None
141
+ else resolver
142
+ )
143
+
144
+ default_port = (
145
+ DEFAULT_PORT_RPC
146
+ if self.resolver is ResolverType.RPC
147
+ else DEFAULT_PORT_IN_PROCESS
148
+ )
149
+
150
+ self.port: int = (
151
+ int(env_or_default(ENV_VAR_PORT, default_port, cast=int))
152
+ if port is None
153
+ else port
154
+ )
155
+
156
+ self.offline_flag_source_path = (
157
+ env_or_default(
158
+ ENV_VAR_OFFLINE_FLAG_SOURCE_PATH, DEFAULT_OFFLINE_SOURCE_PATH
159
+ )
160
+ if offline_flag_source_path is None
161
+ else offline_flag_source_path
162
+ )
163
+
164
+ if (
165
+ self.offline_flag_source_path is not None
166
+ and self.resolver is ResolverType.IN_PROCESS
167
+ ):
168
+ self.resolver = ResolverType.FILE
169
+
170
+ if self.resolver is ResolverType.FILE and self.offline_flag_source_path is None:
171
+ raise AttributeError(
172
+ "Resolver Type 'FILE' requires a offlineFlagSourcePath"
173
+ )
174
+
175
+ self.offline_poll_interval_ms: int = (
176
+ int(
177
+ env_or_default(
178
+ ENV_VAR_OFFLINE_POLL_MS, DEFAULT_OFFLINE_POLL_MS, cast=int
179
+ )
180
+ )
181
+ if offline_poll_interval_ms is None
182
+ else offline_poll_interval_ms
183
+ )
184
+
185
+ self.deadline_ms: int = (
186
+ int(env_or_default(ENV_VAR_DEADLINE_MS, DEFAULT_DEADLINE, cast=int))
187
+ if deadline_ms is None
188
+ else deadline_ms
189
+ )
190
+
191
+ self.stream_deadline_ms: int = (
192
+ int(
193
+ env_or_default(
194
+ ENV_VAR_STREAM_DEADLINE_MS, DEFAULT_STREAM_DEADLINE, cast=int
195
+ )
196
+ )
197
+ if stream_deadline_ms is None
198
+ else stream_deadline_ms
199
+ )
200
+
201
+ self.keep_alive_time: int = (
202
+ int(
203
+ env_or_default(ENV_VAR_KEEP_ALIVE_TIME_MS, DEFAULT_KEEP_ALIVE, cast=int)
204
+ )
205
+ if keep_alive_time is None
206
+ else keep_alive_time
207
+ )
208
+
209
+ self.cache = (
210
+ CacheType(env_or_default(ENV_VAR_CACHE_TYPE, DEFAULT_CACHE))
211
+ if cache is None
212
+ else cache
213
+ )
214
+
215
+ self.max_cache_size: int = (
216
+ int(env_or_default(ENV_VAR_CACHE_SIZE, DEFAULT_CACHE_SIZE, cast=int))
217
+ if max_cache_size is None
218
+ else max_cache_size
219
+ )
220
+
221
+ self.cert_path = (
222
+ env_or_default(ENV_VAR_TLS_CERT, DEFAULT_TLS_CERT)
223
+ if cert_path is None
224
+ else cert_path
225
+ )
226
+
227
+ self.selector = (
228
+ env_or_default(ENV_VAR_SELECTOR, None) if selector is None else selector
34
229
  )
35
- self.timeout = 5 if timeout is None else timeout
@@ -22,25 +22,15 @@
22
22
  """
23
23
 
24
24
  import typing
25
-
26
- import grpc
27
- from google.protobuf.struct_pb2 import Struct
25
+ import warnings
28
26
 
29
27
  from openfeature.evaluation_context import EvaluationContext
30
- from openfeature.exception import (
31
- FlagNotFoundError,
32
- GeneralError,
33
- InvalidContextError,
34
- ParseError,
35
- TypeMismatchError,
36
- )
37
28
  from openfeature.flag_evaluation import FlagResolutionDetails
29
+ from openfeature.provider import AbstractProvider
38
30
  from openfeature.provider.metadata import Metadata
39
- from openfeature.provider.provider import AbstractProvider
40
31
 
41
- from .config import Config
42
- from .flag_type import FlagType
43
- from .proto.schema.v1 import schema_pb2, schema_pb2_grpc
32
+ from .config import CacheType, Config, ResolverType
33
+ from .resolvers import AbstractResolver, GrpcResolver, InProcessResolver
44
34
 
45
35
  T = typing.TypeVar("T")
46
36
 
@@ -48,12 +38,24 @@ T = typing.TypeVar("T")
48
38
  class FlagdProvider(AbstractProvider):
49
39
  """Flagd OpenFeature Provider"""
50
40
 
51
- def __init__(
41
+ def __init__( # noqa: PLR0913
52
42
  self,
53
43
  host: typing.Optional[str] = None,
54
44
  port: typing.Optional[int] = None,
55
45
  tls: typing.Optional[bool] = None,
46
+ deadline_ms: typing.Optional[int] = None,
56
47
  timeout: typing.Optional[int] = None,
48
+ retry_backoff_ms: typing.Optional[int] = None,
49
+ selector: typing.Optional[str] = None,
50
+ resolver_type: typing.Optional[ResolverType] = None,
51
+ offline_flag_source_path: typing.Optional[str] = None,
52
+ stream_deadline_ms: typing.Optional[int] = None,
53
+ keep_alive_time: typing.Optional[int] = None,
54
+ cache: typing.Optional[CacheType] = None,
55
+ max_cache_size: typing.Optional[int] = None,
56
+ retry_backoff_max_ms: typing.Optional[int] = None,
57
+ retry_grace_period: typing.Optional[int] = None,
58
+ cert_path: typing.Optional[str] = None,
57
59
  ):
58
60
  """
59
61
  Create an instance of the FlagdProvider
@@ -61,21 +63,73 @@ class FlagdProvider(AbstractProvider):
61
63
  :param host: the host to make requests to
62
64
  :param port: the port the flagd service is available on
63
65
  :param tls: enable/disable secure TLS connectivity
64
- :param timeout: the maximum to wait before a request times out
66
+ :param deadline_ms: the maximum to wait before a request times out
67
+ :param timeout: the maximum time to wait before a request times out
68
+ :param retry_backoff_ms: the number of milliseconds to backoff
69
+ :param offline_flag_source_path: the path to the flag source file
70
+ :param stream_deadline_ms: the maximum time to wait before a request times out
71
+ :param keep_alive_time: the number of milliseconds to keep alive
72
+ :param resolver_type: the type of resolver to use
65
73
  """
74
+ if deadline_ms is None and timeout is not None:
75
+ deadline_ms = timeout * 1000
76
+ warnings.warn(
77
+ "'timeout' property is deprecated, please use 'deadline' instead, be aware that 'deadline' is in milliseconds",
78
+ DeprecationWarning,
79
+ stacklevel=2,
80
+ )
81
+
66
82
  self.config = Config(
67
83
  host=host,
68
84
  port=port,
69
85
  tls=tls,
70
- timeout=timeout,
86
+ deadline_ms=deadline_ms,
87
+ retry_backoff_ms=retry_backoff_ms,
88
+ retry_backoff_max_ms=retry_backoff_max_ms,
89
+ retry_grace_period=retry_grace_period,
90
+ selector=selector,
91
+ resolver=resolver_type,
92
+ offline_flag_source_path=offline_flag_source_path,
93
+ stream_deadline_ms=stream_deadline_ms,
94
+ keep_alive_time=keep_alive_time,
95
+ cache=cache,
96
+ max_cache_size=max_cache_size,
97
+ cert_path=cert_path,
71
98
  )
72
99
 
73
- channel_factory = grpc.secure_channel if tls else grpc.insecure_channel
74
- self.channel = channel_factory(f"{self.config.host}:{self.config.port}")
75
- self.stub = schema_pb2_grpc.ServiceStub(self.channel)
100
+ self.resolver = self.setup_resolver()
101
+
102
+ def setup_resolver(self) -> AbstractResolver:
103
+ if self.config.resolver == ResolverType.RPC:
104
+ return GrpcResolver(
105
+ self.config,
106
+ self.emit_provider_ready,
107
+ self.emit_provider_error,
108
+ self.emit_provider_stale,
109
+ self.emit_provider_configuration_changed,
110
+ )
111
+ elif (
112
+ self.config.resolver == ResolverType.IN_PROCESS
113
+ or self.config.resolver == ResolverType.FILE
114
+ ):
115
+ return InProcessResolver(
116
+ self.config,
117
+ self.emit_provider_ready,
118
+ self.emit_provider_error,
119
+ self.emit_provider_stale,
120
+ self.emit_provider_configuration_changed,
121
+ )
122
+ else:
123
+ raise ValueError(
124
+ f"`resolver_type` parameter invalid: {self.config.resolver}"
125
+ )
126
+
127
+ def initialize(self, evaluation_context: EvaluationContext) -> None:
128
+ self.resolver.initialize(evaluation_context)
76
129
 
77
130
  def shutdown(self) -> None:
78
- self.channel.close()
131
+ if self.resolver:
132
+ self.resolver.shutdown()
79
133
 
80
134
  def get_metadata(self) -> Metadata:
81
135
  """Returns provider metadata"""
@@ -87,7 +141,9 @@ class FlagdProvider(AbstractProvider):
87
141
  default_value: bool,
88
142
  evaluation_context: typing.Optional[EvaluationContext] = None,
89
143
  ) -> FlagResolutionDetails[bool]:
90
- return self._resolve(key, FlagType.BOOLEAN, default_value, evaluation_context)
144
+ return self.resolver.resolve_boolean_details(
145
+ key, default_value, evaluation_context
146
+ )
91
147
 
92
148
  def resolve_string_details(
93
149
  self,
@@ -95,7 +151,9 @@ class FlagdProvider(AbstractProvider):
95
151
  default_value: str,
96
152
  evaluation_context: typing.Optional[EvaluationContext] = None,
97
153
  ) -> FlagResolutionDetails[str]:
98
- return self._resolve(key, FlagType.STRING, default_value, evaluation_context)
154
+ return self.resolver.resolve_string_details(
155
+ key, default_value, evaluation_context
156
+ )
99
157
 
100
158
  def resolve_float_details(
101
159
  self,
@@ -103,7 +161,9 @@ class FlagdProvider(AbstractProvider):
103
161
  default_value: float,
104
162
  evaluation_context: typing.Optional[EvaluationContext] = None,
105
163
  ) -> FlagResolutionDetails[float]:
106
- return self._resolve(key, FlagType.FLOAT, default_value, evaluation_context)
164
+ return self.resolver.resolve_float_details(
165
+ key, default_value, evaluation_context
166
+ )
107
167
 
108
168
  def resolve_integer_details(
109
169
  self,
@@ -111,7 +171,9 @@ class FlagdProvider(AbstractProvider):
111
171
  default_value: int,
112
172
  evaluation_context: typing.Optional[EvaluationContext] = None,
113
173
  ) -> FlagResolutionDetails[int]:
114
- return self._resolve(key, FlagType.INTEGER, default_value, evaluation_context)
174
+ return self.resolver.resolve_integer_details(
175
+ key, default_value, evaluation_context
176
+ )
115
177
 
116
178
  def resolve_object_details(
117
179
  self,
@@ -119,76 +181,6 @@ class FlagdProvider(AbstractProvider):
119
181
  default_value: typing.Union[dict, list],
120
182
  evaluation_context: typing.Optional[EvaluationContext] = None,
121
183
  ) -> FlagResolutionDetails[typing.Union[dict, list]]:
122
- return self._resolve(key, FlagType.OBJECT, default_value, evaluation_context)
123
-
124
- def _resolve(
125
- self,
126
- flag_key: str,
127
- flag_type: FlagType,
128
- default_value: T,
129
- evaluation_context: typing.Optional[EvaluationContext],
130
- ) -> FlagResolutionDetails[T]:
131
- context = self._convert_context(evaluation_context)
132
- call_args = {"timeout": self.config.timeout}
133
- try:
134
- if flag_type == FlagType.BOOLEAN:
135
- request = schema_pb2.ResolveBooleanRequest( # type:ignore[attr-defined]
136
- flag_key=flag_key, context=context
137
- )
138
- response = self.stub.ResolveBoolean(request, **call_args)
139
- elif flag_type == FlagType.STRING:
140
- request = schema_pb2.ResolveStringRequest( # type:ignore[attr-defined]
141
- flag_key=flag_key, context=context
142
- )
143
- response = self.stub.ResolveString(request, **call_args)
144
- elif flag_type == FlagType.OBJECT:
145
- request = schema_pb2.ResolveObjectRequest( # type:ignore[attr-defined]
146
- flag_key=flag_key, context=context
147
- )
148
- response = self.stub.ResolveObject(request, **call_args)
149
- elif flag_type == FlagType.FLOAT:
150
- request = schema_pb2.ResolveFloatRequest( # type:ignore[attr-defined]
151
- flag_key=flag_key, context=context
152
- )
153
- response = self.stub.ResolveFloat(request, **call_args)
154
- elif flag_type == FlagType.INTEGER:
155
- request = schema_pb2.ResolveIntRequest( # type:ignore[attr-defined]
156
- flag_key=flag_key, context=context
157
- )
158
- response = self.stub.ResolveInt(request, **call_args)
159
- else:
160
- raise ValueError(f"Unknown flag type: {flag_type}")
161
-
162
- except grpc.RpcError as e:
163
- code = e.code()
164
- message = f"received grpc status code {code}"
165
-
166
- if code == grpc.StatusCode.NOT_FOUND:
167
- raise FlagNotFoundError(message) from e
168
- elif code == grpc.StatusCode.INVALID_ARGUMENT:
169
- raise TypeMismatchError(message) from e
170
- elif code == grpc.StatusCode.DATA_LOSS:
171
- raise ParseError(message) from e
172
- raise GeneralError(message) from e
173
-
174
- # Got a valid flag and valid type. Return it.
175
- return FlagResolutionDetails(
176
- value=response.value,
177
- reason=response.reason,
178
- variant=response.variant,
184
+ return self.resolver.resolve_object_details(
185
+ key, default_value, evaluation_context
179
186
  )
180
-
181
- def _convert_context(
182
- self, evaluation_context: typing.Optional[EvaluationContext]
183
- ) -> Struct:
184
- s = Struct()
185
- if evaluation_context:
186
- try:
187
- s["targetingKey"] = evaluation_context.targeting_key
188
- s.update(evaluation_context.attributes)
189
- except ValueError as exc:
190
- message = (
191
- "could not serialize evaluation context to google.protobuf.Struct"
192
- )
193
- raise InvalidContextError(message) from exc
194
- return s
@@ -0,0 +1,5 @@
1
+ from .grpc import GrpcResolver
2
+ from .in_process import InProcessResolver
3
+ from .protocol import AbstractResolver
4
+
5
+ __all__ = ["AbstractResolver", "GrpcResolver", "InProcessResolver"]