openfeature-provider-flagd 0.2.5__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/.gitignore +3 -0
  2. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/CHANGELOG.md +26 -0
  3. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/LICENSE +1 -1
  4. openfeature_provider_flagd-0.2.7/PKG-INFO +208 -0
  5. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/README.md +39 -0
  6. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/pyproject.toml +35 -52
  7. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/provider.py +10 -3
  8. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/grpc.py +82 -27
  9. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/in_process.py +49 -20
  10. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py +70 -38
  11. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/flags.py +11 -14
  12. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/targeting.py +9 -1
  13. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/protocol.py +7 -3
  14. openfeature_provider_flagd-0.2.7/src/openfeature/contrib/provider/flagd/resolvers/types.py +7 -0
  15. openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +72 -0
  16. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.pyi +41 -22
  17. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.py +2 -2
  18. openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +310 -0
  19. openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +56 -0
  20. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.pyi +33 -10
  21. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.py +2 -2
  22. openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +161 -0
  23. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.py +2 -2
  24. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2.pyi +25 -19
  25. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.py +2 -2
  26. openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +311 -0
  27. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.py +2 -2
  28. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2.pyi +9 -9
  29. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.py +2 -2
  30. openfeature_provider_flagd-0.2.7/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +125 -0
  31. openfeature_provider_flagd-0.2.7/src/scripts/scripts.py +28 -0
  32. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/flagd_container.py +5 -11
  33. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/context_steps.py +12 -0
  34. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/flag_step.py +20 -1
  35. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/provider_steps.py +3 -0
  36. openfeature_provider_flagd-0.2.7/tests/flags/basic-flag-without-default.json +12 -0
  37. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/test_errors.py +33 -3
  38. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/test_file_store.py +1 -0
  39. openfeature_provider_flagd-0.2.7/tests/test_grpc_watcher.py +163 -0
  40. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/test_in_process.py +2 -2
  41. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/test_targeting.py +1 -1
  42. openfeature_provider_flagd-0.2.5/PKG-INFO +0 -370
  43. openfeature_provider_flagd-0.2.5/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2.py +0 -72
  44. openfeature_provider_flagd-0.2.5/src/openfeature/schemas/protobuf/flagd/evaluation/v1/evaluation_pb2_grpc.pyi +0 -155
  45. openfeature_provider_flagd-0.2.5/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2.py +0 -50
  46. openfeature_provider_flagd-0.2.5/src/openfeature/schemas/protobuf/flagd/sync/v1/sync_pb2_grpc.pyi +0 -86
  47. openfeature_provider_flagd-0.2.5/src/openfeature/schemas/protobuf/schema/v1/schema_pb2_grpc.pyi +0 -156
  48. openfeature_provider_flagd-0.2.5/src/openfeature/schemas/protobuf/sync/v1/sync_service_pb2_grpc.pyi +0 -70
  49. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/pytest.ini +0 -0
  50. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/__init__.py +0 -0
  51. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/config.py +0 -0
  52. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/flag_type.py +0 -0
  53. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/__init__.py +0 -0
  54. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/__init__.py +0 -0
  55. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/connector/file_watcher.py +0 -0
  56. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py +0 -0
  57. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/src/openfeature/contrib/provider/flagd/sync_metadata_hook.py +0 -0
  58. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/__init__.py +0 -0
  59. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/conftest.py +0 -0
  60. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/__init__.py +0 -0
  61. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/conftest.py +0 -0
  62. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/file/__init__.py +0 -0
  63. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/file/conftest.py +0 -0
  64. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/file/test_flaqd.py +0 -0
  65. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/inprocess/__init__.py +0 -0
  66. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/inprocess/conftest.py +0 -0
  67. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/inprocess/test_flaqd.py +0 -0
  68. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/parsers.py +0 -0
  69. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/paths.py +0 -0
  70. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/rpc/__init__.py +0 -0
  71. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/rpc/conftest.py +0 -0
  72. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/rpc/test_flaqd.py +0 -0
  73. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/_utils.py +0 -0
  74. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/config_steps.py +0 -0
  75. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/step/event_steps.py +0 -0
  76. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/e2e/testfilter.py +0 -0
  77. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-broken-default.json +0 -0
  78. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-broken-state.json +0 -0
  79. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-broken-targeting.json +0 -0
  80. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-broken-variants.json +0 -0
  81. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-combined-metadata.json +0 -0
  82. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-disabled.json +0 -0
  83. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-invalid.not-json +0 -0
  84. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-metadata.json +0 -0
  85. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-no-state.json +0 -0
  86. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-set-metadata.json +0 -0
  87. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-wrong-structure.json +0 -0
  88. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag-wrong-variant.json +0 -0
  89. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag.json +0 -0
  90. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/basic-flag.yaml +0 -0
  91. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-flag-metadata-list.json +0 -0
  92. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-flag-metadata.json +0 -0
  93. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-flag-set-metadata-list.json +0 -0
  94. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-flag-set-metadata.json +0 -0
  95. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-fractional-args-wrong-content.json +0 -0
  96. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-fractional-args.json +0 -0
  97. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-fractional-weights-strings.json +0 -0
  98. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-fractional-weights.json +0 -0
  99. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-semver-args.json +0 -0
  100. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-semver-op.json +0 -0
  101. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/flags/invalid-stringcomp-args.json +0 -0
  102. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/test_config.py +0 -0
  103. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/test_flagd.py +0 -0
  104. {openfeature_provider_flagd-0.2.5 → openfeature_provider_flagd-0.2.7}/tests/test_metadata.py +0 -0
