Skip to content

Commit 2f89c0b

Browse files
committed
fixup: changing to mypy-protobuf
Signed-off-by: Simon Schrottner <[email protected]>
1 parent 62574bf commit 2f89c0b

File tree

20 files changed

+416
-102
lines changed

20 files changed

+416
-102
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,15 @@ jobs:
3838
python-version: ${{ matrix.python-version }}
3939
cache: "pip"
4040

41-
- uses: bufbuild/buf-action@v1
42-
with:
43-
github_token: ${{ github.token }}
44-
setup_only: true
45-
4641
- name: Install hatch
4742
run: pip install hatch
4843

44+
- name: Building first to generate files
45+
run: hatch build
46+
working-directory: ${{ matrix.package }}
47+
4948
- name: Test with pytest
50-
run: hatch run cov
49+
run: hatch test -c
5150
working-directory: ${{ matrix.package }}
5251

5352
- if: matrix.python-version == '3.11'

.gitmodules

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[submodule "schemas"]
2-
path = providers/openfeature-provider-flagd/schemas
2+
path = providers/openfeature-provider-flagd/openfeature/schemas
33
url = https://github.com/open-feature/schemas
44
[submodule "providers/openfeature-provider-flagd/test-harness"]
5-
path = providers/openfeature-provider-flagd/test-harness
5+
path = providers/openfeature-provider-flagd/openfeature/test-harness
66
url = [email protected]:open-feature/flagd-testbed.git
77
[submodule "providers/openfeature-provider-flagd/spec"]
8-
path = providers/openfeature-provider-flagd/spec
8+
path = providers/openfeature-provider-flagd/openfeature/spec
99
url = https://github.com/open-feature/spec

CONTRIBUTING.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ To install Hatch, just run `pip install hatch`.
1818

