From 62cda8e5f8fbabb78953b8375ceb0b836d917cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Gr=C3=BCbel?= Date: Wed, 27 Nov 2024 14:29:26 +0100 Subject: [PATCH 1/7] build: auto generate proto files from schema (#115) build(flagd): auto generate proto files from schema Signed-off-by: gruebel Co-authored-by: Simon Schrottner --- .github/workflows/build.yml | 17 +- .gitmodules | 6 +- .pre-commit-config.yaml | 2 +- CONTRIBUTING.md | 10 +- mypy.ini | 2 +- .../openfeature/schemas | 1 + .../{ => openfeature}/spec | 0 .../{ => openfeature}/test-harness | 0 .../openfeature-provider-flagd/pyproject.toml | 78 ++++- providers/openfeature-provider-flagd/schemas | 1 - .../scripts/gen_protos.sh | 10 - .../src/openfeature/.gitignore | 2 + .../flagd/evaluation/v1/evaluation_pb2.py | 62 ---- .../evaluation/v1/evaluation_pb2_grpc.py | 267 ------------------ .../flagd/proto/flagd/sync/v1/sync_pb2.py | 40 --- .../proto/flagd/sync/v1/sync_pb2_grpc.py | 135 --------- .../flagd/proto/schema/v1/schema_pb2.py | 62 ---- .../flagd/proto/schema/v1/schema_pb2_grpc.py | 267 ------------------ .../flagd/proto/sync/v1/sync_service_pb2.py | 37 --- .../proto/sync/v1/sync_service_pb2_grpc.py | 102 ------- .../contrib/provider/flagd/resolvers/grpc.py | 21 +- .../flagd/resolvers/process/targeting.py | 4 +- .../tests/e2e/conftest.py | 3 + .../tests/e2e/test_in-process-file.py | 11 +- .../tests/e2e/test_rpc.py | 9 +- ruff.toml | 2 +- 26 files changed, 128 insertions(+), 1023 deletions(-) create mode 160000 providers/openfeature-provider-flagd/openfeature/schemas rename providers/openfeature-provider-flagd/{ => openfeature}/spec (100%) rename providers/openfeature-provider-flagd/{ => openfeature}/test-harness (100%) delete mode 160000 providers/openfeature-provider-flagd/schemas delete mode 100755 providers/openfeature-provider-flagd/scripts/gen_protos.sh create mode 100644 providers/openfeature-provider-flagd/src/openfeature/.gitignore delete mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2.py delete mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2_grpc.py delete mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2.py delete mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2_grpc.py delete mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2.py delete mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2_grpc.py delete mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2.py delete mode 100644 providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2_grpc.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67169206..28f96c45 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,16 +38,21 @@ jobs: python-version: ${{ matrix.python-version }} cache: "pip" - - uses: bufbuild/buf-action@v1 - with: - github_token: ${{ github.token }} - setup_only: true - - name: Install hatch run: pip install hatch + - name: Building first to generate files + run: hatch build + working-directory: ${{ matrix.package }} + + - name: Type checking + # TODO: migrate other packages to use their own 'mypy' setup + if: matrix.python-version == '3.11' && matrix.package == 'providers/openfeature-provider-flagd' + working-directory: ${{ matrix.package }} + run: hatch run mypy:run + - name: Test with pytest - run: hatch run cov + run: hatch test -c working-directory: ${{ matrix.package }} - if: matrix.python-version == '3.11' diff --git a/.gitmodules b/.gitmodules index fe96453f..466f2596 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "schemas"] - path = providers/openfeature-provider-flagd/schemas + path = providers/openfeature-provider-flagd/openfeature/schemas url = https://github.com/open-feature/schemas [submodule "providers/openfeature-provider-flagd/test-harness"] - path = providers/openfeature-provider-flagd/test-harness + path = providers/openfeature-provider-flagd/openfeature/test-harness url = git@github.com:open-feature/flagd-testbed.git [submodule "providers/openfeature-provider-flagd/spec"] - path = providers/openfeature-provider-flagd/spec + path = providers/openfeature-provider-flagd/openfeature/spec url = https://github.com/open-feature/spec diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d2ec28d6..4f8a6a85 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,4 +29,4 @@ repos: - mmh3 - semver - panzi-json-logic - exclude: proto|tests + exclude: providers/openfeature-provider-flagd|tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f9c9096..8db34176 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,9 +18,13 @@ To install Hatch, just run `pip install hatch`. You will also need to setup the `pre-commit` hooks. Run `pre-commit install` in the root directory of the repository. If you don't have `pre-commit` installed, you can install it with `pip install pre-commit`. +> **Note** +> Currently our protobuf files will be generated during `hatch build` +> Please run this command once, to generate all necessary files. + ### Testing -Run tests by entering the package directory and running `hatch run test`. +Run tests by entering the package directory and running `hatch test`. We use `pytest` for our unit testing, making use of `parametrized` to inject cases at scale. @@ -28,6 +32,10 @@ We use `pytest` for our unit testing, making use of `parametrized` to inject cas These are planned once the SDK has been stabilized and a Flagd provider implemented. At that point, we will utilize the [gherkin integration tests](https://github.com/open-feature/test-harness/blob/main/features/evaluation.feature) to validate against a live, seeded Flagd instance. +### Type checking + +Run `mypy` by entering the package directory and running `hatch run mypy:run`. + ## Pull Request All contributions to the OpenFeature project are welcome via GitHub pull requests. diff --git a/mypy.ini b/mypy.ini index 4c24b416..2c54f1ab 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,5 @@ [mypy] -files = hooks,providers +files = hooks,providers/openfeature-provider-ofrep exclude = proto|tests untyped_calls_exclude = flagd.proto diff --git a/providers/openfeature-provider-flagd/openfeature/schemas b/providers/openfeature-provider-flagd/openfeature/schemas new file mode 160000 index 00000000..76d611fd --- /dev/null +++ b/providers/openfeature-provider-flagd/openfeature/schemas @@ -0,0 +1 @@ +Subproject commit 76d611fd94689d906af316105ac12670d40f7648 diff --git a/providers/openfeature-provider-flagd/spec b/providers/openfeature-provider-flagd/openfeature/spec similarity index 100% rename from providers/openfeature-provider-flagd/spec rename to providers/openfeature-provider-flagd/openfeature/spec diff --git a/providers/openfeature-provider-flagd/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness similarity index 100% rename from providers/openfeature-provider-flagd/test-harness rename to providers/openfeature-provider-flagd/openfeature/test-harness diff --git a/providers/openfeature-provider-flagd/pyproject.toml b/providers/openfeature-provider-flagd/pyproject.toml index 5da8c69c..696dfd00 100644 --- a/providers/openfeature-provider-flagd/pyproject.toml +++ b/providers/openfeature-provider-flagd/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ ] keywords = [] dependencies = [ - "openfeature-sdk>=0.4.0", + "openfeature-sdk>=0.6.0", "grpcio>=1.60.0", "protobuf>=4.25.2", "mmh3>=4.1.0", @@ -32,7 +32,7 @@ Homepage = "https://github.com/open-feature/python-sdk-contrib" [tool.hatch] -[tool.hatch.envs.default] +[tool.hatch.envs.hatch-test] dependencies = [ "coverage[toml]>=6.5", "pytest", @@ -41,26 +41,60 @@ dependencies = [ "asserts", "grpcio-health-checking==1.60.0", ] -post-install-commands = [ - "./scripts/gen_protos.sh" +pre-install-commands = [ + "hatch build", ] -[tool.hatch.envs.default.scripts] -test = "pytest {args:tests}" -test-cov = "coverage run -m pytest {args:tests}" +[tool.hatch.envs.hatch-test.scripts] +run = "pytest {args:tests}" +run-cov = "coverage run -m pytest {args:tests}" +cov-combine = "coverage combine" cov-report = [ "coverage xml", "coverage html", + "coverage report", ] cov = [ "test-cov", "cov-report", ] +[tool.hatch.envs.mypy] +dependencies = [ + "mypy[faster-cache]>=1.13.0", + "types-protobuf", + "types-pyyaml", +] +pre-install-commands = [ + "hatch build", +] + +[tool.hatch.envs.mypy.scripts] +run = "mypy" + +[tool.hatch.build.hooks.protobuf] +generate_pyi = false +dependencies = [ + "hatch-protobuf", + "mypy-protobuf~=3.0", +] +proto_paths = [ + ".", +] +output_path = "src/" + +[[tool.hatch.build.hooks.protobuf.generators]] +name = "mypy" +outputs = ["{proto_path}/{proto_name}_pb2.pyi"] + +[[tool.hatch.build.hooks.protobuf.generators]] +name = "mypy_grpc" +outputs = ["{proto_path}/{proto_name}_pb2_grpc.pyi"] + [tool.hatch.build.targets.sdist] exclude = [ ".gitignore", - "schemas", + "/openfeature", ] [tool.hatch.build.targets.wheel] @@ -69,6 +103,32 @@ packages = ["src/openfeature"] [tool.coverage.run] omit = [ # exclude generated files - "src/openfeature/contrib/provider/flagd/proto/*", + "src/openfeature/schemas/*", "tests/**", ] + +[tool.mypy] +mypy_path = "src" +files = "src" + +python_version = "3.8" # should be identical to the minimum supported version +namespace_packages = true +explicit_package_bases = true +local_partial_types = true +pretty = true + +strict = true +disallow_any_generics = false + +[[tool.mypy.overrides]] +module = [ + "grpc.*", + "json_logic.*", +] +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = [ + "openfeature.schemas.*" +] +warn_unused_ignores = false diff --git a/providers/openfeature-provider-flagd/schemas b/providers/openfeature-provider-flagd/schemas deleted file mode 160000 index d897c245..00000000 --- a/providers/openfeature-provider-flagd/schemas +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d897c24594784c242358bc1817d1318aba7b3b28 diff --git a/providers/openfeature-provider-flagd/scripts/gen_protos.sh b/providers/openfeature-provider-flagd/scripts/gen_protos.sh deleted file mode 100755 index 5c7c5fb2..00000000 --- a/providers/openfeature-provider-flagd/scripts/gen_protos.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -e - -buf generate buf.build/open-feature/flagd --template schemas/protobuf/buf.gen.python.yaml --output schemas -rm -rf openfeature/contrib/provider/flagd/proto -sed -i.bak 's/^from schema.v1 import/from . import/' proto/python/schema/v1/*.py -rm proto/python/schema/v1/*.bak -mv proto/python src/openfeature/contrib/provider/flagd/proto -rmdir proto diff --git a/providers/openfeature-provider-flagd/src/openfeature/.gitignore b/providers/openfeature-provider-flagd/src/openfeature/.gitignore new file mode 100644 index 00000000..6fbcf5b9 --- /dev/null +++ b/providers/openfeature-provider-flagd/src/openfeature/.gitignore @@ -0,0 +1,2 @@ + +schemas \ No newline at end of file diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2.py deleted file mode 100644 index addd44aa..00000000 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: flagd/evaluation/v1/evaluation.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$flagd/evaluation/v1/evaluation.proto\x12\x13\x66lagd.evaluation.v1\x1a\x1cgoogle/protobuf/struct.proto\"F\n\x11ResolveAllRequest\x12\x31\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\xb6\x01\n\x12ResolveAllResponse\x12H\n\x05\x66lags\x18\x01 \x03(\x0b\x32\x32.flagd.evaluation.v1.ResolveAllResponse.FlagsEntryR\x05\x66lags\x1aV\n\nFlagsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x32\n\x05value\x18\x02 \x01(\x0b\x32\x1c.flagd.evaluation.v1.AnyFlagR\x05value:\x02\x38\x01\"\xed\x01\n\x07\x41nyFlag\x12\x16\n\x06reason\x18\x01 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x02 \x01(\tR\x07variant\x12\x1f\n\nbool_value\x18\x03 \x01(\x08H\x00R\tboolValue\x12#\n\x0cstring_value\x18\x04 \x01(\tH\x00R\x0bstringValue\x12#\n\x0c\x64ouble_value\x18\x05 \x01(\x01H\x00R\x0b\x64oubleValue\x12<\n\x0cobject_value\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructH\x00R\x0bobjectValueB\x07\n\x05value\"e\n\x15ResolveBooleanRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\x95\x01\n\x16ResolveBooleanResponse\x12\x14\n\x05value\x18\x01 \x01(\x08R\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"d\n\x14ResolveStringRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\x94\x01\n\x15ResolveStringResponse\x12\x14\n\x05value\x18\x01 \x01(\tR\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"c\n\x13ResolveFloatRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\x93\x01\n\x14ResolveFloatResponse\x12\x14\n\x05value\x18\x01 \x01(\x01R\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"a\n\x11ResolveIntRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\x91\x01\n\x12ResolveIntResponse\x12\x14\n\x05value\x18\x01 \x01(\x03R\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"d\n\x14ResolveObjectRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\xad\x01\n\x15ResolveObjectResponse\x12-\n\x05value\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"V\n\x13\x45ventStreamResponse\x12\x12\n\x04type\x18\x01 \x01(\tR\x04type\x12+\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x04\x64\x61ta\"\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\xad\x01\n\x17\x63om.flagd.evaluation.v1B\x0f\x45valuationProtoP\x01Z\x13\x66lagd/evaluation/v1\xa2\x02\x03\x46\x45X\xaa\x02\x13\x46lagd.Evaluation.V1\xca\x02\x13\x46lagd\\Evaluation\\V1\xe2\x02\x1f\x46lagd\\Evaluation\\V1\\GPBMetadata\xea\x02\x15\x46lagd::Evaluation::V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flagd.evaluation.v1.evaluation_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\027com.flagd.evaluation.v1B\017EvaluationProtoP\001Z\023flagd/evaluation/v1\242\002\003FEX\252\002\023Flagd.Evaluation.V1\312\002\023Flagd\\Evaluation\\V1\342\002\037Flagd\\Evaluation\\V1\\GPBMetadata\352\002\025Flagd::Evaluation::V1' - _RESOLVEALLRESPONSE_FLAGSENTRY._options = None - _RESOLVEALLRESPONSE_FLAGSENTRY._serialized_options = b'8\001' - _globals['_RESOLVEALLREQUEST']._serialized_start=91 - _globals['_RESOLVEALLREQUEST']._serialized_end=161 - _globals['_RESOLVEALLRESPONSE']._serialized_start=164 - _globals['_RESOLVEALLRESPONSE']._serialized_end=346 - _globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_start=260 - _globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_end=346 - _globals['_ANYFLAG']._serialized_start=349 - _globals['_ANYFLAG']._serialized_end=586 - _globals['_RESOLVEBOOLEANREQUEST']._serialized_start=588 - _globals['_RESOLVEBOOLEANREQUEST']._serialized_end=689 - _globals['_RESOLVEBOOLEANRESPONSE']._serialized_start=692 - _globals['_RESOLVEBOOLEANRESPONSE']._serialized_end=841 - _globals['_RESOLVESTRINGREQUEST']._serialized_start=843 - _globals['_RESOLVESTRINGREQUEST']._serialized_end=943 - _globals['_RESOLVESTRINGRESPONSE']._serialized_start=946 - _globals['_RESOLVESTRINGRESPONSE']._serialized_end=1094 - _globals['_RESOLVEFLOATREQUEST']._serialized_start=1096 - _globals['_RESOLVEFLOATREQUEST']._serialized_end=1195 - _globals['_RESOLVEFLOATRESPONSE']._serialized_start=1198 - _globals['_RESOLVEFLOATRESPONSE']._serialized_end=1345 - _globals['_RESOLVEINTREQUEST']._serialized_start=1347 - _globals['_RESOLVEINTREQUEST']._serialized_end=1444 - _globals['_RESOLVEINTRESPONSE']._serialized_start=1447 - _globals['_RESOLVEINTRESPONSE']._serialized_end=1592 - _globals['_RESOLVEOBJECTREQUEST']._serialized_start=1594 - _globals['_RESOLVEOBJECTREQUEST']._serialized_end=1694 - _globals['_RESOLVEOBJECTRESPONSE']._serialized_start=1697 - _globals['_RESOLVEOBJECTRESPONSE']._serialized_end=1870 - _globals['_EVENTSTREAMRESPONSE']._serialized_start=1872 - _globals['_EVENTSTREAMRESPONSE']._serialized_end=1958 - _globals['_EVENTSTREAMREQUEST']._serialized_start=1960 - _globals['_EVENTSTREAMREQUEST']._serialized_end=1980 - _globals['_SERVICE']._serialized_start=1983 - _globals['_SERVICE']._serialized_end=2712 -# @@protoc_insertion_point(module_scope) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2_grpc.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2_grpc.py deleted file mode 100644 index 299a004b..00000000 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/evaluation/v1/evaluation_pb2_grpc.py +++ /dev/null @@ -1,267 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from flagd.evaluation.v1 import evaluation_pb2 as flagd_dot_evaluation_dot_v1_dot_evaluation__pb2 - - -class ServiceStub(object): - """Service defines the exposed rpcs of flagd - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.ResolveAll = channel.unary_unary( - '/flagd.evaluation.v1.Service/ResolveAll', - request_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveAllRequest.SerializeToString, - response_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveAllResponse.FromString, - ) - self.ResolveBoolean = channel.unary_unary( - '/flagd.evaluation.v1.Service/ResolveBoolean', - request_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveBooleanRequest.SerializeToString, - response_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveBooleanResponse.FromString, - ) - self.ResolveString = channel.unary_unary( - '/flagd.evaluation.v1.Service/ResolveString', - request_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveStringRequest.SerializeToString, - response_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveStringResponse.FromString, - ) - self.ResolveFloat = channel.unary_unary( - '/flagd.evaluation.v1.Service/ResolveFloat', - request_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveFloatRequest.SerializeToString, - response_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveFloatResponse.FromString, - ) - self.ResolveInt = channel.unary_unary( - '/flagd.evaluation.v1.Service/ResolveInt', - request_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveIntRequest.SerializeToString, - response_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveIntResponse.FromString, - ) - self.ResolveObject = channel.unary_unary( - '/flagd.evaluation.v1.Service/ResolveObject', - request_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveObjectRequest.SerializeToString, - response_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveObjectResponse.FromString, - ) - self.EventStream = channel.unary_stream( - '/flagd.evaluation.v1.Service/EventStream', - request_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.EventStreamRequest.SerializeToString, - response_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.EventStreamResponse.FromString, - ) - - -class ServiceServicer(object): - """Service defines the exposed rpcs of flagd - """ - - def ResolveAll(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveBoolean(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveString(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveFloat(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveInt(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveObject(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def EventStream(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'ResolveAll': grpc.unary_unary_rpc_method_handler( - servicer.ResolveAll, - request_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveAllRequest.FromString, - response_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveAllResponse.SerializeToString, - ), - 'ResolveBoolean': grpc.unary_unary_rpc_method_handler( - servicer.ResolveBoolean, - request_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveBooleanRequest.FromString, - response_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveBooleanResponse.SerializeToString, - ), - 'ResolveString': grpc.unary_unary_rpc_method_handler( - servicer.ResolveString, - request_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveStringRequest.FromString, - response_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveStringResponse.SerializeToString, - ), - 'ResolveFloat': grpc.unary_unary_rpc_method_handler( - servicer.ResolveFloat, - request_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveFloatRequest.FromString, - response_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveFloatResponse.SerializeToString, - ), - 'ResolveInt': grpc.unary_unary_rpc_method_handler( - servicer.ResolveInt, - request_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveIntRequest.FromString, - response_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveIntResponse.SerializeToString, - ), - 'ResolveObject': grpc.unary_unary_rpc_method_handler( - servicer.ResolveObject, - request_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveObjectRequest.FromString, - response_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveObjectResponse.SerializeToString, - ), - 'EventStream': grpc.unary_stream_rpc_method_handler( - servicer.EventStream, - request_deserializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.EventStreamRequest.FromString, - response_serializer=flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.EventStreamResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'flagd.evaluation.v1.Service', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class Service(object): - """Service defines the exposed rpcs of flagd - """ - - @staticmethod - def ResolveAll(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/flagd.evaluation.v1.Service/ResolveAll', - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveAllRequest.SerializeToString, - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveAllResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveBoolean(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/flagd.evaluation.v1.Service/ResolveBoolean', - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveBooleanRequest.SerializeToString, - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveBooleanResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveString(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/flagd.evaluation.v1.Service/ResolveString', - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveStringRequest.SerializeToString, - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveStringResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveFloat(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/flagd.evaluation.v1.Service/ResolveFloat', - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveFloatRequest.SerializeToString, - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveFloatResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveInt(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/flagd.evaluation.v1.Service/ResolveInt', - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveIntRequest.SerializeToString, - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveIntResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveObject(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/flagd.evaluation.v1.Service/ResolveObject', - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveObjectRequest.SerializeToString, - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.ResolveObjectResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def EventStream(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/flagd.evaluation.v1.Service/EventStream', - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.EventStreamRequest.SerializeToString, - flagd_dot_evaluation_dot_v1_dot_evaluation__pb2.EventStreamResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2.py deleted file mode 100644 index 89345624..00000000 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: flagd/sync/v1/sync.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lagd/sync/v1/sync.proto\x12\rflagd.sync.v1\x1a\x1cgoogle/protobuf/struct.proto\"O\n\x10SyncFlagsRequest\x12\x1f\n\x0bprovider_id\x18\x01 \x01(\tR\nproviderId\x12\x1a\n\x08selector\x18\x02 \x01(\tR\x08selector\"B\n\x11SyncFlagsResponse\x12-\n\x12\x66lag_configuration\x18\x01 \x01(\tR\x11\x66lagConfiguration\"S\n\x14\x46\x65tchAllFlagsRequest\x12\x1f\n\x0bprovider_id\x18\x01 \x01(\tR\nproviderId\x12\x1a\n\x08selector\x18\x02 \x01(\tR\x08selector\"F\n\x15\x46\x65tchAllFlagsResponse\x12-\n\x12\x66lag_configuration\x18\x01 \x01(\tR\x11\x66lagConfiguration\"\x14\n\x12GetMetadataRequest\"P\n\x13GetMetadataResponse\x12\x33\n\x08metadata\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadataJ\x04\x08\x01\x10\x02\x32\x9b\x02\n\x0f\x46lagSyncService\x12R\n\tSyncFlags\x12\x1f.flagd.sync.v1.SyncFlagsRequest\x1a .flagd.sync.v1.SyncFlagsResponse\"\x00\x30\x01\x12\\\n\rFetchAllFlags\x12#.flagd.sync.v1.FetchAllFlagsRequest\x1a$.flagd.sync.v1.FetchAllFlagsResponse\"\x00\x12V\n\x0bGetMetadata\x12!.flagd.sync.v1.GetMetadataRequest\x1a\".flagd.sync.v1.GetMetadataResponse\"\x00\x42\x83\x01\n\x11\x63om.flagd.sync.v1B\tSyncProtoP\x01Z\rflagd/sync/v1\xa2\x02\x03\x46SX\xaa\x02\rFlagd.Sync.V1\xca\x02\rFlagd\\Sync\\V1\xe2\x02\x19\x46lagd\\Sync\\V1\\GPBMetadata\xea\x02\x0f\x46lagd::Sync::V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flagd.sync.v1.sync_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\021com.flagd.sync.v1B\tSyncProtoP\001Z\rflagd/sync/v1\242\002\003FSX\252\002\rFlagd.Sync.V1\312\002\rFlagd\\Sync\\V1\342\002\031Flagd\\Sync\\V1\\GPBMetadata\352\002\017Flagd::Sync::V1' - _globals['_SYNCFLAGSREQUEST']._serialized_start=73 - _globals['_SYNCFLAGSREQUEST']._serialized_end=152 - _globals['_SYNCFLAGSRESPONSE']._serialized_start=154 - _globals['_SYNCFLAGSRESPONSE']._serialized_end=220 - _globals['_FETCHALLFLAGSREQUEST']._serialized_start=222 - _globals['_FETCHALLFLAGSREQUEST']._serialized_end=305 - _globals['_FETCHALLFLAGSRESPONSE']._serialized_start=307 - _globals['_FETCHALLFLAGSRESPONSE']._serialized_end=377 - _globals['_GETMETADATAREQUEST']._serialized_start=379 - _globals['_GETMETADATAREQUEST']._serialized_end=399 - _globals['_GETMETADATARESPONSE']._serialized_start=401 - _globals['_GETMETADATARESPONSE']._serialized_end=481 - _globals['_FLAGSYNCSERVICE']._serialized_start=484 - _globals['_FLAGSYNCSERVICE']._serialized_end=767 -# @@protoc_insertion_point(module_scope) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2_grpc.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2_grpc.py deleted file mode 100644 index ce040715..00000000 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/flagd/sync/v1/sync_pb2_grpc.py +++ /dev/null @@ -1,135 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from flagd.sync.v1 import sync_pb2 as flagd_dot_sync_dot_v1_dot_sync__pb2 - - -class FlagSyncServiceStub(object): - """FlagService implements a server streaming to provide realtime flag configurations - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.SyncFlags = channel.unary_stream( - '/flagd.sync.v1.FlagSyncService/SyncFlags', - request_serializer=flagd_dot_sync_dot_v1_dot_sync__pb2.SyncFlagsRequest.SerializeToString, - response_deserializer=flagd_dot_sync_dot_v1_dot_sync__pb2.SyncFlagsResponse.FromString, - ) - self.FetchAllFlags = channel.unary_unary( - '/flagd.sync.v1.FlagSyncService/FetchAllFlags', - request_serializer=flagd_dot_sync_dot_v1_dot_sync__pb2.FetchAllFlagsRequest.SerializeToString, - response_deserializer=flagd_dot_sync_dot_v1_dot_sync__pb2.FetchAllFlagsResponse.FromString, - ) - self.GetMetadata = channel.unary_unary( - '/flagd.sync.v1.FlagSyncService/GetMetadata', - request_serializer=flagd_dot_sync_dot_v1_dot_sync__pb2.GetMetadataRequest.SerializeToString, - response_deserializer=flagd_dot_sync_dot_v1_dot_sync__pb2.GetMetadataResponse.FromString, - ) - - -class FlagSyncServiceServicer(object): - """FlagService implements a server streaming to provide realtime flag configurations - """ - - def SyncFlags(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def FetchAllFlags(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetMetadata(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_FlagSyncServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'SyncFlags': grpc.unary_stream_rpc_method_handler( - servicer.SyncFlags, - request_deserializer=flagd_dot_sync_dot_v1_dot_sync__pb2.SyncFlagsRequest.FromString, - response_serializer=flagd_dot_sync_dot_v1_dot_sync__pb2.SyncFlagsResponse.SerializeToString, - ), - 'FetchAllFlags': grpc.unary_unary_rpc_method_handler( - servicer.FetchAllFlags, - request_deserializer=flagd_dot_sync_dot_v1_dot_sync__pb2.FetchAllFlagsRequest.FromString, - response_serializer=flagd_dot_sync_dot_v1_dot_sync__pb2.FetchAllFlagsResponse.SerializeToString, - ), - 'GetMetadata': grpc.unary_unary_rpc_method_handler( - servicer.GetMetadata, - request_deserializer=flagd_dot_sync_dot_v1_dot_sync__pb2.GetMetadataRequest.FromString, - response_serializer=flagd_dot_sync_dot_v1_dot_sync__pb2.GetMetadataResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'flagd.sync.v1.FlagSyncService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class FlagSyncService(object): - """FlagService implements a server streaming to provide realtime flag configurations - """ - - @staticmethod - def SyncFlags(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/flagd.sync.v1.FlagSyncService/SyncFlags', - flagd_dot_sync_dot_v1_dot_sync__pb2.SyncFlagsRequest.SerializeToString, - flagd_dot_sync_dot_v1_dot_sync__pb2.SyncFlagsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def FetchAllFlags(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/flagd.sync.v1.FlagSyncService/FetchAllFlags', - flagd_dot_sync_dot_v1_dot_sync__pb2.FetchAllFlagsRequest.SerializeToString, - flagd_dot_sync_dot_v1_dot_sync__pb2.FetchAllFlagsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetMetadata(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/flagd.sync.v1.FlagSyncService/GetMetadata', - flagd_dot_sync_dot_v1_dot_sync__pb2.GetMetadataRequest.SerializeToString, - flagd_dot_sync_dot_v1_dot_sync__pb2.GetMetadataResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2.py deleted file mode 100644 index 5a6a9134..00000000 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2.py +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: schema/v1/schema.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16schema/v1/schema.proto\x12\tschema.v1\x1a\x1cgoogle/protobuf/struct.proto\"F\n\x11ResolveAllRequest\x12\x31\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\xa2\x01\n\x12ResolveAllResponse\x12>\n\x05\x66lags\x18\x01 \x03(\x0b\x32(.schema.v1.ResolveAllResponse.FlagsEntryR\x05\x66lags\x1aL\n\nFlagsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12(\n\x05value\x18\x02 \x01(\x0b\x32\x12.schema.v1.AnyFlagR\x05value:\x02\x38\x01\"\xed\x01\n\x07\x41nyFlag\x12\x16\n\x06reason\x18\x01 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x02 \x01(\tR\x07variant\x12\x1f\n\nbool_value\x18\x03 \x01(\x08H\x00R\tboolValue\x12#\n\x0cstring_value\x18\x04 \x01(\tH\x00R\x0bstringValue\x12#\n\x0c\x64ouble_value\x18\x05 \x01(\x01H\x00R\x0b\x64oubleValue\x12<\n\x0cobject_value\x18\x06 \x01(\x0b\x32\x17.google.protobuf.StructH\x00R\x0bobjectValueB\x07\n\x05value\"e\n\x15ResolveBooleanRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\x95\x01\n\x16ResolveBooleanResponse\x12\x14\n\x05value\x18\x01 \x01(\x08R\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"d\n\x14ResolveStringRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\x94\x01\n\x15ResolveStringResponse\x12\x14\n\x05value\x18\x01 \x01(\tR\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"c\n\x13ResolveFloatRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\x93\x01\n\x14ResolveFloatResponse\x12\x14\n\x05value\x18\x01 \x01(\x01R\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"a\n\x11ResolveIntRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\x91\x01\n\x12ResolveIntResponse\x12\x14\n\x05value\x18\x01 \x01(\x03R\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"d\n\x14ResolveObjectRequest\x12\x19\n\x08\x66lag_key\x18\x01 \x01(\tR\x07\x66lagKey\x12\x31\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x07\x63ontext\"\xad\x01\n\x15ResolveObjectResponse\x12-\n\x05value\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x05value\x12\x16\n\x06reason\x18\x02 \x01(\tR\x06reason\x12\x18\n\x07variant\x18\x03 \x01(\tR\x07variant\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\"V\n\x13\x45ventStreamResponse\x12\x12\n\x04type\x18\x01 \x01(\tR\x04type\x12+\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x17.google.protobuf.StructR\x04\x64\x61ta\"\x14\n\x12\x45ventStreamRequest2\xcd\x04\n\x07Service\x12K\n\nResolveAll\x12\x1c.schema.v1.ResolveAllRequest\x1a\x1d.schema.v1.ResolveAllResponse\"\x00\x12W\n\x0eResolveBoolean\x12 .schema.v1.ResolveBooleanRequest\x1a!.schema.v1.ResolveBooleanResponse\"\x00\x12T\n\rResolveString\x12\x1f.schema.v1.ResolveStringRequest\x1a .schema.v1.ResolveStringResponse\"\x00\x12Q\n\x0cResolveFloat\x12\x1e.schema.v1.ResolveFloatRequest\x1a\x1f.schema.v1.ResolveFloatResponse\"\x00\x12K\n\nResolveInt\x12\x1c.schema.v1.ResolveIntRequest\x1a\x1d.schema.v1.ResolveIntResponse\"\x00\x12T\n\rResolveObject\x12\x1f.schema.v1.ResolveObjectRequest\x1a .schema.v1.ResolveObjectResponse\"\x00\x12P\n\x0b\x45ventStream\x12\x1d.schema.v1.EventStreamRequest\x1a\x1e.schema.v1.EventStreamResponse\"\x00\x30\x01\x42t\n\rcom.schema.v1B\x0bSchemaProtoP\x01Z\x11schema/service/v1\xa2\x02\x03SXX\xaa\x02\tSchema.V1\xca\x02\tSchema\\V1\xe2\x02\x15Schema\\V1\\GPBMetadata\xea\x02\nSchema::V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'schema.v1.schema_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\rcom.schema.v1B\013SchemaProtoP\001Z\021schema/service/v1\242\002\003SXX\252\002\tSchema.V1\312\002\tSchema\\V1\342\002\025Schema\\V1\\GPBMetadata\352\002\nSchema::V1' - _RESOLVEALLRESPONSE_FLAGSENTRY._options = None - _RESOLVEALLRESPONSE_FLAGSENTRY._serialized_options = b'8\001' - _globals['_RESOLVEALLREQUEST']._serialized_start=67 - _globals['_RESOLVEALLREQUEST']._serialized_end=137 - _globals['_RESOLVEALLRESPONSE']._serialized_start=140 - _globals['_RESOLVEALLRESPONSE']._serialized_end=302 - _globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_start=226 - _globals['_RESOLVEALLRESPONSE_FLAGSENTRY']._serialized_end=302 - _globals['_ANYFLAG']._serialized_start=305 - _globals['_ANYFLAG']._serialized_end=542 - _globals['_RESOLVEBOOLEANREQUEST']._serialized_start=544 - _globals['_RESOLVEBOOLEANREQUEST']._serialized_end=645 - _globals['_RESOLVEBOOLEANRESPONSE']._serialized_start=648 - _globals['_RESOLVEBOOLEANRESPONSE']._serialized_end=797 - _globals['_RESOLVESTRINGREQUEST']._serialized_start=799 - _globals['_RESOLVESTRINGREQUEST']._serialized_end=899 - _globals['_RESOLVESTRINGRESPONSE']._serialized_start=902 - _globals['_RESOLVESTRINGRESPONSE']._serialized_end=1050 - _globals['_RESOLVEFLOATREQUEST']._serialized_start=1052 - _globals['_RESOLVEFLOATREQUEST']._serialized_end=1151 - _globals['_RESOLVEFLOATRESPONSE']._serialized_start=1154 - _globals['_RESOLVEFLOATRESPONSE']._serialized_end=1301 - _globals['_RESOLVEINTREQUEST']._serialized_start=1303 - _globals['_RESOLVEINTREQUEST']._serialized_end=1400 - _globals['_RESOLVEINTRESPONSE']._serialized_start=1403 - _globals['_RESOLVEINTRESPONSE']._serialized_end=1548 - _globals['_RESOLVEOBJECTREQUEST']._serialized_start=1550 - _globals['_RESOLVEOBJECTREQUEST']._serialized_end=1650 - _globals['_RESOLVEOBJECTRESPONSE']._serialized_start=1653 - _globals['_RESOLVEOBJECTRESPONSE']._serialized_end=1826 - _globals['_EVENTSTREAMRESPONSE']._serialized_start=1828 - _globals['_EVENTSTREAMRESPONSE']._serialized_end=1914 - _globals['_EVENTSTREAMREQUEST']._serialized_start=1916 - _globals['_EVENTSTREAMREQUEST']._serialized_end=1936 - _globals['_SERVICE']._serialized_start=1939 - _globals['_SERVICE']._serialized_end=2528 -# @@protoc_insertion_point(module_scope) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2_grpc.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2_grpc.py deleted file mode 100644 index e1dfad8f..00000000 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/schema/v1/schema_pb2_grpc.py +++ /dev/null @@ -1,267 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from . import schema_pb2 as schema_dot_v1_dot_schema__pb2 - - -class ServiceStub(object): - """Service defines the exposed rpcs of flagd - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.ResolveAll = channel.unary_unary( - '/schema.v1.Service/ResolveAll', - request_serializer=schema_dot_v1_dot_schema__pb2.ResolveAllRequest.SerializeToString, - response_deserializer=schema_dot_v1_dot_schema__pb2.ResolveAllResponse.FromString, - ) - self.ResolveBoolean = channel.unary_unary( - '/schema.v1.Service/ResolveBoolean', - request_serializer=schema_dot_v1_dot_schema__pb2.ResolveBooleanRequest.SerializeToString, - response_deserializer=schema_dot_v1_dot_schema__pb2.ResolveBooleanResponse.FromString, - ) - self.ResolveString = channel.unary_unary( - '/schema.v1.Service/ResolveString', - request_serializer=schema_dot_v1_dot_schema__pb2.ResolveStringRequest.SerializeToString, - response_deserializer=schema_dot_v1_dot_schema__pb2.ResolveStringResponse.FromString, - ) - self.ResolveFloat = channel.unary_unary( - '/schema.v1.Service/ResolveFloat', - request_serializer=schema_dot_v1_dot_schema__pb2.ResolveFloatRequest.SerializeToString, - response_deserializer=schema_dot_v1_dot_schema__pb2.ResolveFloatResponse.FromString, - ) - self.ResolveInt = channel.unary_unary( - '/schema.v1.Service/ResolveInt', - request_serializer=schema_dot_v1_dot_schema__pb2.ResolveIntRequest.SerializeToString, - response_deserializer=schema_dot_v1_dot_schema__pb2.ResolveIntResponse.FromString, - ) - self.ResolveObject = channel.unary_unary( - '/schema.v1.Service/ResolveObject', - request_serializer=schema_dot_v1_dot_schema__pb2.ResolveObjectRequest.SerializeToString, - response_deserializer=schema_dot_v1_dot_schema__pb2.ResolveObjectResponse.FromString, - ) - self.EventStream = channel.unary_stream( - '/schema.v1.Service/EventStream', - request_serializer=schema_dot_v1_dot_schema__pb2.EventStreamRequest.SerializeToString, - response_deserializer=schema_dot_v1_dot_schema__pb2.EventStreamResponse.FromString, - ) - - -class ServiceServicer(object): - """Service defines the exposed rpcs of flagd - """ - - def ResolveAll(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveBoolean(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveString(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveFloat(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveInt(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResolveObject(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def EventStream(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_ServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'ResolveAll': grpc.unary_unary_rpc_method_handler( - servicer.ResolveAll, - request_deserializer=schema_dot_v1_dot_schema__pb2.ResolveAllRequest.FromString, - response_serializer=schema_dot_v1_dot_schema__pb2.ResolveAllResponse.SerializeToString, - ), - 'ResolveBoolean': grpc.unary_unary_rpc_method_handler( - servicer.ResolveBoolean, - request_deserializer=schema_dot_v1_dot_schema__pb2.ResolveBooleanRequest.FromString, - response_serializer=schema_dot_v1_dot_schema__pb2.ResolveBooleanResponse.SerializeToString, - ), - 'ResolveString': grpc.unary_unary_rpc_method_handler( - servicer.ResolveString, - request_deserializer=schema_dot_v1_dot_schema__pb2.ResolveStringRequest.FromString, - response_serializer=schema_dot_v1_dot_schema__pb2.ResolveStringResponse.SerializeToString, - ), - 'ResolveFloat': grpc.unary_unary_rpc_method_handler( - servicer.ResolveFloat, - request_deserializer=schema_dot_v1_dot_schema__pb2.ResolveFloatRequest.FromString, - response_serializer=schema_dot_v1_dot_schema__pb2.ResolveFloatResponse.SerializeToString, - ), - 'ResolveInt': grpc.unary_unary_rpc_method_handler( - servicer.ResolveInt, - request_deserializer=schema_dot_v1_dot_schema__pb2.ResolveIntRequest.FromString, - response_serializer=schema_dot_v1_dot_schema__pb2.ResolveIntResponse.SerializeToString, - ), - 'ResolveObject': grpc.unary_unary_rpc_method_handler( - servicer.ResolveObject, - request_deserializer=schema_dot_v1_dot_schema__pb2.ResolveObjectRequest.FromString, - response_serializer=schema_dot_v1_dot_schema__pb2.ResolveObjectResponse.SerializeToString, - ), - 'EventStream': grpc.unary_stream_rpc_method_handler( - servicer.EventStream, - request_deserializer=schema_dot_v1_dot_schema__pb2.EventStreamRequest.FromString, - response_serializer=schema_dot_v1_dot_schema__pb2.EventStreamResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'schema.v1.Service', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class Service(object): - """Service defines the exposed rpcs of flagd - """ - - @staticmethod - def ResolveAll(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/schema.v1.Service/ResolveAll', - schema_dot_v1_dot_schema__pb2.ResolveAllRequest.SerializeToString, - schema_dot_v1_dot_schema__pb2.ResolveAllResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveBoolean(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/schema.v1.Service/ResolveBoolean', - schema_dot_v1_dot_schema__pb2.ResolveBooleanRequest.SerializeToString, - schema_dot_v1_dot_schema__pb2.ResolveBooleanResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveString(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/schema.v1.Service/ResolveString', - schema_dot_v1_dot_schema__pb2.ResolveStringRequest.SerializeToString, - schema_dot_v1_dot_schema__pb2.ResolveStringResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveFloat(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/schema.v1.Service/ResolveFloat', - schema_dot_v1_dot_schema__pb2.ResolveFloatRequest.SerializeToString, - schema_dot_v1_dot_schema__pb2.ResolveFloatResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveInt(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/schema.v1.Service/ResolveInt', - schema_dot_v1_dot_schema__pb2.ResolveIntRequest.SerializeToString, - schema_dot_v1_dot_schema__pb2.ResolveIntResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResolveObject(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/schema.v1.Service/ResolveObject', - schema_dot_v1_dot_schema__pb2.ResolveObjectRequest.SerializeToString, - schema_dot_v1_dot_schema__pb2.ResolveObjectResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def EventStream(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/schema.v1.Service/EventStream', - schema_dot_v1_dot_schema__pb2.EventStreamRequest.SerializeToString, - schema_dot_v1_dot_schema__pb2.EventStreamResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2.py deleted file mode 100644 index 8135eb75..00000000 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: sync/v1/sync_service.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1async/v1/sync_service.proto\x12\x07sync.v1\"O\n\x10SyncFlagsRequest\x12\x1f\n\x0bprovider_id\x18\x01 \x01(\tR\nproviderId\x12\x1a\n\x08selector\x18\x02 \x01(\tR\x08selector\"l\n\x11SyncFlagsResponse\x12-\n\x12\x66lag_configuration\x18\x01 \x01(\tR\x11\x66lagConfiguration\x12(\n\x05state\x18\x02 \x01(\x0e\x32\x12.sync.v1.SyncStateR\x05state\"S\n\x14\x46\x65tchAllFlagsRequest\x12\x1f\n\x0bprovider_id\x18\x01 \x01(\tR\nproviderId\x12\x1a\n\x08selector\x18\x02 \x01(\tR\x08selector\"F\n\x15\x46\x65tchAllFlagsResponse\x12-\n\x12\x66lag_configuration\x18\x01 \x01(\tR\x11\x66lagConfiguration*\x92\x01\n\tSyncState\x12\x1a\n\x16SYNC_STATE_UNSPECIFIED\x10\x00\x12\x12\n\x0eSYNC_STATE_ALL\x10\x01\x12\x12\n\x0eSYNC_STATE_ADD\x10\x02\x12\x15\n\x11SYNC_STATE_UPDATE\x10\x03\x12\x15\n\x11SYNC_STATE_DELETE\x10\x04\x12\x13\n\x0fSYNC_STATE_PING\x10\x05\x32\xab\x01\n\x0f\x46lagSyncService\x12\x46\n\tSyncFlags\x12\x19.sync.v1.SyncFlagsRequest\x1a\x1a.sync.v1.SyncFlagsResponse\"\x00\x30\x01\x12P\n\rFetchAllFlags\x12\x1d.sync.v1.FetchAllFlagsRequest\x1a\x1e.sync.v1.FetchAllFlagsResponse\"\x00\x42l\n\x0b\x63om.sync.v1B\x10SyncServiceProtoP\x01Z\x0e\x66lagd/grpcsync\xa2\x02\x03SXX\xaa\x02\x07Sync.V1\xca\x02\x07Sync\\V1\xe2\x02\x13Sync\\V1\\GPBMetadata\xea\x02\x08Sync::V1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'sync.v1.sync_service_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\013com.sync.v1B\020SyncServiceProtoP\001Z\016flagd/grpcsync\242\002\003SXX\252\002\007Sync.V1\312\002\007Sync\\V1\342\002\023Sync\\V1\\GPBMetadata\352\002\010Sync::V1' - _globals['_SYNCSTATE']._serialized_start=388 - _globals['_SYNCSTATE']._serialized_end=534 - _globals['_SYNCFLAGSREQUEST']._serialized_start=39 - _globals['_SYNCFLAGSREQUEST']._serialized_end=118 - _globals['_SYNCFLAGSRESPONSE']._serialized_start=120 - _globals['_SYNCFLAGSRESPONSE']._serialized_end=228 - _globals['_FETCHALLFLAGSREQUEST']._serialized_start=230 - _globals['_FETCHALLFLAGSREQUEST']._serialized_end=313 - _globals['_FETCHALLFLAGSRESPONSE']._serialized_start=315 - _globals['_FETCHALLFLAGSRESPONSE']._serialized_end=385 - _globals['_FLAGSYNCSERVICE']._serialized_start=537 - _globals['_FLAGSYNCSERVICE']._serialized_end=708 -# @@protoc_insertion_point(module_scope) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2_grpc.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2_grpc.py deleted file mode 100644 index fa38ac67..00000000 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/sync/v1/sync_service_pb2_grpc.py +++ /dev/null @@ -1,102 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from sync.v1 import sync_service_pb2 as sync_dot_v1_dot_sync__service__pb2 - - -class FlagSyncServiceStub(object): - """FlagService implements a server streaming to provide realtime flag configurations - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.SyncFlags = channel.unary_stream( - '/sync.v1.FlagSyncService/SyncFlags', - request_serializer=sync_dot_v1_dot_sync__service__pb2.SyncFlagsRequest.SerializeToString, - response_deserializer=sync_dot_v1_dot_sync__service__pb2.SyncFlagsResponse.FromString, - ) - self.FetchAllFlags = channel.unary_unary( - '/sync.v1.FlagSyncService/FetchAllFlags', - request_serializer=sync_dot_v1_dot_sync__service__pb2.FetchAllFlagsRequest.SerializeToString, - response_deserializer=sync_dot_v1_dot_sync__service__pb2.FetchAllFlagsResponse.FromString, - ) - - -class FlagSyncServiceServicer(object): - """FlagService implements a server streaming to provide realtime flag configurations - """ - - def SyncFlags(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def FetchAllFlags(self, request, context): - """Missing associated documentation comment in .proto file.""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_FlagSyncServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'SyncFlags': grpc.unary_stream_rpc_method_handler( - servicer.SyncFlags, - request_deserializer=sync_dot_v1_dot_sync__service__pb2.SyncFlagsRequest.FromString, - response_serializer=sync_dot_v1_dot_sync__service__pb2.SyncFlagsResponse.SerializeToString, - ), - 'FetchAllFlags': grpc.unary_unary_rpc_method_handler( - servicer.FetchAllFlags, - request_deserializer=sync_dot_v1_dot_sync__service__pb2.FetchAllFlagsRequest.FromString, - response_serializer=sync_dot_v1_dot_sync__service__pb2.FetchAllFlagsResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'sync.v1.FlagSyncService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class FlagSyncService(object): - """FlagService implements a server streaming to provide realtime flag configurations - """ - - @staticmethod - def SyncFlags(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/sync.v1.FlagSyncService/SyncFlags', - sync_dot_v1_dot_sync__service__pb2.SyncFlagsRequest.SerializeToString, - sync_dot_v1_dot_sync__service__pb2.SyncFlagsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def FetchAllFlags(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/sync.v1.FlagSyncService/FetchAllFlags', - sync_dot_v1_dot_sync__service__pb2.FetchAllFlagsRequest.SerializeToString, - sync_dot_v1_dot_sync__service__pb2.FetchAllFlagsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py index 194dc558..f57eef4e 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py @@ -13,10 +13,16 @@ TypeMismatchError, ) from openfeature.flag_evaluation import FlagResolutionDetails +from openfeature.schemas.protobuf.flagd.evaluation.v1 import ( + evaluation_pb2, + evaluation_pb2_grpc, +) from ..config import Config from ..flag_type import FlagType -from ..proto.schema.v1 import schema_pb2, schema_pb2_grpc + +if typing.TYPE_CHECKING: + from google.protobuf.message import Message T = typing.TypeVar("T") @@ -28,7 +34,7 @@ def __init__(self, config: Config): grpc.secure_channel if self.config.tls else grpc.insecure_channel ) self.channel = channel_factory(f"{self.config.host}:{self.config.port}") - self.stub = schema_pb2_grpc.ServiceStub(self.channel) + self.stub = evaluation_pb2_grpc.ServiceStub(self.channel) def shutdown(self) -> None: self.channel.close() @@ -83,20 +89,21 @@ def _resolve( # noqa: PLR0915 context = self._convert_context(evaluation_context) call_args = {"timeout": self.config.timeout} try: + request: Message if flag_type == FlagType.BOOLEAN: - request = schema_pb2.ResolveBooleanRequest( # type:ignore[attr-defined] + request = evaluation_pb2.ResolveBooleanRequest( flag_key=flag_key, context=context ) response = self.stub.ResolveBoolean(request, **call_args) value = response.value elif flag_type == FlagType.STRING: - request = schema_pb2.ResolveStringRequest( # type:ignore[attr-defined] + request = evaluation_pb2.ResolveStringRequest( flag_key=flag_key, context=context ) response = self.stub.ResolveString(request, **call_args) value = response.value elif flag_type == FlagType.OBJECT: - request = schema_pb2.ResolveObjectRequest( # type:ignore[attr-defined] + request = evaluation_pb2.ResolveObjectRequest( flag_key=flag_key, context=context ) response = self.stub.ResolveObject(request, **call_args) @@ -104,13 +111,13 @@ def _resolve( # noqa: PLR0915 "value" ] elif flag_type == FlagType.FLOAT: - request = schema_pb2.ResolveFloatRequest( # type:ignore[attr-defined] + request = evaluation_pb2.ResolveFloatRequest( flag_key=flag_key, context=context ) response = self.stub.ResolveFloat(request, **call_args) value = response.value elif flag_type == FlagType.INTEGER: - request = schema_pb2.ResolveIntRequest( # type:ignore[attr-defined] + request = evaluation_pb2.ResolveIntRequest( flag_key=flag_key, context=context ) response = self.stub.ResolveInt(request, **call_args) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py index bb73a8cd..bcc00a5a 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py @@ -1,8 +1,8 @@ import time import typing -from json_logic import builtins, jsonLogic # type: ignore[import-untyped] -from json_logic.types import JsonValue # type: ignore[import-untyped] +from json_logic import builtins, jsonLogic +from json_logic.types import JsonValue from openfeature.evaluation_context import EvaluationContext diff --git a/providers/openfeature-provider-flagd/tests/e2e/conftest.py b/providers/openfeature-provider-flagd/tests/e2e/conftest.py index 2aae58d9..af14e299 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/conftest.py +++ b/providers/openfeature-provider-flagd/tests/e2e/conftest.py @@ -10,6 +10,9 @@ JsonPrimitive = typing.Union[str, bool, float, int] +TEST_HARNESS_PATH = "../../openfeature/test-harness" +SPEC_PATH = "../../openfeature/spec" + @pytest.fixture(autouse=True, scope="package") def setup(request, port, image, resolver_type): diff --git a/providers/openfeature-provider-flagd/tests/e2e/test_in-process-file.py b/providers/openfeature-provider-flagd/tests/e2e/test_in-process-file.py index 2d09ca11..9f1568a4 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/test_in-process-file.py +++ b/providers/openfeature-provider-flagd/tests/e2e/test_in-process-file.py @@ -6,6 +6,7 @@ import pytest import yaml from pytest_bdd import scenario, scenarios +from tests.e2e.conftest import SPEC_PATH, TEST_HARNESS_PATH from openfeature import api from openfeature.contrib.provider.flagd import FlagdProvider @@ -24,7 +25,7 @@ def file_name(request): result = {KEY_FLAGS: {}, KEY_EVALUATORS: {}} path = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../test-harness/flags/") + os.path.join(os.path.dirname(__file__), f"{TEST_HARNESS_PATH}/flags/") ) for f in listdir(path): @@ -60,13 +61,13 @@ def setup(request, file_name): @pytest.mark.skip(reason="Eventing not implemented") -@scenario("../../test-harness/gherkin/flagd.feature", "Flag change event") +@scenario(f"{TEST_HARNESS_PATH}/gherkin/flagd.feature", "Flag change event") def test_flag_change_event(): """not implemented""" scenarios( - "../../test-harness/gherkin/flagd.feature", - "../../test-harness/gherkin/flagd-json-evaluator.feature", - "../../spec/specification/assets/gherkin/evaluation.feature", + f"{TEST_HARNESS_PATH}/gherkin/flagd.feature", + f"{TEST_HARNESS_PATH}/gherkin/flagd-json-evaluator.feature", + f"{SPEC_PATH}/specification/assets/gherkin/evaluation.feature", ) diff --git a/providers/openfeature-provider-flagd/tests/e2e/test_rpc.py b/providers/openfeature-provider-flagd/tests/e2e/test_rpc.py index d2fe57e9..2a5d1c15 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/test_rpc.py +++ b/providers/openfeature-provider-flagd/tests/e2e/test_rpc.py @@ -1,5 +1,6 @@ import pytest from pytest_bdd import scenario, scenarios +from tests.e2e.conftest import SPEC_PATH, TEST_HARNESS_PATH from openfeature.contrib.provider.flagd.config import ResolverType @@ -20,13 +21,13 @@ def image(): @pytest.mark.skip(reason="Eventing not implemented") -@scenario("../../test-harness/gherkin/flagd.feature", "Flag change event") +@scenario(f"{TEST_HARNESS_PATH}/gherkin/flagd.feature", "Flag change event") def test_flag_change_event(): """not implemented""" scenarios( - "../../test-harness/gherkin/flagd.feature", - "../../test-harness/gherkin/flagd-json-evaluator.feature", - "../../spec/specification/assets/gherkin/evaluation.feature", + f"{TEST_HARNESS_PATH}/gherkin/flagd.feature", + f"{TEST_HARNESS_PATH}/gherkin/flagd-json-evaluator.feature", + f"{SPEC_PATH}/specification/assets/gherkin/evaluation.feature", ) diff --git a/ruff.toml b/ruff.toml index a92d6eec..55f93a89 100644 --- a/ruff.toml +++ b/ruff.toml @@ -3,7 +3,7 @@ exclude = [ ".venv", "__pycache__", "venv", - "providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/proto/*" + "providers/openfeature-provider-flagd/src/openfeature/schemas/**" ] target-version = "py38" From 5adc68af00fe832f0b0c8a4d0c0284a3960c3611 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Wed, 27 Nov 2024 14:36:46 +0100 Subject: [PATCH 2/7] build: readd build for the main branch(#113) (#114) Signed-off-by: Simon Schrottner Co-authored-by: Todd Baert --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28f96c45..75bc338b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,9 @@ name: "Build, lint, and test" on: + push: + branches: + - main pull_request: types: - opened From 61e42e7bcf6d1bf5ae42e260324059d6af8e4d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Gr=C3=BCbel?= Date: Thu, 28 Nov 2024 00:06:18 +0100 Subject: [PATCH 3/7] build: setup mypy for each package independently (#116) setup mypy for each package independently Signed-off-by: gruebel --- .github/workflows/build.yml | 4 +-- .pre-commit-config.yaml | 16 --------- .../pyproject.toml | 21 ++++++++++++ .../openfeature-provider-ofrep/pyproject.toml | 33 ++++++++++++++++--- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 75bc338b..6b153cb0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,7 @@ jobs: package: - "hooks/openfeature-hooks-opentelemetry" - "providers/openfeature-provider-flagd" + - "providers/openfeature-provider-ofrep" steps: - uses: actions/checkout@v4 @@ -49,8 +50,7 @@ jobs: working-directory: ${{ matrix.package }} - name: Type checking - # TODO: migrate other packages to use their own 'mypy' setup - if: matrix.python-version == '3.11' && matrix.package == 'providers/openfeature-provider-flagd' + if: matrix.python-version == '3.11' working-directory: ${{ matrix.package }} run: hatch run mypy:run diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f8a6a85..9b08fdba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,19 +14,3 @@ repos: - id: check-yaml - id: trailing-whitespace - id: check-merge-conflict - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 - hooks: - - id: mypy - args: [--python-version=3.8] - additional_dependencies: - - openfeature-sdk>=0.4.0 - - opentelemetry-api - - types-protobuf - - types-PyYAML - - types-requests - - mmh3 - - semver - - panzi-json-logic - exclude: providers/openfeature-provider-flagd|tests diff --git a/hooks/openfeature-hooks-opentelemetry/pyproject.toml b/hooks/openfeature-hooks-opentelemetry/pyproject.toml index 0c585f3c..b826dfeb 100644 --- a/hooks/openfeature-hooks-opentelemetry/pyproject.toml +++ b/hooks/openfeature-hooks-opentelemetry/pyproject.toml @@ -50,6 +50,14 @@ cov = [ "cov-report", ] +[tool.hatch.envs.mypy] +dependencies = [ + "mypy[faster-cache]>=1.13.0", +] + +[tool.hatch.envs.mypy.scripts] +run = "mypy" + [tool.hatch.build.targets.sdist] exclude = [ ".gitignore", @@ -57,3 +65,16 @@ exclude = [ [tool.hatch.build.targets.wheel] packages = ["src/openfeature"] + +[tool.mypy] +mypy_path = "src" +files = "src" + +python_version = "3.8" # should be identical to the minimum supported version +namespace_packages = true +explicit_package_bases = true +local_partial_types = true +pretty = true + +strict = true +disallow_any_generics = false diff --git a/providers/openfeature-provider-ofrep/pyproject.toml b/providers/openfeature-provider-ofrep/pyproject.toml index 693ec7bf..30a174da 100644 --- a/providers/openfeature-provider-ofrep/pyproject.toml +++ b/providers/openfeature-provider-ofrep/pyproject.toml @@ -27,26 +27,36 @@ Homepage = "https://github.com/open-feature/python-sdk-contrib" [tool.hatch] -[tool.hatch.envs.default] +[tool.hatch.envs.hatch-test] dependencies = [ "coverage[toml]>=6.5", "pytest", "requests-mock", - "types-requests", ] -[tool.hatch.envs.default.scripts] -test = "pytest {args:tests}" -test-cov = "coverage run -m pytest {args:tests}" +[tool.hatch.envs.hatch-test.scripts] +run = "pytest {args:tests}" +run-cov = "coverage run -m pytest {args:tests}" +cov-combine = "coverage combine" cov-report = [ "coverage xml", "coverage html", + "coverage report", ] cov = [ "test-cov", "cov-report", ] +[tool.hatch.envs.mypy] +dependencies = [ + "mypy[faster-cache]>=1.13.0", + "types-requests", +] + +[tool.hatch.envs.mypy.scripts] +run = "mypy" + [tool.hatch.build.targets.sdist] exclude = [ ".gitignore", @@ -60,3 +70,16 @@ packages = ["src/openfeature"] omit = [ "tests/**", ] + +[tool.mypy] +mypy_path = "src" +files = "src" + +python_version = "3.8" # should be identical to the minimum supported version +namespace_packages = true +explicit_package_bases = true +local_partial_types = true +pretty = true + +strict = true +disallow_any_generics = false From b62d3d1ab5ce40f275e795ae2682ae3fe315f431 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Thu, 28 Nov 2024 16:20:31 +0100 Subject: [PATCH 4/7] feat(flagd-rpc)!: add events for rpc mode, some breaking config fixes (#108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build(flagd): auto generate proto files from schema Signed-off-by: Simon Schrottner * fixup: changing to mypy-protobuf Signed-off-by: Simon Schrottner * fixup: changing to mypy-protobuf Signed-off-by: Simon Schrottner * Update providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py Co-authored-by: Todd Baert Signed-off-by: Simon Schrottner * Apply suggestions from code review Signed-off-by: Simon Schrottner * Update providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py Co-authored-by: Anton Grübel Signed-off-by: Simon Schrottner --------- Signed-off-by: Simon Schrottner Co-authored-by: Todd Baert Co-authored-by: Anton Grübel --- .../openfeature-provider-flagd/README.md | 83 ++++++++- .../openfeature-provider-flagd/pyproject.toml | 2 +- .../contrib/provider/flagd/config.py | 106 +++++++++-- .../contrib/provider/flagd/provider.py | 40 +++- .../provider/flagd/resolvers/__init__.py | 2 + .../contrib/provider/flagd/resolvers/grpc.py | 123 +++++++++++- .../provider/flagd/resolvers/in_process.py | 7 +- .../tests/e2e/conftest.py | 15 +- .../tests/e2e/steps.py | 175 ++++++++++++++---- .../tests/e2e/test_in-process-file.py | 36 +++- .../tests/e2e/test_rpc.py | 21 +-- .../tests/e2e/test_rpc_reconnect.py | 30 +++ .../tests/test_config.py | 139 ++++++++++++-- 13 files changed, 656 insertions(+), 123 deletions(-) create mode 100644 providers/openfeature-provider-flagd/tests/e2e/test_rpc_reconnect.py diff --git a/providers/openfeature-provider-flagd/README.md b/providers/openfeature-provider-flagd/README.md index aa63c55c..6833da0c 100644 --- a/providers/openfeature-provider-flagd/README.md +++ b/providers/openfeature-provider-flagd/README.md @@ -10,6 +10,14 @@ pip install openfeature-provider-flagd ## Configuration and Usage +The flagd provider can operate in two modes: [RPC](#remote-resolver-rpc) (evaluation takes place in flagd, via gRPC calls) or [in-process](#in-process-resolver) (evaluation takes place in-process, with the provider getting a ruleset from a compliant sync-source). + +### Remote resolver (RPC) + +This is the default mode of operation of the provider. +In this mode, `FlagdProvider` communicates with [flagd](https://github.com/open-feature/flagd) via the gRPC protocol. +Flag evaluations take place remotely at the connected flagd instance. + Instantiate a new FlagdProvider instance and configure the OpenFeature SDK to use it: ```python @@ -19,7 +27,9 @@ from openfeature.contrib.provider.flagd import FlagdProvider api.set_provider(FlagdProvider()) ``` -To use in-process evaluation in offline mode with a file as source: +### In-process resolver + +This mode performs flag evaluations locally (in-process). ```python from openfeature import api @@ -36,12 +46,71 @@ api.set_provider(FlagdProvider( The default options can be defined in the FlagdProvider constructor. -| Option name | Type & Values | Default | -|----------------|---------------|-----------| -| host | str | localhost | -| port | int | 8013 | -| schema | str | http | -| timeout | int | 2 | +| Option name | Environment variable name | Type & Values | Default | Compatible resolver | +| ------------------------ | ------------------------------ | -------------------------- | ----------------------------- | ------------------- | +| resolver_type | FLAGD_RESOLVER | enum - `rpc`, `in-process` | rpc | | +| host | FLAGD_HOST | str | localhost | rpc & in-process | +| port | FLAGD_PORT | int | 8013 (rpc), 8015 (in-process) | rpc & in-process | +| tls | FLAGD_TLS | bool | false | rpc & in-process | +| deadline | FLAGD_DEADLINE_MS | int | 500 | rpc & in-process | +| stream_deadline_ms | FLAGD_STREAM_DEADLINE_MS | int | 600000 | rpc & in-process | +| keep_alive_time | FLAGD_KEEP_ALIVE_TIME_MS | int | 0 | rpc & in-process | +| selector | FLAGD_SOURCE_SELECTOR | str | null | in-process | +| cache_type | FLAGD_CACHE | enum - `lru`, `disabled` | lru | rpc | +| max_cache_size | FLAGD_MAX_CACHE_SIZE | int | 1000 | rpc | +| retry_backoff_ms | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc | +| offline_flag_source_path | FLAGD_OFFLINE_FLAG_SOURCE_PATH | str | null | in-process | + + + +> [!NOTE] +> Some configurations are only applicable for RPC resolver. + + + +### Reconnection + +Reconnection is supported by the underlying gRPC connections. +If the connection to flagd is lost, it will reconnect automatically. +A failure to connect will result in an [error event](https://openfeature.dev/docs/reference/concepts/events#provider_error) from the provider, though it will attempt to reconnect indefinitely. + +### Deadlines + +Deadlines are used to define how long the provider waits to complete initialization or flag evaluations. +They behave differently based on the resolver type. + +#### Deadlines with Remote resolver (RPC) + +If the remote evaluation call is not completed within this deadline, the gRPC call is terminated with the error `DEADLINE_EXCEEDED` +and the evaluation will default. + +### TLS + +TLS is available in situations where flagd is running on another host. + + ## License diff --git a/providers/openfeature-provider-flagd/pyproject.toml b/providers/openfeature-provider-flagd/pyproject.toml index 696dfd00..7e74e810 100644 --- a/providers/openfeature-provider-flagd/pyproject.toml +++ b/providers/openfeature-provider-flagd/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ keywords = [] dependencies = [ "openfeature-sdk>=0.6.0", - "grpcio>=1.60.0", + "grpcio>=1.68.0", "protobuf>=4.25.2", "mmh3>=4.1.0", "panzi-json-logic>=1.0.1", diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py index a95c3153..a393d270 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py @@ -2,6 +2,33 @@ import typing from enum import Enum + +class ResolverType(Enum): + RPC = "rpc" + IN_PROCESS = "in-process" + + +DEFAULT_DEADLINE = 500 +DEFAULT_HOST = "localhost" +DEFAULT_KEEP_ALIVE = 0 +DEFAULT_OFFLINE_SOURCE_PATH: typing.Optional[str] = None +DEFAULT_PORT_IN_PROCESS = 8015 +DEFAULT_PORT_RPC = 8013 +DEFAULT_RESOLVER_TYPE = ResolverType.RPC +DEFAULT_RETRY_BACKOFF = 1000 +DEFAULT_STREAM_DEADLINE = 600000 +DEFAULT_TLS = False + +ENV_VAR_DEADLINE_MS = "FLAGD_DEADLINE_MS" +ENV_VAR_HOST = "FLAGD_HOST" +ENV_VAR_KEEP_ALIVE_TIME_MS = "FLAGD_KEEP_ALIVE_TIME_MS" +ENV_VAR_OFFLINE_FLAG_SOURCE_PATH = "FLAGD_OFFLINE_FLAG_SOURCE_PATH" +ENV_VAR_PORT = "FLAGD_PORT" +ENV_VAR_RESOLVER_TYPE = "FLAGD_RESOLVER_TYPE" +ENV_VAR_RETRY_BACKOFF_MS = "FLAGD_RETRY_BACKOFF_MS" +ENV_VAR_STREAM_DEADLINE_MS = "FLAGD_STREAM_DEADLINE_MS" +ENV_VAR_TLS = "FLAGD_TLS" + T = typing.TypeVar("T") @@ -18,42 +45,83 @@ def env_or_default( return val if cast is None else cast(val) -class ResolverType(Enum): - GRPC = "grpc" - IN_PROCESS = "in-process" - - class Config: def __init__( # noqa: PLR0913 self, host: typing.Optional[str] = None, port: typing.Optional[int] = None, tls: typing.Optional[bool] = None, - timeout: typing.Optional[int] = None, resolver_type: typing.Optional[ResolverType] = None, offline_flag_source_path: typing.Optional[str] = None, - offline_poll_interval_seconds: typing.Optional[float] = None, + retry_backoff_ms: typing.Optional[int] = None, + deadline: typing.Optional[int] = None, + stream_deadline_ms: typing.Optional[int] = None, + keep_alive_time: typing.Optional[int] = None, ): - self.host = env_or_default("FLAGD_HOST", "localhost") if host is None else host - self.port = ( - env_or_default("FLAGD_PORT", 8013, cast=int) if port is None else port - ) + self.host = env_or_default(ENV_VAR_HOST, DEFAULT_HOST) if host is None else host + self.tls = ( - env_or_default("FLAGD_TLS", False, cast=str_to_bool) if tls is None else tls + env_or_default(ENV_VAR_TLS, DEFAULT_TLS, cast=str_to_bool) + if tls is None + else tls ) - self.timeout = 5 if timeout is None else timeout + + self.retry_backoff_ms: int = ( + int( + env_or_default( + ENV_VAR_RETRY_BACKOFF_MS, DEFAULT_RETRY_BACKOFF, cast=int + ) + ) + if retry_backoff_ms is None + else retry_backoff_ms + ) + self.resolver_type = ( - ResolverType(env_or_default("FLAGD_RESOLVER_TYPE", "grpc")) + ResolverType(env_or_default(ENV_VAR_RESOLVER_TYPE, DEFAULT_RESOLVER_TYPE)) if resolver_type is None else resolver_type ) + + default_port = ( + DEFAULT_PORT_RPC + if self.resolver_type is ResolverType.RPC + else DEFAULT_PORT_IN_PROCESS + ) + + self.port: int = ( + int(env_or_default(ENV_VAR_PORT, default_port, cast=int)) + if port is None + else port + ) + self.offline_flag_source_path = ( - env_or_default("FLAGD_OFFLINE_FLAG_SOURCE_PATH", None) + env_or_default( + ENV_VAR_OFFLINE_FLAG_SOURCE_PATH, DEFAULT_OFFLINE_SOURCE_PATH + ) if offline_flag_source_path is None else offline_flag_source_path ) - self.offline_poll_interval_seconds = ( - float(env_or_default("FLAGD_OFFLINE_POLL_INTERVAL_SECONDS", 1.0)) - if offline_poll_interval_seconds is None - else offline_poll_interval_seconds + + self.deadline: int = ( + int(env_or_default(ENV_VAR_DEADLINE_MS, DEFAULT_DEADLINE, cast=int)) + if deadline is None + else deadline + ) + + self.stream_deadline_ms: int = ( + int( + env_or_default( + ENV_VAR_STREAM_DEADLINE_MS, DEFAULT_STREAM_DEADLINE, cast=int + ) + ) + if stream_deadline_ms is None + else stream_deadline_ms + ) + + self.keep_alive_time: int = ( + int( + env_or_default(ENV_VAR_KEEP_ALIVE_TIME_MS, DEFAULT_KEEP_ALIVE, cast=int) + ) + if keep_alive_time is None + else keep_alive_time ) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py index 76307475..c45b4a86 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py @@ -22,6 +22,7 @@ """ import typing +import warnings from openfeature.evaluation_context import EvaluationContext from openfeature.flag_evaluation import FlagResolutionDetails @@ -42,10 +43,13 @@ def __init__( # noqa: PLR0913 host: typing.Optional[str] = None, port: typing.Optional[int] = None, tls: typing.Optional[bool] = None, + deadline: typing.Optional[int] = None, timeout: typing.Optional[int] = None, + retry_backoff_ms: typing.Optional[int] = None, resolver_type: typing.Optional[ResolverType] = None, offline_flag_source_path: typing.Optional[str] = None, - offline_poll_interval_seconds: typing.Optional[float] = None, + stream_deadline_ms: typing.Optional[int] = None, + keep_alive_time: typing.Optional[int] = None, ): """ Create an instance of the FlagdProvider @@ -53,23 +57,44 @@ def __init__( # noqa: PLR0913 :param host: the host to make requests to :param port: the port the flagd service is available on :param tls: enable/disable secure TLS connectivity - :param timeout: the maximum to wait before a request times out + :param deadline: the maximum to wait before a request times out + :param timeout: the maximum time to wait before a request times out + :param retry_backoff_ms: the number of milliseconds to backoff + :param offline_flag_source_path: the path to the flag source file + :param stream_deadline_ms: the maximum time to wait before a request times out + :param keep_alive_time: the number of milliseconds to keep alive + :param resolver_type: the type of resolver to use """ + if deadline is None and timeout is not None: + deadline = timeout * 1000 + warnings.warn( + "'timeout' property is deprecated, please use 'deadline' instead, be aware that 'deadline' is in milliseconds", + DeprecationWarning, + stacklevel=2, + ) + self.config = Config( host=host, port=port, tls=tls, - timeout=timeout, + deadline=deadline, + retry_backoff_ms=retry_backoff_ms, resolver_type=resolver_type, offline_flag_source_path=offline_flag_source_path, - offline_poll_interval_seconds=offline_poll_interval_seconds, + stream_deadline_ms=stream_deadline_ms, + keep_alive_time=keep_alive_time, ) self.resolver = self.setup_resolver() def setup_resolver(self) -> AbstractResolver: - if self.config.resolver_type == ResolverType.GRPC: - return GrpcResolver(self.config) + if self.config.resolver_type == ResolverType.RPC: + return GrpcResolver( + self.config, + self.emit_provider_ready, + self.emit_provider_error, + self.emit_provider_configuration_changed, + ) elif self.config.resolver_type == ResolverType.IN_PROCESS: return InProcessResolver(self.config, self) else: @@ -77,6 +102,9 @@ def setup_resolver(self) -> AbstractResolver: f"`resolver_type` parameter invalid: {self.config.resolver_type}" ) + def initialize(self, evaluation_context: EvaluationContext) -> None: + self.resolver.initialize(evaluation_context) + def shutdown(self) -> None: if self.resolver: self.resolver.shutdown() diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/__init__.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/__init__.py index d0d46f59..73923abb 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/__init__.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/__init__.py @@ -8,6 +8,8 @@ class AbstractResolver(typing.Protocol): + def initialize(self, evaluation_context: EvaluationContext) -> None: ... + def shutdown(self) -> None: ... def resolve_boolean_details( diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py index f57eef4e..9026e4b2 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py @@ -1,3 +1,6 @@ +import logging +import threading +import time import typing import grpc @@ -5,11 +8,14 @@ from google.protobuf.struct_pb2 import Struct from openfeature.evaluation_context import EvaluationContext +from openfeature.event import ProviderEventDetails from openfeature.exception import ( + ErrorCode, FlagNotFoundError, GeneralError, InvalidContextError, ParseError, + ProviderNotReadyError, TypeMismatchError, ) from openfeature.flag_evaluation import FlagResolutionDetails @@ -26,19 +32,124 @@ T = typing.TypeVar("T") +logger = logging.getLogger("openfeature.contrib") + class GrpcResolver: - def __init__(self, config: Config): + MAX_BACK_OFF = 120 + + def __init__( + self, + config: Config, + emit_provider_ready: typing.Callable[[ProviderEventDetails], None], + emit_provider_error: typing.Callable[[ProviderEventDetails], None], + emit_provider_configuration_changed: typing.Callable[ + [ProviderEventDetails], None + ], + ): self.config = config - channel_factory = ( - grpc.secure_channel if self.config.tls else grpc.insecure_channel + self.emit_provider_ready = emit_provider_ready + self.emit_provider_error = emit_provider_error + self.emit_provider_configuration_changed = emit_provider_configuration_changed + self.stub, self.channel = self._create_stub() + self.retry_backoff_seconds = config.retry_backoff_ms * 0.001 + self.streamline_deadline_seconds = config.stream_deadline_ms * 0.001 + self.deadline = config.deadline * 0.001 + self.connected = False + + def _create_stub( + self, + ) -> typing.Tuple[evaluation_pb2_grpc.ServiceStub, grpc.Channel]: + config = self.config + channel_factory = grpc.secure_channel if config.tls else grpc.insecure_channel + channel = channel_factory( + f"{config.host}:{config.port}", + options=(("grpc.keepalive_time_ms", config.keep_alive_time),), ) - self.channel = channel_factory(f"{self.config.host}:{self.config.port}") - self.stub = evaluation_pb2_grpc.ServiceStub(self.channel) + stub = evaluation_pb2_grpc.ServiceStub(channel) + return stub, channel + + def initialize(self, evaluation_context: EvaluationContext) -> None: + self.connect() def shutdown(self) -> None: + self.active = False self.channel.close() + def connect(self) -> None: + self.active = True + self.thread = threading.Thread( + target=self.listen, daemon=True, name="FlagdGrpcServiceWorkerThread" + ) + self.thread.start() + + ## block until ready or deadline reached + timeout = self.deadline + time.time() + while not self.connected and time.time() < timeout: + time.sleep(0.05) + logger.debug("Finished blocking gRPC state initialization") + + if not self.connected: + raise ProviderNotReadyError( + "Blocking init finished before data synced. Consider increasing startup deadline to avoid inconsistent evaluations." + ) + + def listen(self) -> None: + retry_delay = self.retry_backoff_seconds + + call_args = ( + {"timeout": self.streamline_deadline_seconds} + if self.streamline_deadline_seconds > 0 + else {} + ) + while self.active: + request = evaluation_pb2.EventStreamRequest() + try: + logger.debug("Setting up gRPC sync flags connection") + for message in self.stub.EventStream(request, **call_args): + if message.type == "provider_ready": + if not self.connected: + self.emit_provider_ready( + ProviderEventDetails( + message="gRPC sync connection established" + ) + ) + self.connected = True + # reset retry delay after successsful read + retry_delay = self.retry_backoff_seconds + + elif message.type == "configuration_change": + data = MessageToDict(message)["data"] + self.handle_changed_flags(data) + + if not self.active: + logger.info("Terminating gRPC sync thread") + return + except grpc.RpcError as e: + logger.error(f"SyncFlags stream error, {e.code()=} {e.details()=}") + # re-create the stub if there's a connection issue - otherwise reconnect does not work as expected + self.stub, self.channel = self._create_stub() + except ParseError: + logger.exception( + f"Could not parse flag data using flagd syntax: {message=}" + ) + + self.connected = False + self.emit_provider_error( + ProviderEventDetails( + message=f"gRPC sync disconnected, reconnecting in {retry_delay}s", + error_code=ErrorCode.GENERAL, + ) + ) + logger.info(f"gRPC sync disconnected, reconnecting in {retry_delay}s") + time.sleep(retry_delay) + retry_delay = min(1.1 * retry_delay, self.MAX_BACK_OFF) + + def handle_changed_flags(self, data: typing.Any) -> None: + changed_flags = list(data["flags"].keys()) + + self.emit_provider_configuration_changed(ProviderEventDetails(changed_flags)) + def resolve_boolean_details( self, key: str, @@ -87,7 +198,7 @@ def _resolve( # noqa: PLR0915 evaluation_context: typing.Optional[EvaluationContext], ) -> FlagResolutionDetails[T]: context = self._convert_context(evaluation_context) - call_args = {"timeout": self.config.timeout} + call_args = {"timeout": self.deadline} try: request: Message if flag_type == FlagType.BOOLEAN: diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py index 69b4989a..a14dbb8c 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/in_process.py @@ -3,7 +3,7 @@ from openfeature.evaluation_context import EvaluationContext from openfeature.exception import FlagNotFoundError, ParseError from openfeature.flag_evaluation import FlagResolutionDetails, Reason -from openfeature.provider.provider import AbstractProvider +from openfeature.provider import AbstractProvider from ..config import Config from .process.file_watcher import FileWatcherFlagStore @@ -23,9 +23,12 @@ def __init__(self, config: Config, provider: AbstractProvider): self.flag_store = FileWatcherFlagStore( self.config.offline_flag_source_path, self.provider, - self.config.offline_poll_interval_seconds, + self.config.retry_backoff_ms * 0.001, ) + def initialize(self, evaluation_context: EvaluationContext) -> None: + pass + def shutdown(self) -> None: self.flag_store.shutdown() diff --git a/providers/openfeature-provider-flagd/tests/e2e/conftest.py b/providers/openfeature-provider-flagd/tests/e2e/conftest.py index af14e299..142ec7f0 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/conftest.py +++ b/providers/openfeature-provider-flagd/tests/e2e/conftest.py @@ -5,32 +5,25 @@ from tests.e2e.flagd_container import FlagdContainer from tests.e2e.steps import * # noqa: F403 -from openfeature import api -from openfeature.contrib.provider.flagd import FlagdProvider - JsonPrimitive = typing.Union[str, bool, float, int] TEST_HARNESS_PATH = "../../openfeature/test-harness" SPEC_PATH = "../../openfeature/spec" -@pytest.fixture(autouse=True, scope="package") -def setup(request, port, image, resolver_type): +@pytest.fixture(autouse=True, scope="module") +def setup(request, port, image): container: DockerContainer = FlagdContainer( image=image, port=port, ) # Setup code c = container.start() - api.set_provider( - FlagdProvider( - resolver_type=resolver_type, - port=int(container.get_exposed_port(port)), - ) - ) def fin(): c.stop() # Teardown code request.addfinalizer(fin) + + return c.get_exposed_port(port) diff --git a/providers/openfeature-provider-flagd/tests/e2e/steps.py b/providers/openfeature-provider-flagd/tests/e2e/steps.py index fe490c5f..477d8c25 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/steps.py +++ b/providers/openfeature-provider-flagd/tests/e2e/steps.py @@ -1,3 +1,4 @@ +import logging import time import typing @@ -8,8 +9,9 @@ from openfeature import api from openfeature.client import OpenFeatureClient +from openfeature.contrib.provider.flagd import FlagdProvider from openfeature.evaluation_context import EvaluationContext -from openfeature.event import EventDetails, ProviderEvent +from openfeature.event import ProviderEvent from openfeature.flag_evaluation import ErrorCode, FlagEvaluationDetails, Reason from openfeature.provider import ProviderStatus @@ -24,8 +26,12 @@ def evaluation_context() -> EvaluationContext: @given("a flagd provider is set", target_fixture="client") @given("a provider is registered", target_fixture="client") -def setup_provider() -> OpenFeatureClient: - client = api.get_client() +def setup_provider(setup, resolver_type, client_name) -> OpenFeatureClient: + api.set_provider( + FlagdProvider(resolver_type=resolver_type, port=setup, timeout=1), + client_name, + ) + client = api.get_client(client_name) wait_for(lambda: client.get_provider_status() == ProviderStatus.READY) return client @@ -491,27 +497,35 @@ def assert_reason( assert_equal(evaluation_result.reason, reason) -@when(parsers.cfparse("a PROVIDER_READY handler is added")) -def provider_ready_add(client: OpenFeatureClient, context): - def provider_ready_handler(event_details: EventDetails): - context["provider_ready_ran"] = True +@pytest.fixture() +def event_handles() -> list: + return [] - client.add_handler(ProviderEvent.PROVIDER_READY, provider_ready_handler) +@pytest.fixture() +def error_handles() -> list: + return [] -@then(parsers.cfparse("the PROVIDER_READY handler must run")) -def provider_ready_was_executed(client: OpenFeatureClient, context): - assert_true(context["provider_ready_ran"]) +@when( + parsers.cfparse( + "a {event_type:ProviderEvent} handler is added", + extra_types={"ProviderEvent": ProviderEvent}, + ), +) +def add_event_handler( + client: OpenFeatureClient, event_type: ProviderEvent, event_handles: list +): + def handler(event): + logging.debug((event_type, event)) + event_handles.append( + { + "type": event_type, + "event": event, + } + ) -@when(parsers.cfparse("a PROVIDER_CONFIGURATION_CHANGED handler is added")) -def provider_changed_add(client: OpenFeatureClient, context): - def provider_changed_handler(event_details: EventDetails): - context["provider_changed_ran"] = True - - client.add_handler( - ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, provider_changed_handler - ) + client.add_handler(event_type, handler) @pytest.fixture(scope="function") @@ -519,30 +533,100 @@ def context(): return {} -@when(parsers.cfparse('a flag with key "{flag_key}" is modified')) -def assert_reason2( +@when( + parsers.cfparse( + "a {event_type:ProviderEvent} handler and a {event_type2:ProviderEvent} handler are added", + extra_types={"ProviderEvent": ProviderEvent}, + ) +) +def add_event_handlers( client: OpenFeatureClient, - context, - flag_key: str, + event_type: ProviderEvent, + event_type2: ProviderEvent, + event_handles, + error_handles, ): - context["flag_key"] = flag_key + add_event_handler(client, event_type, event_handles) + add_event_handler(client, event_type2, error_handles) +def assert_handlers( + handles, event_type: ProviderEvent, max_wait: int = 2, num_events: int = 1 +): + poll_interval = 1 + while max_wait > 0: + if sum([h["type"] == event_type for h in handles]) < num_events: + max_wait -= poll_interval + time.sleep(poll_interval) + continue + break + + logging.info(f"asserting num({event_type}) >= {num_events}: {handles}") + actual_num_events = sum([h["type"] == event_type for h in handles]) + assert ( + num_events <= actual_num_events + ), f"Expected {num_events} but got {actual_num_events}: {handles}" + + +@then( + parsers.cfparse( + "the {event_type:ProviderEvent} handler must run", + extra_types={"ProviderEvent": ProviderEvent}, + ) +) @then( - parsers.cfparse("the PROVIDER_CONFIGURATION_CHANGED handler must run"), + parsers.cfparse( + "the {event_type:ProviderEvent} handler must run when the provider connects", + extra_types={"ProviderEvent": ProviderEvent}, + ) ) -def provider_changed_was_executed(client: OpenFeatureClient, context): - wait_for(lambda: context.get("provider_changed_ran")) - assert_equal(context["provider_changed_ran"], True) +def assert_handler_run(event_type: ProviderEvent, event_handles): + assert_handlers(event_handles, event_type, max_wait=6) -@then(parsers.cfparse('the event details must indicate "{flag_name}" was altered')) -def flag_was_changed( - flag_name: str, - context, +@then( + parsers.cfparse( + "the {event_type:ProviderEvent} handler must run when the provider's connection is lost", + extra_types={"ProviderEvent": ProviderEvent}, + ) +) +def assert_disconnect_handler(error_handles, event_type: ProviderEvent): + # docker sync upstream restarts every 5s, waiting 2 cycles reduces test noise + assert_handlers(error_handles, event_type, max_wait=30) + + +@when( + parsers.cfparse('a flag with key "{flag_key}" is modified'), + target_fixture="changed_flag", +) +def changed_flag( + flag_key: str, +): + return flag_key + + +@then( + parsers.cfparse( + "when the connection is reestablished the {event_type:ProviderEvent} handler must run again", + extra_types={"ProviderEvent": ProviderEvent}, + ) +) +def assert_disconnect_error( + client: OpenFeatureClient, event_type: ProviderEvent, event_handles: list ): - wait_for(lambda: flag_name in context.get("changed_flags")) - assert_in(flag_name, context.get("changed_flags")) + assert_handlers(event_handles, event_type, max_wait=30, num_events=2) + + +@then(parsers.cfparse('the event details must indicate "{key}" was altered')) +def assert_flag_changed(event_handles, key): + handle = None + for h in event_handles: + if h["type"] == ProviderEvent.PROVIDER_CONFIGURATION_CHANGED: + handle = h + break + + assert handle is not None + assert key in handle["event"].flags_changed def wait_for(pred, poll_sec=2, timeout_sec=10): @@ -551,3 +635,26 @@ def wait_for(pred, poll_sec=2, timeout_sec=10): time.sleep(poll_sec) assert_true(pred()) return ok + + +@given("flagd is unavailable", target_fixture="client") +def flagd_unavailable(resolver_type): + api.set_provider( + FlagdProvider( + resolver_type=resolver_type, + port=99999, + ), + "unavailable", + ) + return api.get_client("unavailable") + + +@when("a flagd provider is set and initialization is awaited") +def flagd_init(client: OpenFeatureClient, event_handles, error_handles): + add_event_handler(client, ProviderEvent.PROVIDER_ERROR, error_handles) + add_event_handler(client, ProviderEvent.PROVIDER_READY, event_handles) + + +@then("an error should be indicated within the configured deadline") +def flagd_error(error_handles): + assert_handlers(error_handles, ProviderEvent.PROVIDER_ERROR) diff --git a/providers/openfeature-provider-flagd/tests/e2e/test_in-process-file.py b/providers/openfeature-provider-flagd/tests/e2e/test_in-process-file.py index 9f1568a4..86be937d 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/test_in-process-file.py +++ b/providers/openfeature-provider-flagd/tests/e2e/test_in-process-file.py @@ -5,12 +5,15 @@ import pytest import yaml -from pytest_bdd import scenario, scenarios +from pytest_bdd import given, scenario, scenarios from tests.e2e.conftest import SPEC_PATH, TEST_HARNESS_PATH +from tests.e2e.steps import wait_for from openfeature import api +from openfeature.client import OpenFeatureClient from openfeature.contrib.provider.flagd import FlagdProvider from openfeature.contrib.provider.flagd.config import ResolverType +from openfeature.provider import ProviderStatus KEY_EVALUATORS = "$evaluators" @@ -19,7 +22,7 @@ MERGED_FILE = "merged_file" -@pytest.fixture(params=["json", "yaml"], scope="package") +@pytest.fixture(params=["json", "yaml"], scope="module") def file_name(request): extension = request.param result = {KEY_FLAGS: {}, KEY_EVALUATORS: {}} @@ -49,17 +52,36 @@ def file_name(request): return outfile -@pytest.fixture(autouse=True, scope="package") -def setup(request, file_name): - """`file_name` tests""" +@pytest.fixture(autouse=True, scope="module") +def client_name() -> str: + return "in-process" + + +@pytest.fixture(autouse=True, scope="module") +def resolver_type() -> ResolverType: + return ResolverType.IN_PROCESS + + +@pytest.fixture(autouse=True, scope="module") +def setup(request, client_name, file_name, resolver_type): + """nothing to boot""" api.set_provider( FlagdProvider( - resolver_type=ResolverType.IN_PROCESS, + resolver_type=resolver_type, offline_flag_source_path=file_name.name, - ) + ), + client_name, ) +@given("a flagd provider is set", target_fixture="client") +@given("a provider is registered", target_fixture="client") +def setup_provider(client_name) -> OpenFeatureClient: + client = api.get_client(client_name) + wait_for(lambda: client.get_provider_status() == ProviderStatus.READY) + return client + + @pytest.mark.skip(reason="Eventing not implemented") @scenario(f"{TEST_HARNESS_PATH}/gherkin/flagd.feature", "Flag change event") def test_flag_change_event(): diff --git a/providers/openfeature-provider-flagd/tests/e2e/test_rpc.py b/providers/openfeature-provider-flagd/tests/e2e/test_rpc.py index 2a5d1c15..0a939d5a 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/test_rpc.py +++ b/providers/openfeature-provider-flagd/tests/e2e/test_rpc.py @@ -1,31 +1,30 @@ import pytest -from pytest_bdd import scenario, scenarios +from pytest_bdd import scenarios from tests.e2e.conftest import SPEC_PATH, TEST_HARNESS_PATH from openfeature.contrib.provider.flagd.config import ResolverType -@pytest.fixture(autouse=True, scope="package") +@pytest.fixture(autouse=True, scope="module") +def client_name() -> str: + return "rpc" + + +@pytest.fixture(autouse=True, scope="module") def resolver_type() -> ResolverType: - return ResolverType.GRPC + return ResolverType.RPC -@pytest.fixture(autouse=True, scope="package") +@pytest.fixture(autouse=True, scope="module") def port(): return 8013 -@pytest.fixture(autouse=True, scope="package") +@pytest.fixture(autouse=True, scope="module") def image(): return "ghcr.io/open-feature/flagd-testbed:v0.5.13" -@pytest.mark.skip(reason="Eventing not implemented") -@scenario(f"{TEST_HARNESS_PATH}/gherkin/flagd.feature", "Flag change event") -def test_flag_change_event(): - """not implemented""" - - scenarios( f"{TEST_HARNESS_PATH}/gherkin/flagd.feature", f"{TEST_HARNESS_PATH}/gherkin/flagd-json-evaluator.feature", diff --git a/providers/openfeature-provider-flagd/tests/e2e/test_rpc_reconnect.py b/providers/openfeature-provider-flagd/tests/e2e/test_rpc_reconnect.py new file mode 100644 index 00000000..b99df2be --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/e2e/test_rpc_reconnect.py @@ -0,0 +1,30 @@ +import pytest +from pytest_bdd import scenarios +from tests.e2e.conftest import TEST_HARNESS_PATH + +from openfeature.contrib.provider.flagd.config import ResolverType + + +@pytest.fixture(autouse=True, scope="module") +def client_name() -> str: + return "rpc-reconnect" + + +@pytest.fixture(autouse=True, scope="module") +def resolver_type() -> ResolverType: + return ResolverType.RPC + + +@pytest.fixture(autouse=True, scope="module") +def port(): + return 8013 + + +@pytest.fixture(autouse=True, scope="module") +def image(): + return "ghcr.io/open-feature/flagd-testbed-unstable:v0.5.13" + + +scenarios( + f"{TEST_HARNESS_PATH}/gherkin/flagd-reconnect.feature", +) diff --git a/providers/openfeature-provider-flagd/tests/test_config.py b/providers/openfeature-provider-flagd/tests/test_config.py index 1fb0c720..54d7ee3a 100644 --- a/providers/openfeature-provider-flagd/tests/test_config.py +++ b/providers/openfeature-provider-flagd/tests/test_config.py @@ -1,29 +1,130 @@ -from openfeature.contrib.provider.flagd.config import Config +import pytest +from openfeature.contrib.provider.flagd.config import ( + DEFAULT_DEADLINE, + DEFAULT_HOST, + DEFAULT_KEEP_ALIVE, + DEFAULT_OFFLINE_SOURCE_PATH, + DEFAULT_PORT_IN_PROCESS, + DEFAULT_PORT_RPC, + DEFAULT_RESOLVER_TYPE, + DEFAULT_RETRY_BACKOFF, + DEFAULT_STREAM_DEADLINE, + DEFAULT_TLS, + ENV_VAR_DEADLINE_MS, + ENV_VAR_HOST, + ENV_VAR_KEEP_ALIVE_TIME_MS, + ENV_VAR_OFFLINE_FLAG_SOURCE_PATH, + ENV_VAR_PORT, + ENV_VAR_RESOLVER_TYPE, + ENV_VAR_RETRY_BACKOFF_MS, + ENV_VAR_STREAM_DEADLINE_MS, + ENV_VAR_TLS, + Config, + ResolverType, +) -def test_return_default_values(): + +def test_return_default_values_rpc(): config = Config() - assert config.host == "localhost" - assert config.port == 8013 - assert config.tls is False - assert config.timeout == 5 + assert config.deadline == DEFAULT_DEADLINE + assert config.host == DEFAULT_HOST + assert config.keep_alive_time == DEFAULT_KEEP_ALIVE + assert config.offline_flag_source_path == DEFAULT_OFFLINE_SOURCE_PATH + assert config.port == DEFAULT_PORT_RPC + assert config.resolver_type == DEFAULT_RESOLVER_TYPE + assert config.retry_backoff_ms == DEFAULT_RETRY_BACKOFF + assert config.stream_deadline_ms == DEFAULT_STREAM_DEADLINE + assert config.tls is DEFAULT_TLS + + +def test_return_default_values_in_process(): + config = Config(resolver_type=ResolverType.IN_PROCESS) + assert config.deadline == DEFAULT_DEADLINE + assert config.host == DEFAULT_HOST + assert config.keep_alive_time == DEFAULT_KEEP_ALIVE + assert config.offline_flag_source_path == DEFAULT_OFFLINE_SOURCE_PATH + assert config.port == DEFAULT_PORT_IN_PROCESS + assert config.resolver_type == ResolverType.IN_PROCESS + assert config.retry_backoff_ms == DEFAULT_RETRY_BACKOFF + assert config.stream_deadline_ms == DEFAULT_STREAM_DEADLINE + assert config.tls is DEFAULT_TLS + +@pytest.fixture(params=ResolverType, scope="module") +def resolver_type(request): + return request.param -def test_overrides_defaults_with_environment(monkeypatch): - monkeypatch.setenv("FLAGD_HOST", "flagd") - monkeypatch.setenv("FLAGD_PORT", "1234") - monkeypatch.setenv("FLAGD_TLS", "true") + +def test_overrides_defaults_with_environment(monkeypatch, resolver_type): + deadline = 1 + host = "flagd" + keep_alive = 2 + offline_path = "path" + port = 1234 + retry_backoff = 3 + stream_deadline = 4 + tls = True + + monkeypatch.setenv(ENV_VAR_DEADLINE_MS, str(deadline)) + monkeypatch.setenv(ENV_VAR_HOST, host) + monkeypatch.setenv(ENV_VAR_KEEP_ALIVE_TIME_MS, str(keep_alive)) + monkeypatch.setenv(ENV_VAR_OFFLINE_FLAG_SOURCE_PATH, offline_path) + monkeypatch.setenv(ENV_VAR_PORT, str(port)) + monkeypatch.setenv(ENV_VAR_RESOLVER_TYPE, str(resolver_type.value)) + monkeypatch.setenv(ENV_VAR_RETRY_BACKOFF_MS, str(retry_backoff)) + monkeypatch.setenv(ENV_VAR_STREAM_DEADLINE_MS, str(stream_deadline)) + monkeypatch.setenv(ENV_VAR_TLS, str(tls)) config = Config() - assert config.host == "flagd" - assert config.port == 1234 - assert config.tls is True + assert config.deadline == deadline + assert config.host == host + assert config.keep_alive_time == keep_alive + assert config.offline_flag_source_path == offline_path + assert config.port == port + assert config.resolver_type == resolver_type + assert config.retry_backoff_ms == retry_backoff + assert config.stream_deadline_ms == stream_deadline + assert config.tls is tls + +def test_uses_arguments_over_environments_and_defaults(monkeypatch, resolver_type): + deadline = 1 + host = "flagd" + keep_alive = 2 + offline_path = "path" + port = 1234 + retry_backoff = 3 + stream_deadline = 4 + tls = True -def test_uses_arguments_over_environments_and_defaults(monkeypatch): - monkeypatch.setenv("FLAGD_HOST", "flagd") + monkeypatch.setenv(ENV_VAR_DEADLINE_MS, str(deadline) + "value") + monkeypatch.setenv(ENV_VAR_HOST, host + "value") + monkeypatch.setenv(ENV_VAR_KEEP_ALIVE_TIME_MS, str(keep_alive) + "value") + monkeypatch.setenv(ENV_VAR_OFFLINE_FLAG_SOURCE_PATH, offline_path + "value") + monkeypatch.setenv(ENV_VAR_PORT, str(port) + "value") + monkeypatch.setenv(ENV_VAR_RESOLVER_TYPE, str(resolver_type) + "value") + monkeypatch.setenv(ENV_VAR_RETRY_BACKOFF_MS, str(retry_backoff) + "value") + monkeypatch.setenv(ENV_VAR_STREAM_DEADLINE_MS, str(stream_deadline) + "value") + monkeypatch.setenv(ENV_VAR_TLS, str(tls) + "value") - config = Config(host="flagd2", port=12345, tls=True) - assert config.host == "flagd2" - assert config.port == 12345 - assert config.tls is True + config = Config( + deadline=deadline, + host=host, + port=port, + resolver_type=resolver_type, + retry_backoff_ms=retry_backoff, + stream_deadline_ms=stream_deadline, + tls=tls, + keep_alive_time=keep_alive, + offline_flag_source_path=offline_path, + ) + assert config.deadline == deadline + assert config.host == host + assert config.keep_alive_time == keep_alive + assert config.offline_flag_source_path == offline_path + assert config.port == port + assert config.resolver_type == resolver_type + assert config.retry_backoff_ms == retry_backoff + assert config.stream_deadline_ms == stream_deadline + assert config.tls is tls From 266d2f102ccb982c7a88c58a3401058a7a494394 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Thu, 28 Nov 2024 19:15:40 +0100 Subject: [PATCH 5/7] feat(flagd-rpc): add caching with tests Signed-off-by: Simon Schrottner --- .../openfeature-provider-flagd/pyproject.toml | 2 + .../openfeature-provider-flagd/pytest.ini | 10 + .../contrib/provider/flagd/config.py | 45 ++++- .../contrib/provider/flagd/provider.py | 8 +- .../contrib/provider/flagd/resolvers/grpc.py | 42 +++- .../tests/e2e/config.feature | 189 ++++++++++++++++++ .../tests/e2e/conftest.py | 9 + .../tests/e2e/rpc_cache.feature | 44 ++++ .../tests/e2e/steps.py | 10 + .../tests/e2e/test_config.py | 100 +++++++++ ...rocess-file.py => test_in_process_file.py} | 3 +- .../tests/e2e/test_rpc.py | 1 + .../tests/test_config.py | 37 +++- 13 files changed, 477 insertions(+), 23 deletions(-) create mode 100644 providers/openfeature-provider-flagd/pytest.ini create mode 100644 providers/openfeature-provider-flagd/tests/e2e/config.feature create mode 100644 providers/openfeature-provider-flagd/tests/e2e/rpc_cache.feature create mode 100644 providers/openfeature-provider-flagd/tests/e2e/test_config.py rename providers/openfeature-provider-flagd/tests/e2e/{test_in-process-file.py => test_in_process_file.py} (96%) diff --git a/providers/openfeature-provider-flagd/pyproject.toml b/providers/openfeature-provider-flagd/pyproject.toml index 7e74e810..738ba02a 100644 --- a/providers/openfeature-provider-flagd/pyproject.toml +++ b/providers/openfeature-provider-flagd/pyproject.toml @@ -24,6 +24,7 @@ dependencies = [ "panzi-json-logic>=1.0.1", "semver>=3,<4", "pyyaml>=6.0.1", + "cachebox" ] requires-python = ">=3.8" @@ -59,6 +60,7 @@ cov = [ "cov-report", ] + [tool.hatch.envs.mypy] dependencies = [ "mypy[faster-cache]>=1.13.0", diff --git a/providers/openfeature-provider-flagd/pytest.ini b/providers/openfeature-provider-flagd/pytest.ini new file mode 100644 index 00000000..66da895f --- /dev/null +++ b/providers/openfeature-provider-flagd/pytest.ini @@ -0,0 +1,10 @@ +[pytest] +markers = + rpc: tests for rpc mode. + in-process: tests for rpc mode. + customCert: Supports custom certs. + unixsocket: Supports unixsockets. + events: Supports events. + sync: Supports sync. + caching: Supports caching. + offline: Supports offline. diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py index a393d270..1bb73ece 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py @@ -8,6 +8,13 @@ class ResolverType(Enum): IN_PROCESS = "in-process" +class CacheType(Enum): + LRU = "lru" + DISABLED = "disabled" + + +DEFAULT_CACHE = CacheType.LRU +DEFAULT_CACHE_SIZE = 1000 DEFAULT_DEADLINE = 500 DEFAULT_HOST = "localhost" DEFAULT_KEEP_ALIVE = 0 @@ -19,12 +26,14 @@ class ResolverType(Enum): DEFAULT_STREAM_DEADLINE = 600000 DEFAULT_TLS = False +ENV_VAR_CACHE_SIZE = "FLAGD_MAX_CACHE_SIZE" +ENV_VAR_CACHE_TYPE = "FLAGD_CACHE" ENV_VAR_DEADLINE_MS = "FLAGD_DEADLINE_MS" ENV_VAR_HOST = "FLAGD_HOST" ENV_VAR_KEEP_ALIVE_TIME_MS = "FLAGD_KEEP_ALIVE_TIME_MS" ENV_VAR_OFFLINE_FLAG_SOURCE_PATH = "FLAGD_OFFLINE_FLAG_SOURCE_PATH" ENV_VAR_PORT = "FLAGD_PORT" -ENV_VAR_RESOLVER_TYPE = "FLAGD_RESOLVER_TYPE" +ENV_VAR_RESOLVER_TYPE = "FLAGD_RESOLVER" ENV_VAR_RETRY_BACKOFF_MS = "FLAGD_RETRY_BACKOFF_MS" ENV_VAR_STREAM_DEADLINE_MS = "FLAGD_STREAM_DEADLINE_MS" ENV_VAR_TLS = "FLAGD_TLS" @@ -36,6 +45,14 @@ def str_to_bool(val: str) -> bool: return val.lower() == "true" +def convert_resolver_type(val: typing.Union[str, ResolverType]) -> ResolverType: + if isinstance(val, str): + v = val.lower() + return ResolverType(v) + else: + return ResolverType(val) + + def env_or_default( env_var: str, default: T, cast: typing.Optional[typing.Callable[[str], T]] = None ) -> typing.Union[str, T]: @@ -56,7 +73,9 @@ def __init__( # noqa: PLR0913 retry_backoff_ms: typing.Optional[int] = None, deadline: typing.Optional[int] = None, stream_deadline_ms: typing.Optional[int] = None, - keep_alive_time: typing.Optional[int] = None, + keep_alive: typing.Optional[int] = None, + cache_type: typing.Optional[CacheType] = None, + max_cache_size: typing.Optional[int] = None, ): self.host = env_or_default(ENV_VAR_HOST, DEFAULT_HOST) if host is None else host @@ -77,7 +96,9 @@ def __init__( # noqa: PLR0913 ) self.resolver_type = ( - ResolverType(env_or_default(ENV_VAR_RESOLVER_TYPE, DEFAULT_RESOLVER_TYPE)) + env_or_default( + ENV_VAR_RESOLVER_TYPE, DEFAULT_RESOLVER_TYPE, cast=convert_resolver_type + ) if resolver_type is None else resolver_type ) @@ -118,10 +139,22 @@ def __init__( # noqa: PLR0913 else stream_deadline_ms ) - self.keep_alive_time: int = ( + self.keep_alive: int = ( int( env_or_default(ENV_VAR_KEEP_ALIVE_TIME_MS, DEFAULT_KEEP_ALIVE, cast=int) ) - if keep_alive_time is None - else keep_alive_time + if keep_alive is None + else keep_alive + ) + + self.cache_type = ( + CacheType(env_or_default(ENV_VAR_CACHE_TYPE, DEFAULT_CACHE)) + if cache_type is None + else cache_type + ) + + self.max_cache_size: int = ( + int(env_or_default(ENV_VAR_CACHE_SIZE, DEFAULT_CACHE_SIZE, cast=int)) + if max_cache_size is None + else max_cache_size ) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py index c45b4a86..35fe2059 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py @@ -29,7 +29,7 @@ from openfeature.provider.metadata import Metadata from openfeature.provider.provider import AbstractProvider -from .config import Config, ResolverType +from .config import CacheType, Config, ResolverType from .resolvers import AbstractResolver, GrpcResolver, InProcessResolver T = typing.TypeVar("T") @@ -50,6 +50,8 @@ def __init__( # noqa: PLR0913 offline_flag_source_path: typing.Optional[str] = None, stream_deadline_ms: typing.Optional[int] = None, keep_alive_time: typing.Optional[int] = None, + cache_type: typing.Optional[CacheType] = None, + max_cache_size: typing.Optional[int] = None, ): """ Create an instance of the FlagdProvider @@ -82,7 +84,9 @@ def __init__( # noqa: PLR0913 resolver_type=resolver_type, offline_flag_source_path=offline_flag_source_path, stream_deadline_ms=stream_deadline_ms, - keep_alive_time=keep_alive_time, + keep_alive=keep_alive_time, + cache_type=cache_type, + max_cache_size=max_cache_size, ) self.resolver = self.setup_resolver() diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py index 9026e4b2..7e2f1600 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py @@ -4,6 +4,7 @@ import typing import grpc +from cachebox import BaseCacheImpl, LRUCache from google.protobuf.json_format import MessageToDict from google.protobuf.struct_pb2 import Struct @@ -18,13 +19,13 @@ ProviderNotReadyError, TypeMismatchError, ) -from openfeature.flag_evaluation import FlagResolutionDetails +from openfeature.flag_evaluation import FlagResolutionDetails, Reason from openfeature.schemas.protobuf.flagd.evaluation.v1 import ( evaluation_pb2, evaluation_pb2_grpc, ) -from ..config import Config +from ..config import CacheType, Config from ..flag_type import FlagType if typing.TYPE_CHECKING: @@ -57,6 +58,12 @@ def __init__( self.deadline = config.deadline * 0.001 self.connected = False + self._cache: typing.Optional[BaseCacheImpl] = ( + LRUCache(maxsize=self.config.max_cache_size) + if self.config.cache_type == CacheType.LRU + else None + ) + def _create_stub( self, ) -> typing.Tuple[evaluation_pb2_grpc.ServiceStub, grpc.Channel]: @@ -64,17 +71,27 @@ def _create_stub( channel_factory = grpc.secure_channel if config.tls else grpc.insecure_channel channel = channel_factory( f"{config.host}:{config.port}", - options=(("grpc.keepalive_time_ms", config.keep_alive_time),), + options=(("grpc.keepalive_time_ms", config.keep_alive),), ) stub = evaluation_pb2_grpc.ServiceStub(channel) return stub, channel def initialize(self, evaluation_context: EvaluationContext) -> None: self.connect() + self.retry_backoff_seconds = 0.1 + self.connected = False + + self._cache = ( + LRUCache(maxsize=self.config.max_cache_size) + if self.config.cache_type == CacheType.LRU + else None + ) def shutdown(self) -> None: self.active = False self.channel.close() + if self._cache: + self._cache.clear() def connect(self) -> None: self.active = True @@ -96,7 +113,6 @@ def connect(self) -> None: def listen(self) -> None: retry_delay = self.retry_backoff_seconds - call_args = ( {"timeout": self.streamline_deadline_seconds} if self.streamline_deadline_seconds > 0 @@ -148,6 +164,10 @@ def listen(self) -> None: def handle_changed_flags(self, data: typing.Any) -> None: changed_flags = list(data["flags"].keys()) + if self._cache: + for flag in changed_flags: + self._cache.pop(flag) + self.emit_provider_configuration_changed(ProviderEventDetails(changed_flags)) def resolve_boolean_details( @@ -190,13 +210,18 @@ def resolve_object_details( ) -> FlagResolutionDetails[typing.Union[dict, list]]: return self._resolve(key, FlagType.OBJECT, default_value, evaluation_context) - def _resolve( # noqa: PLR0915 + def _resolve( # noqa: PLR0915 C901 self, flag_key: str, flag_type: FlagType, default_value: T, evaluation_context: typing.Optional[EvaluationContext], ) -> FlagResolutionDetails[T]: + if self._cache is not None and flag_key in self._cache: + cached_flag: FlagResolutionDetails[T] = self._cache[flag_key] + cached_flag.reason = Reason.CACHED + return cached_flag + context = self._convert_context(evaluation_context) call_args = {"timeout": self.deadline} try: @@ -249,12 +274,17 @@ def _resolve( # noqa: PLR0915 raise GeneralError(message) from e # Got a valid flag and valid type. Return it. - return FlagResolutionDetails( + result = FlagResolutionDetails( value=value, reason=response.reason, variant=response.variant, ) + if response.reason == Reason.STATIC and self._cache is not None: + self._cache.insert(flag_key, result) + + return result + def _convert_context( self, evaluation_context: typing.Optional[EvaluationContext] ) -> Struct: diff --git a/providers/openfeature-provider-flagd/tests/e2e/config.feature b/providers/openfeature-provider-flagd/tests/e2e/config.feature new file mode 100644 index 00000000..f2bd715e --- /dev/null +++ b/providers/openfeature-provider-flagd/tests/e2e/config.feature @@ -0,0 +1,189 @@ +Feature: Configuration Test + + @rpc @in-process + Scenario Outline: Default Config + When we initialize a config + Then the option "