@@ -54,3 +54,6 @@ docs/_build/
54
54
 
55
55
  # vscode
56
56
  .vscode/
57
+
58
+ # test coverage
59
+ /providers/openfeature-provider-env-var/tests-results.xml
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.7](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.6...openfeature-provider-flagd/v0.2.7) (2026-01-31)
4
+
5
+
6
+ ### 🐛 Bug Fixes
7
+
8
+ * **build:** bump protobuf package version to a compatible version ([#327](https://github.com/open-feature/python-sdk-contrib/issues/327)) ([223a903](https://github.com/open-feature/python-sdk-contrib/commit/223a9037cea6c7ac6b1c9bb7a159987e592ce5a4))
9
+
10
+
11
+ ### ✨ New Features
12
+
13
+ * adjust to flagd metadata toggle ([#287](https://github.com/open-feature/python-sdk-contrib/issues/287)) ([6ac014d](https://github.com/open-feature/python-sdk-contrib/commit/6ac014d1af7a90b09203ae38dfd60bba275bc9a9))
14
+ * **flagd:** Implement dual-mode selector handling (header and body) for in-process mode in python-sdk-contrib ([#320](https://github.com/open-feature/python-sdk-contrib/issues/320)) ([9ad25a4](https://github.com/open-feature/python-sdk-contrib/commit/9ad25a4ddebb91e4383b108d478288a782ede796))
15
+ * **flagd:** support Python 3.14 for flagd ([#307](https://github.com/open-feature/python-sdk-contrib/issues/307)) ([a60b237](https://github.com/open-feature/python-sdk-contrib/commit/a60b23756fce05220201a0d0660be2a06a6807f4))
16
+ * migrate to uv ([#293](https://github.com/open-feature/python-sdk-contrib/issues/293)) ([9590554](https://github.com/open-feature/python-sdk-contrib/commit/9590554be4c8a7f77b0c0f20d6b48076c9870f52))
17
+ * Update python-sdk ([#289](https://github.com/open-feature/python-sdk-contrib/issues/289)) ([f2028f5](https://github.com/open-feature/python-sdk-contrib/commit/f2028f5f81824310a842507a3127a3bb9bc8a984))
18
+ * Use time.monotonic to avoid endless loop when using time machine ([#330](https://github.com/open-feature/python-sdk-contrib/issues/330)) ([a424cdc](https://github.com/open-feature/python-sdk-contrib/commit/a424cdc68cba4dd3da28a9b18ca10ffbaf1e2689))
19
+
20
+ ## [0.2.6](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.5...openfeature-provider-flagd/v0.2.6) (2025-07-22)
21
+
22
+
23
+ ### 🐛 Bug Fixes
24
+
25
+ * **flagd:** Add upperbound to protobuf version to reduce potential conflict with other version ([5ad2896](https://github.com/open-feature/python-sdk-contrib/commit/5ad289683a0d9128a53d142d2804f9fffb9dd36f))
26
+ * **flagd:** adjust flagd to updated error codes ([#285](https://github.com/open-feature/python-sdk-contrib/issues/285)) ([64d755b](https://github.com/open-feature/python-sdk-contrib/commit/64d755b869a076216a91b2d409b1ba5d627ebe08))
27
+ * **flagd:** fix protobuf version for file generation to lowest 5.x version ([#280](https://github.com/open-feature/python-sdk-contrib/issues/280)) ([5ad2896](https://github.com/open-feature/python-sdk-contrib/commit/5ad289683a0d9128a53d142d2804f9fffb9dd36f))
28
+
3
29
  ## [0.2.5](https://github.com/open-feature/python-sdk-contrib/compare/openfeature-provider-flagd/v0.2.4...openfeature-provider-flagd/v0.2.5) (2025-07-08)
4
30
 
5
31
 
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright [yyyy] [name of copyright owner]
189
+ Copyright OpenFeature Maintainers
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.4
2
+ Name: openfeature-provider-flagd
3
+ Version: 0.2.7
4
+ Summary: OpenFeature provider for the flagd flag evaluation engine
5
+ Project-URL: Homepage, https://github.com/open-feature/python-sdk-contrib
6
+ Author-email: OpenFeature <openfeature-core@groups.io>
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
9
+ Classifier: Programming Language :: Python
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.9
12
+ Requires-Dist: cachebox<6.0.0,>=5.1.0
13
+ Requires-Dist: grpcio>=1.76.0
14
+ Requires-Dist: mmh3<6.0.0,>=5.0.0
15
+ Requires-Dist: openfeature-sdk>=0.8.2
16
+ Requires-Dist: panzi-json-logic>=1.0.1
17
+ Requires-Dist: protobuf<7.0.0,>=6.30.0
18
+ Requires-Dist: pyyaml>=6.0.1
19
+ Requires-Dist: semver<4,>=3
20
+ Description-Content-Type: text/markdown
21
+
22
+ # flagd Provider for OpenFeature
23
+
24
+ This provider is designed to use flagd's [evaluation protocol](https://github.com/open-feature/schemas/blob/main/protobuf/schema/v1/schema.proto), or locally evaluate flags defined in a flagd [flag definition](https://github.com/open-feature/schemas/blob/main/json/flagd-definitions.json) via the OpenFeature Python SDK.
25
+
26
+ ## Installation
27
+
28
+ ```
29
+ pip install openfeature-provider-flagd
30
+ ```
31
+
32
+ ## Configuration and Usage
33
+
34
+ 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).
35
+
36
+ ### Remote resolver (RPC)
37
+
38
+ This is the default mode of operation of the provider.
39
+ In this mode, `FlagdProvider` communicates with [flagd](https://github.com/open-feature/flagd) via the gRPC protocol.
40
+ Flag evaluations take place remotely at the connected flagd instance.
41
+
42
+ Instantiate a new FlagdProvider instance and configure the OpenFeature SDK to use it:
43
+
44
+ ```python
45
+ from openfeature import api
46
+ from openfeature.contrib.provider.flagd import FlagdProvider
47
+
48
+ api.set_provider(FlagdProvider())
49
+ ```
50
+
51
+ ### In-process resolver
52
+
53
+ This mode performs flag evaluations locally (in-process). Flag configurations for evaluation are obtained via gRPC protocol using [sync protobuf schema](https://buf.build/open-feature/flagd/file/main:sync/v1/sync_service.proto) service definition.
54
+
55
+ Consider the following example to create a `FlagdProvider` with in-process evaluations,
56
+
57
+ ```python
58
+ from openfeature import api
59
+ from openfeature.contrib.provider.flagd import FlagdProvider
60
+ from openfeature.contrib.provider.flagd.config import ResolverType
61
+
62
+ api.set_provider(FlagdProvider(
63
+ resolver_type=ResolverType.IN_PROCESS,
64
+ ))
65
+ ```
66
+
67
+ In the above example, in-process handlers attempt to connect to a sync service on address `localhost:8013` to obtain [flag definitions](https://github.com/open-feature/schemas/blob/main/json/flags.json).
68
+
69
+ <!--
70
+ #### Sync-metadata
71
+
72
+ To support the injection of contextual data configured in flagd for in-process evaluation, the provider exposes a `getSyncMetadata` accessor which provides the most recent value returned by the [GetMetadata RPC](https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1#flagd.sync.v1.FlagSyncService.GetMetadata).
73
+ The value is updated with every (re)connection to the sync implementation.
74
+ This can be used to enrich evaluations with such data.
75
+ If the `in-process` mode is not used, and before the provider is ready, the `getSyncMetadata` returns an empty map.
76
+ -->
77
+ ### File mode
78
+
79
+ In-process resolvers can also work in an offline mode.
80
+ To enable this mode, you should provide a valid flag configuration file with the option `offlineFlagSourcePath`.
81
+
82
+ ```python
83
+ from openfeature import api
84
+ from openfeature.contrib.provider.flagd import FlagdProvider
85
+ from openfeature.contrib.provider.flagd.config import ResolverType
86
+
87
+ api.set_provider(FlagdProvider(
88
+ resolver_type=ResolverType.FILE,
89
+ offline_flag_source_path="my-flag.json",
90
+ ))
91
+ ```
92
+
93
+ Provider will attempt to detect file changes using polling.
94
+ Polling happens at 5 second intervals and this is currently unconfigurable.
95
+ This mode is useful for local development, tests and offline applications.
96
+
97
+ ### Configuration options
98
+
99
+ The default options can be defined in the FlagdProvider constructor.
100
+
101
+ | Option name | Environment variable name | Type & Values | Default | Compatible resolver |
102
+ |--------------------------|--------------------------------|----------------------------|-------------------------------|---------------------|
103
+ | resolver_type | FLAGD_RESOLVER | enum - `rpc`, `in-process` | rpc | |
104
+ | host | FLAGD_HOST | str | localhost | rpc & in-process |
105
+ | port | FLAGD_PORT | int | 8013 (rpc), 8015 (in-process) | rpc & in-process |
106
+ | tls | FLAGD_TLS | bool | false | rpc & in-process |
107
+ | cert_path | FLAGD_SERVER_CERT_PATH | String | null | rpc & in-process |
108
+ | deadline | FLAGD_DEADLINE_MS | int | 500 | rpc & in-process |
109
+ | stream_deadline_ms | FLAGD_STREAM_DEADLINE_MS | int | 600000 | rpc & in-process |
110
+ | keep_alive_time | FLAGD_KEEP_ALIVE_TIME_MS | int | 0 | rpc & in-process |
111
+ | selector | FLAGD_SOURCE_SELECTOR | str | null | in-process |
112
+ | cache_type | FLAGD_CACHE | enum - `lru`, `disabled` | lru | rpc |
113
+ | max_cache_size | FLAGD_MAX_CACHE_SIZE | int | 1000 | rpc |
114
+ | retry_backoff_ms | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc |
115
+ | offline_flag_source_path | FLAGD_OFFLINE_FLAG_SOURCE_PATH | str | null | in-process |
116
+
117
+ > [!NOTE]
118
+ > The `selector` configuration is only used in **in-process** mode for filtering flag configurations. See [Selector Handling](#selector-handling-in-process-mode-only) for migration guidance.
119
+
120
+ <!-- not implemented
121
+ | target_uri | FLAGD_TARGET_URI | alternative to host/port, supporting custom name resolution | string | null | rpc & in-process |
122
+ | socket_path | FLAGD_SOCKET_PATH | alternative to host port, unix socket | String | null | rpc & in-process |
123
+ | context_enricher | - | sync-metadata to evaluation context mapping function | function | identity function | in-process |
124
+ | offline_pollIntervalMs | FLAGD_OFFLINE_POLL_MS | poll interval for reading offlineFlagSourcePath | int | 5000 | in-process |
125
+ -->
126
+
127
+ > [!NOTE]
128
+ > Some configurations are only applicable for RPC resolver.
129
+
130
+ ### Selector Handling (In-Process Mode Only)
131
+
132
+ > [!IMPORTANT]
133
+ > This section only applies to **in-process** resolver mode. RPC mode is not affected by selector handling changes.
134
+
135
+ #### Current Implementation
136
+
137
+ As of this SDK version, the `selector` parameter is passed via **both** gRPC metadata headers (`flagd-selector`) and the request body when using in-process mode. This dual approach ensures maximum compatibility with all flagd versions.
138
+
139
+ **Configuration Example:**
140
+ ```python
141
+ from openfeature import api
142
+ from openfeature.contrib.provider.flagd import FlagdProvider
143
+ from openfeature.contrib.provider.flagd.config import ResolverType
144
+
145
+ api.set_provider(FlagdProvider(
146
+ resolver_type=ResolverType.IN_PROCESS,
147
+ selector="my-flag-source", # Passed via both header and request body
148
+ ))
149
+ ```
150
+
151
+ The selector is automatically passed via:
152
+ - **gRPC metadata header** (`flagd-selector`) - For flagd v0.11.0+ selector normalization
153
+ - **Request body** - For backward compatibility with older flagd versions
154
+
155
+ #### Backward Compatibility
156
+
157
+ This dual transmission approach ensures the Python SDK works seamlessly with all flagd service versions:
158
+ - **Older flagd versions** read the selector from the request body
159
+ - **Newer flagd versions (v0.11.0+)** prefer the selector from the gRPC metadata header
160
+ - Both approaches are supported simultaneously for maximum compatibility
161
+
162
+ **Related Resources:**
163
+ - Upstream issue: [open-feature/flagd#1814](https://github.com/open-feature/flagd/issues/1814)
164
+ - Selector normalization affects in-process evaluations that filter flag configurations by source
165
+
166
+ <!--
167
+ ### Unix socket support
168
+ Unix socket communication with flagd is facilitated by usaging of the linux-native `epoll` library on `linux-x86_64`
169
+ only (ARM support is pending the release of `netty-transport-native-epoll` v5).
170
+ Unix sockets are not supported on other platforms or architectures.
171
+ -->
172
+
173
+ ### Reconnection
174
+
175
+ Reconnection is supported by the underlying gRPC connections.
176
+ If the connection to flagd is lost, it will reconnect automatically.
177
+ 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.
178
+
179
+ ### Deadlines
180
+
181
+ Deadlines are used to define how long the provider waits to complete initialization or flag evaluations.
182
+ They behave differently based on the resolver type.
183
+
184
+ #### Deadlines with Remote resolver (RPC)
185
+
186
+ If the remote evaluation call is not completed within this deadline, the gRPC call is terminated with the error `DEADLINE_EXCEEDED`
187
+ and the evaluation will default.
188
+
189
+ ### TLS
190
+
191
+ TLS is available in situations where flagd is running on another host.
192
+
193
+
194
+ You may optionally supply an X.509 certificate in PEM format. Otherwise, the default certificate store will be used.
195
+
196
+ ```python
197
+ from openfeature import api
198
+ from openfeature.contrib.provider.flagd import FlagdProvider
199
+
200
+ api.set_provider(FlagdProvider(
201
+ tls=True, # use TLS
202
+ cert_path="etc/cert/ca.crt" # PEM cert
203
+ ))
204
+ ```
205
+
206
+ ## License
207
+
208
+ Apache 2.0 - See [LICENSE](./LICENSE) for more information.
@@ -93,6 +93,9 @@ The default options can be defined in the FlagdProvider constructor.
93
93
  | retry_backoff_ms | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc |
94
94
  | offline_flag_source_path | FLAGD_OFFLINE_FLAG_SOURCE_PATH | str | null | in-process |
95
95
 
96
+ > [!NOTE]
97
+ > The `selector` configuration is only used in **in-process** mode for filtering flag configurations. See [Selector Handling](#selector-handling-in-process-mode-only) for migration guidance.
98
+
96
99
  <!-- not implemented
97
100
  | target_uri | FLAGD_TARGET_URI | alternative to host/port, supporting custom name resolution | string | null | rpc & in-process |
98
101
  | socket_path | FLAGD_SOCKET_PATH | alternative to host port, unix socket | String | null | rpc & in-process |
@@ -103,6 +106,42 @@ The default options can be defined in the FlagdProvider constructor.
103
106
  > [!NOTE]
104
107
  > Some configurations are only applicable for RPC resolver.
105
108
 
109
+ ### Selector Handling (In-Process Mode Only)
110
+
111
+ > [!IMPORTANT]
112
+ > This section only applies to **in-process** resolver mode. RPC mode is not affected by selector handling changes.
113
+
114
+ #### Current Implementation
115
+
116
+ As of this SDK version, the `selector` parameter is passed via **both** gRPC metadata headers (`flagd-selector`) and the request body when using in-process mode. This dual approach ensures maximum compatibility with all flagd versions.
117
+
118
+ **Configuration Example:**
119
+ ```python
120
+ from openfeature import api
121
+ from openfeature.contrib.provider.flagd import FlagdProvider
122
+ from openfeature.contrib.provider.flagd.config import ResolverType
123
+
124
+ api.set_provider(FlagdProvider(
125
+ resolver_type=ResolverType.IN_PROCESS,
126
+ selector="my-flag-source", # Passed via both header and request body
127
+ ))
128
+ ```
129
+
130
+ The selector is automatically passed via:
131
+ - **gRPC metadata header** (`flagd-selector`) - For flagd v0.11.0+ selector normalization
132
+ - **Request body** - For backward compatibility with older flagd versions
133
+
134
+ #### Backward Compatibility
135
+
136
+ This dual transmission approach ensures the Python SDK works seamlessly with all flagd service versions:
137
+ - **Older flagd versions** read the selector from the request body
138
+ - **Newer flagd versions (v0.11.0+)** prefer the selector from the gRPC metadata header
139
+ - Both approaches are supported simultaneously for maximum compatibility
140
+
141
+ **Related Resources:**
142
+ - Upstream issue: [open-feature/flagd#1814](https://github.com/open-feature/flagd/issues/1814)
143
+ - Selector normalization affects in-process evaluations that filter flag configurations by source
144
+
106
145
  <!--
107
146
  ### Unix socket support
108
147
  Unix socket communication with flagd is facilitated by usaging of the linux-native `epoll` library on `linux-x86_64`
@@ -1,82 +1,54 @@
1
- # pyproject.toml
2
1
  [build-system]
2
+ # `uv_build` backend doesn't support build hooks yet https://github.com/astral-sh/uv/issues/14561
3
3
  requires = ["hatchling"]
4
4
  build-backend = "hatchling.build"
5
5
 
6
6
  [project]
7
7
  name = "openfeature-provider-flagd"
8
- version = "0.2.5"
8
+ version = "0.2.7"
9
9
  description = "OpenFeature provider for the flagd flag evaluation engine"
10
10
  readme = "README.md"
11
11
  authors = [{ name = "OpenFeature", email = "openfeature-core@groups.io" }]
12
- license = { file = "LICENSE" }
12
+ license = "Apache-2.0"
13
+ license-files = ["LICENSE"]
13
14
  classifiers = [
14
- "License :: OSI Approved :: Apache Software License",
15
15
  "Programming Language :: Python",
16
16
  "Programming Language :: Python :: 3",
17
17
  ]
18
18
  keywords = []
19
19
  dependencies = [
20
- "openfeature-sdk>=0.6.0",
21
- "grpcio>=1.68.1",
22
- "protobuf>=4.29.2",
23
- "mmh3>=4.1.0",
20
+ "openfeature-sdk>=0.8.2",
21
+ "grpcio>=1.76.0",
22
+ "protobuf>=6.30.0,<7.0.0",
23
+ "mmh3>=5.0.0,<6.0.0",
24
24
  "panzi-json-logic>=1.0.1",
25
25
  "semver>=3,<4",
26
26
  "pyyaml>=6.0.1",
27
- "cachebox"
27
+ "cachebox>=5.1.0,<6.0.0",
28
28
  ]
29
29
  requires-python = ">=3.9"
30
30
 
31
31
  [project.urls]
32
32
  Homepage = "https://github.com/open-feature/python-sdk-contrib"
33
33
 
34
- [tool.hatch]
35
-
36
- [tool.hatch.envs.hatch-test]
37
- dependencies = [
38
- "coverage[toml]>=6.5",
39
- "pytest",
40
- "pytest-bdd",
41
- "testcontainers",
42
- "asserts",
43
- "grpcio-health-checking==1.73.1",
34
+ [dependency-groups]
35
+ dev = [
36
+ "asserts>=0.13.0,<0.14.0",
37
+ "coverage[toml]>=7.10.0,<8.0.0",
38
+ "grpcio-health-checking>=1.74.0,<2.0.0",
39
+ "mypy>=1.18.0,<2.0.0",
40
+ "pytest>=8.4.0,<9.0.0",
41
+ "pytest-bdd>=8.1.0,<9.0.0",
42
+ "testcontainers>=4.12.0,<5.0.0",
43
+ "types-grpcio>=1.0.0,<2.0.0",
44
+ "types-protobuf>=6.30.0,<7.0.0",
45
+ "types-pyyaml>=6.0.0,<7.0.0",
44
46
  ]
45
- pre-install-commands = [
46
- "hatch build",
47
- ]
48
-
49
- [tool.hatch.envs.hatch-test.scripts]
50
- run = "pytest {args:tests}"
51
- run-cov = "coverage run -m pytest {args:tests}"
52
- cov-combine = "coverage combine"
53
- cov-report = [
54
- "coverage xml",
55
- "coverage html",
56
- "coverage report",
57
- ]
58
- cov = [
59
- "test-cov",
60
- "cov-report",
61
- ]
62
-
63
-
64
- [tool.hatch.envs.mypy]
65
- dependencies = [
66
- "mypy[faster-cache]>=1.13.0",
67
- "types-protobuf",
68
- "types-pyyaml",
69
- ]
70
- pre-install-commands = [
71
- "hatch build",
72
- ]
73
-
74
- [tool.hatch.envs.mypy.scripts]
75
- run = "mypy"
76
47
 
77
48
  [tool.hatch.build.hooks.protobuf]
78
49
  generate_pyi = false
79
50
  dependencies = [
51
+ "protobuf~=6.0",
80
52
  "hatch-protobuf",
81
53
  "mypy-protobuf~=3.0",
82
54
  ]
@@ -116,7 +88,9 @@ files = "src"
116
88
  python_version = "3.9" # should be identical to the minimum supported version
117
89
  namespace_packages = true
118
90
  explicit_package_bases = true
119
- local_partial_types = true
91
+ local_partial_types = true # will become the new default from version 2
92
+ allow_redefinition_new = true # will become the new default from version 2
93
+ fixed_format_cache = true # new caching mechanism
120
94
  pretty = true
121
95
 
122
96
  strict = true
@@ -124,7 +98,6 @@ disallow_any_generics = false
124
98
 
125
99
  [[tool.mypy.overrides]]
126
100
  module = [
127
- "grpc.*",
128
101
  "json_logic.*",
129
102
  ]
130
103
  ignore_missing_imports = true
@@ -134,3 +107,13 @@ module = [
134
107
  "openfeature.schemas.*"
135
108
  ]
136
109
  warn_unused_ignores = false
110
+
111
+ [project.scripts]
112
+ # workaround while UV doesn't support scripts directly in the pyproject.toml
113
+ # see: https://github.com/astral-sh/uv/issues/5903
114
+ cov-report = "scripts.scripts:cov_report"
115
+ cov = "scripts.scripts:cov"
116
+ # don't name it `mypy` otherwise `uv` will override the actual binary
117
+ mypy-check = "scripts.scripts:mypy"
118
+ test = "scripts.scripts:test"
119
+ test-cov = "scripts.scripts:test_cov"
@@ -28,7 +28,7 @@ import grpc
28
28
 
29
29
  from openfeature.evaluation_context import EvaluationContext
30
30
  from openfeature.event import ProviderEventDetails
31
- from openfeature.flag_evaluation import FlagResolutionDetails
31
+ from openfeature.flag_evaluation import FlagResolutionDetails, FlagValueType
32
32
  from openfeature.hook import Hook
33
33
  from openfeature.provider import AbstractProvider
34
34
  from openfeature.provider.metadata import Metadata
@@ -75,6 +75,9 @@ class FlagdProvider(AbstractProvider):
75
75
  :param deadline_ms: the maximum to wait before a request times out
76
76
  :param timeout: the maximum time to wait before a request times out
77
77
  :param retry_backoff_ms: the number of milliseconds to backoff
78
+ :param selector: filter flag configurations by source (in-process mode only)
79
+ Passed via both flagd-selector gRPC metadata header and request body
80
+ for backward compatibility with all flagd versions.
78
81
  :param offline_flag_source_path: the path to the flag source file
79
82
  :param stream_deadline_ms: the maximum time to wait before a request times out
80
83
  :param keep_alive_time: the number of milliseconds to keep alive
@@ -199,9 +202,13 @@ class FlagdProvider(AbstractProvider):
199
202
  def resolve_object_details(
200
203
  self,
201
204
  flag_key: str,
202
- default_value: typing.Union[dict, list],
205
+ default_value: typing.Union[
206
+ typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
207
+ ],
203
208
  evaluation_context: typing.Optional[EvaluationContext] = None,
204
- ) -> FlagResolutionDetails[typing.Union[dict, list]]:
209
+ ) -> FlagResolutionDetails[
210
+ typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
211
+ ]:
205
212
  return self.resolver.resolve_object_details(
206
213
  flag_key, default_value, evaluation_context
207
214
  )
@@ -21,7 +21,7 @@ from openfeature.exception import (
21
21
  ProviderNotReadyError,
22
22
  TypeMismatchError,
23
23
  )
24
- from openfeature.flag_evaluation import FlagResolutionDetails, Reason
24
+ from openfeature.flag_evaluation import FlagResolutionDetails, FlagValueType, Reason
25
25
  from openfeature.schemas.protobuf.flagd.evaluation.v1 import (
26
26
  evaluation_pb2,
27
27
  evaluation_pb2_grpc,
@@ -29,6 +29,7 @@ from openfeature.schemas.protobuf.flagd.evaluation.v1 import (
29
29
 
30
30
  from ..config import CacheType, Config
31
31
  from ..flag_type import FlagType
32
+ from .types import GrpcMultiCallableArgs
32
33
 
33
34
  if typing.TYPE_CHECKING:
34
35
  from google.protobuf.message import Message
@@ -71,8 +72,6 @@ class GrpcResolver:
71
72
  self.thread: typing.Optional[threading.Thread] = None
72
73
  self.timer: typing.Optional[threading.Timer] = None
73
74
 
74
- self.start_time = time.time()
75
-
76
75
  def _generate_channel(self, config: Config) -> grpc.Channel:
77
76
  target = f"{config.host}:{config.port}"
78
77
  # Create the channel with the service config
@@ -121,15 +120,16 @@ class GrpcResolver:
121
120
  ),
122
121
  ]
123
122
  if config.tls:
124
- channel_args = {
125
- "options": options,
126
- "credentials": grpc.ssl_channel_credentials(),
127
- }
123
+ credentials = grpc.ssl_channel_credentials()
128
124
  if config.cert_path:
129
125
  with open(config.cert_path, "rb") as f:
130
- channel_args["credentials"] = grpc.ssl_channel_credentials(f.read())
126
+ credentials = grpc.ssl_channel_credentials(f.read())
131
127
 
132
- channel = grpc.secure_channel(target, **channel_args)
128
+ channel = grpc.secure_channel(
129
+ target,
130
+ credentials=credentials,
131
+ options=options,
132
+ )
133
133
 
134
134
  else:
135
135
  channel = grpc.insecure_channel(
@@ -161,8 +161,8 @@ class GrpcResolver:
161
161
  )
162
162
  self.monitor_thread.start()
163
163
  ## block until ready or deadline reached
164
- timeout = self.deadline + time.time()
165
- while not self.connected and time.time() < timeout:
164
+ timeout = self.deadline + time.monotonic()
165
+ while not self.connected and time.monotonic() < timeout:
166
166
  time.sleep(0.05)
167
167
  logger.debug("Finished blocking gRPC state initialization")
168
168
 
@@ -199,7 +199,6 @@ class GrpcResolver:
199
199
  message="gRPC sync disconnected, reconnecting",
200
200
  )
201
201
  )
202
- self.start_time = time.time()
203
202
  # adding a timer, so we can emit the error event after time
204
203
  self.timer = threading.Timer(self.retry_grace_period, self.emit_error)
205
204
 
@@ -220,20 +219,16 @@ class GrpcResolver:
220
219
 
221
220
  def listen(self) -> None:
222
221
  logger.debug("gRPC starting listener thread")
223
- call_args = (
224
- {"timeout": self.streamline_deadline_seconds}
225
- if self.streamline_deadline_seconds > 0
226
- else {}
227
- )
222
+ call_args: GrpcMultiCallableArgs = {"wait_for_ready": True}
223
+ if self.streamline_deadline_seconds > 0:
224
+ call_args["timeout"] = self.streamline_deadline_seconds
228
225
  request = evaluation_pb2.EventStreamRequest()
229
226
 
230
227
  # defining a never ending loop to recreate the stream
231
228
  while self.active:
232
229
  try:
233
230
  logger.debug("Setting up gRPC sync flags connection")
234
- for message in self.stub.EventStream(
235
- request, wait_for_ready=True, **call_args
236
- ):
231
+ for message in self.stub.EventStream(request, **call_args):
237
232
  if message.type == "provider_ready":
238
233
  self.emit_provider_ready(
239
234
  ProviderEventDetails(
@@ -300,25 +295,81 @@ class GrpcResolver:
300
295
  def resolve_object_details(
301
296
  self,
302
297
  key: str,
303
- default_value: typing.Union[dict, list],
298
+ default_value: typing.Union[
299
+ typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
300
+ ],
304
301
  evaluation_context: typing.Optional[EvaluationContext] = None,
305
- ) -> FlagResolutionDetails[typing.Union[dict, list]]:
302
+ ) -> FlagResolutionDetails[
303
+ typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
304
+ ]:
306
305
  return self._resolve(key, FlagType.OBJECT, default_value, evaluation_context)
307
306
 
307
+ @typing.overload
308
+ def _resolve(
309
+ self,
310
+ flag_key: str,
311
+ flag_type: FlagType,
312
+ default_value: bool,
313
+ evaluation_context: typing.Optional[EvaluationContext],
314
+ ) -> FlagResolutionDetails[bool]: ...
315
+
316
+ @typing.overload
317
+ def _resolve(
318
+ self,
319
+ flag_key: str,
320
+ flag_type: FlagType,
321
+ default_value: int,
322
+ evaluation_context: typing.Optional[EvaluationContext],
323
+ ) -> FlagResolutionDetails[int]: ...
324
+
325
+ @typing.overload
326
+ def _resolve(
327
+ self,
328
+ flag_key: str,
329
+ flag_type: FlagType,
330
+ default_value: float,
331
+ evaluation_context: typing.Optional[EvaluationContext],
332
+ ) -> FlagResolutionDetails[float]: ...
333
+
334
+ @typing.overload
335
+ def _resolve(
336
+ self,
337
+ flag_key: str,
338
+ flag_type: FlagType,
339
+ default_value: str,
340
+ evaluation_context: typing.Optional[EvaluationContext],
341
+ ) -> FlagResolutionDetails[str]: ...
342
+
343
+ @typing.overload
344
+ def _resolve(
345
+ self,
346
+ flag_key: str,
347
+ flag_type: FlagType,
348
+ default_value: typing.Union[
349
+ typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]
350
+ ],
351
+ evaluation_context: typing.Optional[EvaluationContext],
352
+ ) -> FlagResolutionDetails[
353
+ typing.Union[typing.Sequence[FlagValueType], typing.Mapping[str, FlagValueType]]
354
+ ]: ...
355
+
308
356
  def _resolve( # noqa: PLR0915 C901
309
357
  self,
310
358
  flag_key: str,
311
359
  flag_type: FlagType,
312
- default_value: T,
360
+ default_value: FlagValueType,
313
361
  evaluation_context: typing.Optional[EvaluationContext],
314
- ) -> FlagResolutionDetails[T]:
362
+ ) -> FlagResolutionDetails[FlagValueType]:
315
363
  if self.cache is not None and flag_key in self.cache:
316
- cached_flag: FlagResolutionDetails[T] = self.cache[flag_key]
364
+ cached_flag: FlagResolutionDetails[FlagValueType] = self.cache[flag_key]
317
365
  cached_flag.reason = Reason.CACHED
318
366
  return cached_flag
319
367
 
320
368
  context = self._convert_context(evaluation_context)
321
- call_args = {"timeout": self.deadline, "wait_for_ready": True}
369
+ call_args: GrpcMultiCallableArgs = {
370
+ "timeout": self.deadline,
371
+ "wait_for_ready": True,
372
+ }
322
373
  try:
323
374
  request: Message
324
375
  if flag_type == FlagType.BOOLEAN:
@@ -387,7 +438,11 @@ class GrpcResolver:
387
438
  if evaluation_context:
388
439
  try:
389
440
  s["targetingKey"] = evaluation_context.targeting_key
390
- s.update(evaluation_context.attributes)
441
+ s.update(
442
+ typing.cast(
443
+ "typing.Mapping[str, typing.Any]", evaluation_context.attributes
444
+ )
445
+ )
391
446
  except ValueError as exc:
392
447
  message = (
393
448
  "could not serialize evaluation context to google.protobuf.Struct"