1919
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`.
2020

21+
> **Note**
22+
> Currently our protobuf files will be generated during `hatch build`
23+
> Please run this command once, to generate all necessary files.
24+
2125
### Testing
2226

23-
Run tests by entering the package directory and running `hatch run test`.
27+
Run tests by entering the package directory and running `hatch test`.
2428

2529
We use `pytest` for our unit testing, making use of `parametrized` to inject cases at scale.
2630

providers/openfeature-provider-flagd/README.md

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ pip install openfeature-provider-flagd
1010

1111
## Configuration and Usage
1212

13+
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).
14+
15+
### Remote resolver (RPC)
16+
17+
This is the default mode of operation of the provider.
18+
In this mode, `FlagdProvider` communicates with [flagd](https://github.com/open-feature/flagd) via the gRPC protocol.
19+
Flag evaluations take place remotely at the connected flagd instance.
20+
1321
Instantiate a new FlagdProvider instance and configure the OpenFeature SDK to use it:
1422

1523
```python
@@ -19,7 +27,9 @@ from openfeature.contrib.provider.flagd import FlagdProvider
1927
api.set_provider(FlagdProvider())
2028
```
2129

22-
To use in-process evaluation in offline mode with a file as source:
30+
### In-process resolver
31+
32+
This mode performs flag evaluations locally (in-process).
2333

2434
```python
2535
from openfeature import api
@@ -36,12 +46,72 @@ api.set_provider(FlagdProvider(
3646

3747
The default options can be defined in the FlagdProvider constructor.
3848

39-
| Option name | Type & Values | Default |
40-
|----------------|---------------|-----------|
41-
| host | str | localhost |
42-
| port | int | 8013 |
43-
| schema | str | http |
44-
| timeout | int | 2 |
49+
| Option name | Environment variable name | Type & Values | Default | Compatible resolver |
50+
| ------------------------ | ------------------------------ | -------------------------- | ----------------------------- | ------------------- |
51+
| resolver_type | FLAGD_RESOLVER | enum - `rpc`, `in-process` | rpc | |
52+
| host | FLAGD_HOST | str | localhost | rpc & in-process |
53+
| port | FLAGD_PORT | int | 8013 (rpc), 8015 (in-process) | rpc & in-process |
54+
| tls | FLAGD_TLS | bool | false | rpc & in-process |
55+
| deadline | FLAGD_DEADLINE_MS | int | 500 | rpc & in-process |
56+
| stream_deadline_ms | FLAGD_STREAM_DEADLINE_MS | int | 600000 | rpc & in-process |
57+
| keep_alive_time | FLAGD_KEEP_ALIVE_TIME_MS | int | 0 | rpc & in-process |
58+
| selector | FLAGD_SOURCE_SELECTOR | str | null | in-process |
59+
| cache_type | FLAGD_CACHE | enum - `lru`, `disabled` | lru | rpc |
60+
| max_cache_size | FLAGD_MAX_CACHE_SIZE | int | 1000 | rpc |
61+
| retry_backoff_ms | FLAGD_RETRY_BACKOFF_MS | int | 1000 | rpc |
62+
| offline_flag_source_path | FLAGD_OFFLINE_FLAG_SOURCE_PATH | str | null | in-process |
63+
64+
<!-- not implemented
65+
| target_uri | FLAGD_TARGET_URI | alternative to host/port, supporting custom name resolution | string | null | rpc & in-process |
66+
| socket_path | FLAGD_SOCKET_PATH | alternative to host port, unix socket | String | null | rpc & in-process |
67+
| cert_path | FLAGD_SERVER_CERT_PATH | tls cert path | String | null | rpc & in-process |
68+
| max_event_stream_retries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | rpc |
69+
| context_enricher | - | sync-metadata to evaluation context mapping function | function | identity function | in-process |
70+
| offline_pollIntervalMs | FLAGD_OFFLINE_POLL_MS | poll interval for reading offlineFlagSourcePath | int | 5000 | in-process |
71+
-->
72+
73+
> [!NOTE]
74+
> Some configurations are only applicable for RPC resolver.
75+
76+
<!--
77+
### Unix socket support
78+
Unix socket communication with flagd is facilitated by usaging of the linux-native `epoll` library on `linux-x86_64`
79+
only (ARM support is pending the release of `netty-transport-native-epoll` v5).
80+
Unix sockets are not supported on other platforms or architectures.
81+
-->
82+
83+
### Reconnection
84+
85+
Reconnection is supported by the underlying gRPC connections.
86+
If the connection to flagd is lost, it will reconnect automatically.
87+
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
88+
indefinitely.
89+
90+
### Deadlines
91+
92+
Deadlines are used to define how long the provider waits to complete initialization or flag evaluations.
93+
They behave differently based on the resolver type.
94+
95+
#### Deadlines with Remote resolver (RPC)
96+
97+
If the remote evaluation call is not completed within this deadline, the gRPC call is terminated with the error `DEADLINE_EXCEEDED`
98+
and the evaluation will default.
99+
100+
### TLS
101+
102+
TLS is available in situations where flagd is running on another host.
103+
104+
<!--
105+
You may optionally supply an X.509 certificate in PEM format. Otherwise, the default certificate store will be used.
106+
```java
107+
FlagdProvider flagdProvider = new FlagdProvider(
108+
FlagdOptions.builder()
109+
.host("myflagdhost")
110+
.tls(true) // use TLS
111+
.certPath("etc/cert/ca.crt") // PEM cert
112+
.build());
113+
```
114+
-->
45115

46116
## License
47117

providers/openfeature-provider-flagd/pyproject.toml

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ classifiers = [
1818
keywords = []
1919
dependencies = [
2020
"openfeature-sdk>=0.4.0",
21-
"grpcio>=1.60.0",
21+
"grpcio>=1.68.0",
2222
"protobuf>=4.25.2",
2323
"mmh3>=4.1.0",
2424
"panzi-json-logic>=1.0.1",
@@ -32,7 +32,7 @@ Homepage = "https://github.com/open-feature/python-sdk-contrib"
3232

3333
[tool.hatch]
3434

35-
[tool.hatch.envs.default]
35+
[tool.hatch.envs.hatch-test]
3636
dependencies = [
3737
"coverage[toml]>=6.5",
3838
"pytest",
@@ -41,28 +41,47 @@ dependencies = [
4141
"asserts",
4242
"grpcio-health-checking==1.60.0",
4343
]
44+
pre-install-commands = [
45+
"hatch build",
46+
]
4447

45-
[tool.hatch.envs.default.scripts]
46-
test = "pytest {args:tests}"
47-
test-cov = "coverage run -m pytest {args:tests}"
48+
[tool.hatch.envs.hatch-test.scripts]
49+
run = "pytest {args:tests}"
50+
run-cov = "coverage run -m pytest {args:tests}"
51+
cov-combine = "coverage combine"
4852
cov-report = [
4953
"coverage xml",
5054
"coverage html",
55+
"coverage report",
5156
]
5257
cov = [
5358
"test-cov",
5459
"cov-report",
5560
]
5661

5762
[tool.hatch.build.hooks.protobuf]
58-
dependencies = ["hatch-protobuf"]
59-
paths = ["schemas/protobuf/schema/v1/schema.proto", "schemas/protobuf/sync/v1/sync_service.proto"]
60-
output_path = "src"
63+
generate_pyi = false
64+
dependencies = [
65+
"hatch-protobuf",
66+
"mypy-protobuf~=3.0",
67+
]
68+
proto_paths = [
69+
".",
70+
]
71+
output_path = "src/"
72+
73+
[[tool.hatch.build.hooks.protobuf.generators]]
74+
name = "mypy"
75+
outputs = ["{proto_path}/{proto_name}_pb2.pyi"]
76+
77+
[[tool.hatch.build.hooks.protobuf.generators]]
78+
name = "mypy_grpc"
79+
outputs = ["{proto_path}/{proto_name}_pb2_grpc.pyi"]
6180

6281
[tool.hatch.build.targets.sdist]
6382
exclude = [
6483
".gitignore",
65-
"schemas",
84+
"/openfeature",
6685
]
6786

6887
[tool.hatch.build.targets.wheel]
@@ -71,6 +90,6 @@ packages = ["src/openfeature"]
7190
[tool.coverage.run]
7291
omit = [
7392
# exclude generated files
74-
"src/openfeature/contrib/provider/flagd/proto/*",
93+
"src/openfeature/schemas/*",
7594
"tests/**",
7695
]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
schemas

providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22
import typing
33
from enum import Enum
44

5+
6+
class ResolverType(Enum):
7+
RPC = "rpc"
8+
IN_PROCESS = "in-process"
9+
10+
11+
DEFAULT_DEADLINE = 500
12+
DEFAULT_HOST = "localhost"
13+
DEFAULT_KEEP_ALIVE = 0
14+
DEFAULT_OFFLINE_SOURCE_PATH: typing.Optional[str] = None
15+
DEFAULT_PORT_IN_PROCESS = 8015
16+
DEFAULT_PORT_RPC = 8013
17+
DEFAULT_RESOLVER_TYPE = ResolverType.RPC
18+
DEFAULT_RETRY_BACKOFF = 1000
19+
DEFAULT_STREAM_DEADLINE = 600000
20+
DEFAULT_TLS = False
21+
22+
ENV_VAR_DEADLINE_MS = "FLAGD_DEADLINE_MS"
23+
ENV_VAR_HOST = "FLAGD_HOST"
24+
ENV_VAR_KEEP_ALIVE_TIME_MS = "FLAGD_KEEP_ALIVE_TIME_MS"
25+
ENV_VAR_OFFLINE_FLAG_SOURCE_PATH = "FLAGD_OFFLINE_FLAG_SOURCE_PATH"
26+
ENV_VAR_PORT = "FLAGD_PORT"
27+
ENV_VAR_RESOLVER_TYPE = "FLAGD_RESOLVER_TYPE"
28+
ENV_VAR_RETRY_BACKOFF_MS = "FLAGD_RETRY_BACKOFF_MS"
29+
ENV_VAR_STREAM_DEADLINE_MS = "FLAGD_STREAM_DEADLINE_MS"
30+
ENV_VAR_TLS = "FLAGD_TLS"
31+
532
T = typing.TypeVar("T")
633

734

@@ -18,42 +45,79 @@ def env_or_default(
1845
return val if cast is None else cast(val)
1946

2047

21-
class ResolverType(Enum):
22-
GRPC = "grpc"
23-
IN_PROCESS = "in-process"
24-
25-
2648
class Config:
2749
def __init__( # noqa: PLR0913
2850
self,
2951
host: typing.Optional[str] = None,
3052
port: typing.Optional[int] = None,
3153
tls: typing.Optional[bool] = None,
32-
timeout: typing.Optional[int] = None,
3354
resolver_type: typing.Optional[ResolverType] = None,
3455
offline_flag_source_path: typing.Optional[str] = None,
35-
offline_poll_interval_seconds: typing.Optional[float] = None,
56+
retry_backoff_ms: typing.Optional[int] = None,
57+
deadline: typing.Optional[int] = None,
58+
stream_deadline_ms: typing.Optional[int] = None,
59+
keep_alive_time: typing.Optional[int] = None,
3660
):
37-
self.host = env_or_default("FLAGD_HOST", "localhost") if host is None else host
38-
self.port = (
39-
env_or_default("FLAGD_PORT", 8013, cast=int) if port is None else port
40-
)
61+
self.host = env_or_default(ENV_VAR_HOST, DEFAULT_HOST) if host is None else host
4162
self.tls = (
42-
env_or_default("FLAGD_TLS", False, cast=str_to_bool) if tls is None else tls
63+
env_or_default(ENV_VAR_TLS, DEFAULT_TLS, cast=str_to_bool)
64+
if tls is None
65+
else tls
66+
)
67+
self.retry_backoff_ms: int = (
68+
int(
69+
env_or_default(
70+
ENV_VAR_RETRY_BACKOFF_MS, DEFAULT_RETRY_BACKOFF, cast=int
71+
)
72+
)
73+
if retry_backoff_ms is None
74+
else retry_backoff_ms
4375
)
44-
self.timeout = 5 if timeout is None else timeout
76+
4577
self.resolver_type = (
46-
ResolverType(env_or_default("FLAGD_RESOLVER_TYPE", "grpc"))
78+
ResolverType(env_or_default(ENV_VAR_RESOLVER_TYPE, DEFAULT_RESOLVER_TYPE))
4779
if resolver_type is None
4880
else resolver_type
4981
)
82+
83+
default_port = (
84+
DEFAULT_PORT_RPC
85+
if self.resolver_type is ResolverType.RPC
86+
else DEFAULT_PORT_IN_PROCESS
87+
)
88+
self.port: int = (
89+
int(env_or_default(ENV_VAR_PORT, default_port, cast=int))
90+
if port is None
91+
else port
92+
)
5093
self.offline_flag_source_path = (
51-
env_or_default("FLAGD_OFFLINE_FLAG_SOURCE_PATH", None)
94+
env_or_default(
95+
ENV_VAR_OFFLINE_FLAG_SOURCE_PATH, DEFAULT_OFFLINE_SOURCE_PATH
96+
)
5297
if offline_flag_source_path is None
5398
else offline_flag_source_path
5499
)
55-
self.offline_poll_interval_seconds = (
56-
float(env_or_default("FLAGD_OFFLINE_POLL_INTERVAL_SECONDS", 1.0))
57-
if offline_poll_interval_seconds is None
58-
else offline_poll_interval_seconds
100+
101+
self.deadline: int = (
102+
int(env_or_default(ENV_VAR_DEADLINE_MS, DEFAULT_DEADLINE, cast=int))
103+
if deadline is None
104+
else deadline
105+
)
106+
107+
self.stream_deadline_ms: int = (
108+
int(
109+
env_or_default(
110+
ENV_VAR_STREAM_DEADLINE_MS, DEFAULT_STREAM_DEADLINE, cast=int
111+
)
112+
)
113+
if stream_deadline_ms is None
114+
else stream_deadline_ms
115+
)
116+
117+
self.keep_alive_time: int = (
118+
int(
119+
env_or_default(ENV_VAR_KEEP_ALIVE_TIME_MS, DEFAULT_KEEP_ALIVE, cast=int)
120+
)
121+
if keep_alive_time is None
122+
else keep_alive_time
59123
)

0 commit comments

Comments
 (0)