openfeature-provider-flagd 0.1.5__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 (43) hide show
  1. openfeature/.gitignore +2 -0
  2. openfeature/contrib/provider/flagd/config.py +193 -23
  3. openfeature/contrib/provider/flagd/provider.py +62 -12
  4. openfeature/contrib/provider/flagd/resolvers/__init__.py +1 -47
  5. openfeature/contrib/provider/flagd/resolvers/grpc.py +226 -17
  6. openfeature/contrib/provider/flagd/resolvers/in_process.py +40 -31
  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 +58 -19
  11. openfeature/contrib/provider/flagd/resolvers/process/flags.py +50 -6
  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.5.dist-info → openfeature_provider_flagd-0.2.0.dist-info}/METADATA +116 -15
  31. openfeature_provider_flagd-0.2.0.dist-info/RECORD +35 -0
  32. {openfeature_provider_flagd-0.1.5.dist-info → openfeature_provider_flagd-0.2.0.dist-info}/WHEEL +1 -1
  33. {openfeature_provider_flagd-0.1.5.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/contrib/provider/flagd/resolvers/process/file_watcher.py +0 -89
  43. openfeature_provider_flagd-0.1.5.dist-info/RECORD +0 -22
@@ -0,0 +1,192 @@
1
+ import json
2
+ import logging
3
+ import threading
4
+ import time
5
+ import typing
6
+
7
+ import grpc
8
+
9
+ from openfeature.evaluation_context import EvaluationContext
10
+ from openfeature.event import ProviderEventDetails
11
+ from openfeature.exception import ErrorCode, ParseError, ProviderNotReadyError
12
+ from openfeature.schemas.protobuf.flagd.sync.v1 import (
13
+ sync_pb2,
14
+ sync_pb2_grpc,
15
+ )
16
+
17
+ from ....config import Config
18
+ from ..connector import FlagStateConnector
19
+ from ..flags import FlagStore
20
+
21
+ logger = logging.getLogger("openfeature.contrib")
22
+
23
+
24
+ class GrpcWatcher(FlagStateConnector):
25
+ def __init__(
26
+ self,
27
+ config: Config,
28
+ flag_store: FlagStore,
29
+ emit_provider_ready: typing.Callable[[ProviderEventDetails], None],
30
+ emit_provider_error: typing.Callable[[ProviderEventDetails], None],
31
+ emit_provider_stale: typing.Callable[[ProviderEventDetails], None],
32
+ ):
33
+ self.flag_store = flag_store
34
+ self.config = config
35
+
36
+ self.channel = self._generate_channel(config)
37
+ self.stub = sync_pb2_grpc.FlagSyncServiceStub(self.channel)
38
+ self.retry_backoff_seconds = config.retry_backoff_ms * 0.001
39
+ self.retry_backoff_max_seconds = config.retry_backoff_ms * 0.001
40
+ self.retry_grace_period = config.retry_grace_period
41
+ self.streamline_deadline_seconds = config.stream_deadline_ms * 0.001
42
+ self.deadline = config.deadline_ms * 0.001
43
+ self.selector = config.selector
44
+ self.emit_provider_ready = emit_provider_ready
45
+ self.emit_provider_error = emit_provider_error
46
+ self.emit_provider_stale = emit_provider_stale
47
+
48
+ self.connected = False
49
+ self.thread: typing.Optional[threading.Thread] = None
50
+ self.timer: typing.Optional[threading.Timer] = None
51
+
52
+ self.start_time = time.time()
53
+
54
+ def _generate_channel(self, config: Config) -> grpc.Channel:
55
+ target = f"{config.host}:{config.port}"
56
+ # Create the channel with the service config
57
+ options = [
58
+ ("grpc.keepalive_time_ms", config.keep_alive_time),
59
+ ("grpc.initial_reconnect_backoff_ms", config.retry_backoff_ms),
60
+ ("grpc.max_reconnect_backoff_ms", config.retry_backoff_max_ms),
61
+ ("grpc.min_reconnect_backoff_ms", config.stream_deadline_ms),
62
+ ]
63
+ if config.tls:
64
+ channel_args = {
65
+ "options": options,
66
+ "credentials": grpc.ssl_channel_credentials(),
67
+ }
68
+ if config.cert_path:
69
+ with open(config.cert_path, "rb") as f:
70
+ channel_args["credentials"] = grpc.ssl_channel_credentials(f.read())
71
+
72
+ channel = grpc.secure_channel(target, **channel_args)
73
+
74
+ else:
75
+ channel = grpc.insecure_channel(
76
+ target,
77
+ options=options,
78
+ )
79
+
80
+ return channel
81
+
82
+ def initialize(self, context: EvaluationContext) -> None:
83
+ self.connect()
84
+
85
+ def connect(self) -> None:
86
+ self.active = True
87
+
88
+ # Run monitoring in a separate thread
89
+ self.monitor_thread = threading.Thread(
90
+ target=self.monitor, daemon=True, name="FlagdGrpcSyncServiceMonitorThread"
91
+ )
92
+ self.monitor_thread.start()
93
+ ## block until ready or deadline reached
94
+ timeout = self.deadline + time.time()
95
+ while not self.connected and time.time() < timeout:
96
+ time.sleep(0.05)
97
+ logger.debug("Finished blocking gRPC state initialization")
98
+
99
+ if not self.connected:
100
+ raise ProviderNotReadyError(
101
+ "Blocking init finished before data synced. Consider increasing startup deadline to avoid inconsistent evaluations."
102
+ )
103
+
104
+ def monitor(self) -> None:
105
+ self.channel.subscribe(self._state_change_callback, try_to_connect=True)
106
+
107
+ def _state_change_callback(self, new_state: grpc.ChannelConnectivity) -> None:
108
+ logger.debug(f"gRPC state change: {new_state}")
109
+ if new_state == grpc.ChannelConnectivity.READY:
110
+ if not self.thread or not self.thread.is_alive():
111
+ self.thread = threading.Thread(
112
+ target=self.listen,
113
+ daemon=True,
114
+ name="FlagdGrpcSyncWorkerThread",
115
+ )
116
+ self.thread.start()
117
+
118
+ if self.timer and self.timer.is_alive():
119
+ logger.debug("gRPC error timer expired")
120
+ self.timer.cancel()
121
+
122
+ elif new_state == grpc.ChannelConnectivity.TRANSIENT_FAILURE:
123
+ # this is the failed reconnect attempt so we are going into stale
124
+ self.emit_provider_stale(
125
+ ProviderEventDetails(
126
+ message="gRPC sync disconnected, reconnecting",
127
+ )
128
+ )
129
+ self.start_time = time.time()
130
+ # adding a timer, so we can emit the error event after time
131
+ self.timer = threading.Timer(self.retry_grace_period, self.emit_error)
132
+
133
+ logger.debug("gRPC error timer started")
134
+ self.timer.start()
135
+ self.connected = False
136
+
137
+ def emit_error(self) -> None:
138
+ logger.debug("gRPC error emitted")
139
+ self.emit_provider_error(
140
+ ProviderEventDetails(
141
+ message="gRPC sync disconnected, reconnecting",
142
+ error_code=ErrorCode.GENERAL,
143
+ )
144
+ )
145
+
146
+ def shutdown(self) -> None:
147
+ self.active = False
148
+ self.channel.close()
149
+
150
+ def listen(self) -> None:
151
+ call_args = (
152
+ {"timeout": self.streamline_deadline_seconds}
153
+ if self.streamline_deadline_seconds > 0
154
+ else {}
155
+ )
156
+ request_args = {"selector": self.selector} if self.selector is not None else {}
157
+
158
+ while self.active:
159
+ try:
160
+ request = sync_pb2.SyncFlagsRequest(**request_args)
161
+
162
+ logger.debug("Setting up gRPC sync flags connection")
163
+ for flag_rsp in self.stub.SyncFlags(
164
+ request, wait_for_ready=True, **call_args
165
+ ):
166
+ flag_str = flag_rsp.flag_configuration
167
+ logger.debug(
168
+ f"Received flag configuration - {abs(hash(flag_str)) % (10**8)}"
169
+ )
170
+ self.flag_store.update(json.loads(flag_str))
171
+
172
+ if not self.connected:
173
+ self.emit_provider_ready(
174
+ ProviderEventDetails(
175
+ message="gRPC sync connection established"
176
+ )
177
+ )
178
+ self.connected = True
179
+
180
+ if not self.active:
181
+ logger.debug("Terminating gRPC sync thread")
182
+ return
183
+ except grpc.RpcError as e: # noqa: PERF203
184
+ logger.error(f"SyncFlags stream error, {e.code()=} {e.details()=}")
185
+ except json.JSONDecodeError:
186
+ logger.exception(
187
+ f"Could not parse JSON flag data from SyncFlags endpoint: {flag_str=}"
188
+ )
189
+ except ParseError:
190
+ logger.exception(
191
+ f"Could not parse flag data using flagd syntax: {flag_str=}"
192
+ )
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import typing
3
+ from dataclasses import dataclass
3
4
 
4
5
  import mmh3
5
6
  import semver
@@ -10,6 +11,12 @@ JsonLogicArg = typing.Union[JsonPrimitive, typing.Sequence[JsonPrimitive]]
10
11
  logger = logging.getLogger("openfeature.contrib")
11
12
 
12
13
 
14
+ @dataclass
15
+ class Fraction:
16
+ variant: str
17
+ weight: int = 1
18
+
19
+
13
20
  def fractional(data: dict, *args: JsonLogicArg) -> typing.Optional[str]:
14
21
  if not args:
15
22
  logger.error("No arguments provided to fractional operator.")
@@ -32,28 +39,52 @@ def fractional(data: dict, *args: JsonLogicArg) -> typing.Optional[str]:
32
39
  return None
33
40
 
34
41
  hash_ratio = abs(mmh3.hash(bucket_by)) / (2**31 - 1)
35
- bucket = int(hash_ratio * 100)
36
-
37
- for arg in args:
38
- if (
39
- not isinstance(arg, (tuple, list))
40
- or len(arg) != 2
41
- or not isinstance(arg[0], str)
42
- or not isinstance(arg[1], int)
43
- ):
44
- logger.error("Fractional variant weights must be (str, int) tuple")
45
- return None
46
- variant_weights: typing.Tuple[typing.Tuple[str, int]] = args # type: ignore[assignment]
42
+ bucket = hash_ratio * 100
47
43
 
48
- range_end = 0
49
- for variant, weight in variant_weights:
50
- range_end += weight
51
- if bucket < range_end:
52
- return variant
44
+ total_weight = 0
45
+ fractions = []
46
+ try:
47
+ for arg in args:
48
+ fraction = _parse_fraction(arg)
49
+ if fraction:
50
+ fractions.append(fraction)
51
+ total_weight += fraction.weight
52
+
53
+ except ValueError:
54
+ logger.debug(f"Invalid {args} configuration")
55
+ return None
53
56
 
57
+ range_end: float = 0
58
+ for fraction in fractions:
59
+ range_end += fraction.weight * 100 / total_weight
60
+ if bucket < range_end:
61
+ return fraction.variant
54
62
  return None
55
63
 
56
64
 
65
+ def _parse_fraction(arg: JsonLogicArg) -> Fraction:
66
+ if not isinstance(arg, (tuple, list)) or not arg or len(arg) > 2:
67
+ raise ValueError(
68
+ "Fractional variant weights must be (str, int) tuple or [str] list"
69
+ )
70
+
71
+ if not isinstance(arg[0], str):
72
+ raise ValueError(
73
+ "Fractional variant identifier (first element) isn't of type 'str'"
74
+ )
75
+
76
+ if len(arg) >= 2 and not isinstance(arg[1], int):
77
+ raise ValueError(
78
+ "Fractional variant weight value (second element) isn't of type 'int'"
79
+ )
80
+
81
+ fraction = Fraction(variant=arg[0])
82
+ if len(arg) >= 2:
83
+ fraction.weight = arg[1]
84
+
85
+ return fraction
86
+
87
+
57
88
  def starts_with(data: dict, *args: JsonLogicArg) -> typing.Optional[bool]:
58
89
  def f(s1: str, s2: str) -> bool:
59
90
  return s1.startswith(s2)
@@ -99,8 +130,8 @@ def sem_ver(data: dict, *args: JsonLogicArg) -> typing.Optional[bool]: # noqa:
99
130
  arg1, op, arg2 = args
100
131
 
101
132
  try:
102
- v1 = semver.Version.parse(str(arg1))
103
- v2 = semver.Version.parse(str(arg2))
133
+ v1 = parse_version(arg1)
134
+ v2 = parse_version(arg2)
104
135
  except ValueError as e:
105
136
  logger.exception(e)
106
137
  return None
@@ -124,3 +155,11 @@ def sem_ver(data: dict, *args: JsonLogicArg) -> typing.Optional[bool]: # noqa:
124
155
  else:
125
156
  logger.error(f"Op not supported by sem_ver: {op}")
126
157
  return None
158
+
159
+
160
+ def parse_version(arg: typing.Any) -> semver.Version:
161
+ version = str(arg)
162
+ if version.startswith(("v", "V")):
163
+ version = version[1:]
164
+
165
+ return semver.Version.parse(version)
@@ -1,9 +1,45 @@
1
+ import json
2
+ import re
1
3
  import typing
2
4
  from dataclasses import dataclass
3
5
 
6
+ from openfeature.event import ProviderEventDetails
4
7
  from openfeature.exception import ParseError
5
8
 
6
9
 
10
+ class FlagStore:
11
+ def __init__(
12
+ self,
13
+ emit_provider_configuration_changed: typing.Callable[
14
+ [ProviderEventDetails], None
15
+ ],
16
+ ):
17
+ self.emit_provider_configuration_changed = emit_provider_configuration_changed
18
+ self.flags: typing.Mapping[str, Flag] = {}
19
+
20
+ def get_flag(self, key: str) -> typing.Optional["Flag"]:
21
+ return self.flags.get(key)
22
+
23
+ def update(self, flags_data: dict) -> None:
24
+ flags = flags_data.get("flags", {})
25
+ evaluators: typing.Optional[dict] = flags_data.get("$evaluators")
26
+ if evaluators:
27
+ transposed = json.dumps(flags)
28
+ for name, rule in evaluators.items():
29
+ transposed = re.sub(
30
+ rf"{{\s*\"\$ref\":\s*\"{name}\"\s*}}", json.dumps(rule), transposed
31
+ )
32
+ flags = json.loads(transposed)
33
+
34
+ if not isinstance(flags, dict):
35
+ raise ParseError("`flags` key of configuration must be a dictionary")
36
+ self.flags = {key: Flag.from_dict(key, data) for key, data in flags.items()}
37
+
38
+ self.emit_provider_configuration_changed(
39
+ ProviderEventDetails(flags_changed=list(self.flags.keys()))
40
+ )
41
+
42
+
7
43
  @dataclass
8
44
  class Flag:
9
45
  key: str
@@ -32,19 +68,27 @@ class Flag:
32
68
 
33
69
  @classmethod
34
70
  def from_dict(cls, key: str, data: dict) -> "Flag":
35
- data["default_variant"] = data["defaultVariant"]
36
- del data["defaultVariant"]
37
- flag = cls(key=key, **data)
71
+ if "defaultVariant" in data:
72
+ data["default_variant"] = data["defaultVariant"]
73
+ del data["defaultVariant"]
38
74
 
39
- return flag
75
+ if "source" in data:
76
+ del data["source"]
77
+ if "selector" in data:
78
+ del data["selector"]
79
+ try:
80
+ flag = cls(key=key, **data)
81
+ return flag
82
+ except Exception as err:
83
+ raise ParseError from err
40
84
 
41
85
  @property
42
- def default(self) -> typing.Tuple[str, typing.Any]:
86
+ def default(self) -> tuple[str, typing.Any]:
43
87
  return self.get_variant(self.default_variant)
44
88
 
45
89
  def get_variant(
46
90
  self, variant_key: typing.Union[str, bool]
47
- ) -> typing.Tuple[str, typing.Any]:
91
+ ) -> tuple[str, typing.Any]:
48
92
  if isinstance(variant_key, bool):
49
93
  variant_key = str(variant_key).lower()
50
94
 
@@ -0,0 +1,35 @@
1
+ import time
2
+ import typing
3
+
4
+ from json_logic import builtins, jsonLogic
5
+ from json_logic.types import JsonValue
6
+
7
+ from openfeature.evaluation_context import EvaluationContext
8
+
9
+ from .custom_ops import (
10
+ ends_with,
11
+ fractional,
12
+ sem_ver,
13
+ starts_with,
14
+ )
15
+
16
+ OPERATORS = {
17
+ **builtins.BUILTINS,
18
+ "fractional": fractional,
19
+ "starts_with": starts_with,
20
+ "ends_with": ends_with,
21
+ "sem_ver": sem_ver,
22
+ }
23
+
24
+
25
+ def targeting(
26
+ key: str,
27
+ targeting: dict,
28
+ evaluation_context: typing.Optional[EvaluationContext] = None,
29
+ ) -> JsonValue:
30
+ json_logic_context = evaluation_context.attributes if evaluation_context else {}
31
+ json_logic_context["$flagd"] = {"flagKey": key, "timestamp": int(time.time())}
32
+ json_logic_context["targetingKey"] = (
33
+ evaluation_context.targeting_key if evaluation_context else None
34
+ )
35
+ return jsonLogic(targeting, json_logic_context, OPERATORS)
@@ -0,0 +1,47 @@
1
+ import typing
2
+
3
+ from typing_extensions import Protocol
4
+
5
+ from openfeature.evaluation_context import EvaluationContext
6
+ from openfeature.flag_evaluation import FlagResolutionDetails
7
+
8
+
9
+ class AbstractResolver(Protocol):
10
+ def initialize(self, evaluation_context: EvaluationContext) -> None: ...
11
+
12
+ def shutdown(self) -> None: ...
13
+
14
+ def resolve_boolean_details(
15
+ self,
16
+ key: str,
17
+ default_value: bool,
18
+ evaluation_context: typing.Optional[EvaluationContext] = None,
19
+ ) -> FlagResolutionDetails[bool]: ...
20
+
21
+ def resolve_string_details(
22
+ self,
23
+ key: str,
24
+ default_value: str,
25
+ evaluation_context: typing.Optional[EvaluationContext] = None,
26
+ ) -> FlagResolutionDetails[str]: ...
27
+
28
+ def resolve_float_details(
29
+ self,
30
+ key: str,
31
+ default_value: float,
32
+ evaluation_context: typing.Optional[EvaluationContext] = None,
33
+ ) -> FlagResolutionDetails[float]: ...
34
+
35
+ def resolve_integer_details(
36
+ self,
37
+ key: str,
38
+ default_value: int,
39
+ evaluation_context: typing.Optional[EvaluationContext] = None,
40
+ ) -> FlagResolutionDetails[int]: ...
41
+
42
+ def resolve_object_details(
43
+ self,
44
+ key: str,
45
+ default_value: typing.Union[dict, list],
46
+ evaluation_context: typing.Optional[EvaluationContext] = None,
47
+ ) -> FlagResolutionDetails[typing.Union[dict, list]]: ...
@@ -0,0 +1,72 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto
5
+ # Protobuf Python Version: 5.29.0
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 5,
15
+ 29,
16
+ 0,
17
+ '',
18
+ 'openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+ from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
26
+
27
+
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\nAopenfeature/schemas/protobuf/flagd/evaluation/v1/evaluation.proto\x12\x13\x66lagd.evaluation.v1\x1a\x1cgoogle/protobuf/struct.proto\"=\n\x11ResolveAllRequest\x12(\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xa3\x01\n\x12ResolveAllResponse\x12\x41\n\x05\x66lags\x18\x01 \x03(\x0b\x32\x32.flagd.evaluation.v1.ResolveAllResponse.FlagsEntry\x1aJ\n\nFlagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05value\x18\x02 \x01(\x0b\x32\x1c.flagd.evaluation.v1.AnyFlag:\x02\x38\x01\"\xaa\x01\n\x07\x41nyFlag\x12\x0e\n\x06reason\x18\x01 \x01(\t\x12\x0f\n\x07variant\x18\x02 \x01(\t\x12\x14\n\nbool_value\x18\x03 \x01(\x08H\x00\x12\x16\n\x0cstring_value\x18\x04 \x01(\tH\x00\x12\x16\n\x0c\x64ouble_value\x18\x05 \x01(\x01H\x00\x12/\n\x0cobject_value\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x42\x07\n\x05value\"S\n\x15ResolveBooleanRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"s\n\x16ResolveBooleanResponse\x12\r\n\x05value\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"R\n\x14ResolveStringRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"r\n\x15ResolveStringResponse\x12\r\n\x05value\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"Q\n\x13ResolveFloatRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"q\n\x14ResolveFloatResponse\x12\r\n\x05value\x18\x01 \x01(\x01\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"O\n\x11ResolveIntRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"o\n\x12ResolveIntResponse\x12\r\n\x05value\x18\x01 \x01(\x03\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"R\n\x14ResolveObjectRequest\x12\x10\n\x08\x66lag_key\x18\x01 \x01(\t\x12(\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x8b\x01\n\x15ResolveObjectResponse\x12&\n\x05value\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x0f\n\x07variant\x18\x03 \x01(\t\x12)\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"J\n\x13\x45ventStreamResponse\x12\x0c\n\x04type\x18\x01 \x01(\t\x12%\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x14\n\x12\x45ventStreamRequest2\xd9\x05\n\x07Service\x12_\n\nResolveAll\x12&.flagd.evaluation.v1.ResolveAllRequest\x1a\'.flagd.evaluation.v1.ResolveAllResponse\"\x00\x12k\n\x0eResolveBoolean\x12*.flagd.evaluation.v1.ResolveBooleanRequest\x1a+.flagd.evaluation.v1.ResolveBooleanResponse\"\x00\x12h\n\rResolveString\x12).flagd.evaluation.v1.ResolveStringRequest\x1a*.flagd.evaluation.v1.ResolveStringResponse\"\x00\x12\x65\n\x0cResolveFloat\x12(.flagd.evaluation.v1.ResolveFloatRequest\x1a).flagd.evaluation.v1.ResolveFloatResponse\"\x00\x12_\n\nResolveInt\x12&.flagd.evaluation.v1.ResolveIntRequest\x1a\'.flagd.evaluation.v1.ResolveIntResponse\"\x00\x12h\n\rResolveObject\x12).flagd.evaluation.v1.ResolveObjectRequest\x1a*.flagd.evaluation.v1.ResolveObjectResponse\"\x00\x12\x64\n\x0b\x45ventStream\x12\'.flagd.evaluation.v1.EventStreamRequest\x1a(.flagd.evaluation.v1.EventStreamResponse\"\x00\x30\x01\x42\xc6\x01\n%dev.openfeature.flagd.grpc.evaluationZ\x13\x66lagd/evaluation/v1\xaa\x02!OpenFeature.Flagd.Grpc.Evaluation\xca\x02\x32OpenFeature\\Providers\\Flagd\\Schema\\Grpc\\Evaluation\xea\x02.OpenFeature::Flagd::Provider::Grpc::Evaluationb\x06proto3')
29
+
30
+ _globals = globals()
31
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'openfeature.schemas.protobuf.flagd.evaluation.v1.evaluation_pb2', _globals)
33
+ if not _descriptor._USE_C_DESCRIPTORS:
34
+ _globals['DESCRIPTOR']._loaded_options = None
35
+ _globals['DESCRIPTOR']._serialized_options = b'\n%dev.openfeature.flagd.grpc.evaluationZ\023flagd/evaluation/v1\252\002!OpenFeature.Flagd.Grpc.Evaluation\312\0022OpenFeature\\Providers\\Flagd\\Schema\\Grpc\\Evaluation\352\002.OpenFeature::Flagd::Provider::Grpc::Evaluation'
36
+ _globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._loaded_options = None
37
+ _globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_options = b'8\001'
38
+ _globals['_RESOLVEALLREQUEST']._serialized_start=120
39
+ _globals['_RESOLVEALLREQUEST']._serialized_end=181
40
+ _globals['_RESOLVEALLRESPONSE']._serialized_start=184
41
+ _globals['_RESOLVEALLRESPONSE']._serialized_end=347
42
+ _globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_start=273
43
+ _globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_end=347
44
+ _globals['_ANYFLAG']._serialized_start=350
45
+ _globals['_ANYFLAG']._serialized_end=520
46
+ _globals['_RESOLVEBOOLEANREQUEST']._serialized_start=522
47
+ _globals['_RESOLVEBOOLEANREQUEST']._serialized_end=605
48
+ _globals['_RESOLVEBOOLEANRESPONSE']._serialized_start=607
49
+ _globals['_RESOLVEBOOLEANRESPONSE']._serialized_end=722
50
+ _globals['_RESOLVESTRINGREQUEST']._serialized_start=724
51
+ _globals['_RESOLVESTRINGREQUEST']._serialized_end=806
52
+ _globals['_RESOLVESTRINGRESPONSE']._serialized_start=808
53
+ _globals['_RESOLVESTRINGRESPONSE']._serialized_end=922
54
+ _globals['_RESOLVEFLOATREQUEST']._serialized_start=924
55
+ _globals['_RESOLVEFLOATREQUEST']._serialized_end=1005
56
+ _globals['_RESOLVEFLOATRESPONSE']._serialized_start=1007
57
+ _globals['_RESOLVEFLOATRESPONSE']._serialized_end=1120
58
+ _globals['_RESOLVEINTREQUEST']._serialized_start=1122
59
+ _globals['_RESOLVEINTREQUEST']._serialized_end=1201
60
+ _globals['_RESOLVEINTRESPONSE']._serialized_start=1203
61
+ _globals['_RESOLVEINTRESPONSE']._serialized_end=1314
62
+ _globals['_RESOLVEOBJECTREQUEST']._serialized_start=1316
63
+ _globals['_RESOLVEOBJECTREQUEST']._serialized_end=1398
64
+ _globals['_RESOLVEOBJECTRESPONSE']._serialized_start=1401
65
+ _globals['_RESOLVEOBJECTRESPONSE']._serialized_end=1540
66
+ _globals['_EVENTSTREAMRESPONSE']._serialized_start=1542
67
+ _globals['_EVENTSTREAMRESPONSE']._serialized_end=1616
68
+ _globals['_EVENTSTREAMREQUEST']._serialized_start=1618
69
+ _globals['_EVENTSTREAMREQUEST']._serialized_end=1638
70
+ _globals['_SERVICE']._serialized_start=1641
71
+ _globals['_SERVICE']._serialized_end=2370
72
+ # @@protoc_insertion_point(module_scope)