Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
3477153
build(flagd): auto generate proto files from schema
aepfli Nov 22, 2024
db81de1
fix(flagd): adding events for rpc mode
aepfli Nov 21, 2024
8cafa56
feat: add grpc sync flag store
colebaileygit Apr 27, 2024
a0df24f
fix: float/int type casting
colebaileygit Apr 27, 2024
373b343
add: test cases and reconnect functionality
colebaileygit May 1, 2024
73dbce3
fix: pyproject settings
colebaileygit May 1, 2024
9f3e15a
add: thread shutdown and cleaner flag store / connector logic
colebaileygit May 1, 2024
6f9b093
add: selector and sync init timeout
colebaileygit May 1, 2024
1033fe6
fix: noisy test due to insufficient wait time
colebaileygit May 1, 2024
4243ec7
docs: configuration options in readme
colebaileygit May 1, 2024
8b02e10
tests: edge cases for grpc response parsing
colebaileygit May 1, 2024
dc3c8dd
Add env var configs to readme
colebaileygit May 3, 2024
93b0269
fix: improve naming of emit flag
colebaileygit May 4, 2024
08d8172
docs: update CONTRIBUTING details
colebaileygit May 4, 2024
794e35f
add: worker thread names
colebaileygit Jun 20, 2024
bff53a9
fix: remove hasattr usage
colebaileygit Jun 20, 2024
f775b99
fix: circular dependency
colebaileygit Jun 20, 2024
fb110bb
fix: AbstractProvider dependency everywhere
colebaileygit Jun 20, 2024
914d9e0
Merge branch 'main' into feat/autogenerate_proto_files
aepfli Nov 22, 2024
6bf2b3c
feat(flagd-rpc): add caching
aepfli Nov 22, 2024
e8bb3f2
Merge branch 'main' into feat/caching
aepfli Nov 23, 2024
f01d6e5
feat(flagd-rpc): add caching
aepfli Nov 22, 2024
af0df41
fixup: adding gherkin tests for evaluations, and fxing found issues
aepfli Nov 17, 2024
c7f81b6
Merge branch 'feat/caching' into feat/grpc-sync-addition
aepfli Nov 23, 2024
07a38e6
fixup: improve readme and use all the settings everywhere
aepfli Nov 23, 2024
4b74a53
fixup: changing to mypy-protobuf
aepfli Nov 24, 2024
87c50a4
Merge branch 'feat/autogenerate_proto_files' into feat/grpc-sync-addi…
aepfli Nov 25, 2024
6cff3d4
fixup: grpc -> rpc
toddbaert Nov 25, 2024
228008d
Merge branch 'main' into feat/grpc-sync-addition
toddbaert Nov 25, 2024
cd6b35b
fixup: comment
toddbaert Nov 25, 2024
9e10577
chore: comment lol
toddbaert Nov 25, 2024
4ba24f0
fixup: 3rd try...
toddbaert Nov 25, 2024
f91dd5c
fixup: recreate channel on any grpc err
toddbaert Nov 25, 2024
266d2f1
feat(flagd-rpc): add caching with tests
aepfli Nov 28, 2024
755f04c
fixup: using new test-harness
aepfli Dec 2, 2024
3c0e9cc
fixup(flagd): remove merge conflict error as stated by warber
aepfli Dec 4, 2024
c05b07d
Merge branch 'feat/caching' into feat/grpc-sync-addition
aepfli Dec 6, 2024
a37e5a3
Merge branch 'feat/grace_attempts' into feat/grpc-sync-addition
aepfli Dec 6, 2024
41d0ad8
feat(flagd-rpc): adding grace attempts (#117)
aepfli Dec 12, 2024
3c3e9c8
feat(flagd): use test-harness version number for integration tests (#…
aepfli Dec 18, 2024
0b749b5
ci: add renovate config (#123)
gruebel Dec 22, 2024
8ac7ab7
chore(deps): update codecov/codecov-action action to v4.6.0 (#124)
renovate[bot] Dec 22, 2024
4e75a36
chore(deps): update dependency grpcio-health-checking to v1.68.1 (#125)
renovate[bot] Dec 22, 2024
d92e8c6
chore(deps): update python docker tag to v3.13 (#127)
renovate[bot] Dec 22, 2024
4e7b0e5
chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v5…
renovate[bot] Dec 22, 2024
f0118f0
chore(deps): update codecov/codecov-action action to v5 (#128)
renovate[bot] Dec 23, 2024
f156ea5
chore(config): migrate renovate config (#130)
renovate[bot] Dec 26, 2024
8e23a70
feat: attempts with connection improvements (#118)
aepfli Dec 27, 2024
f6431e6
build(renovate): Utilize default OpenFeature Renovate configuration (…
aepfli Dec 27, 2024
f50351a
feat(flagd): add custom cert path (#131)
aepfli Dec 27, 2024
a2a0ba0
chore(deps): update providers/openfeature-provider-flagd/openfeature/…
renovate[bot] Dec 27, 2024
9dcb6a5
chore(deps): pin dependencies (#133)
renovate[bot] Dec 27, 2024
4db7a48
Merge remote-tracking branch 'upstream/main' into feat/grpc-sync-addi…
aepfli Dec 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ jobs:
- "providers/openfeature-provider-ofrep"

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
submodules: recursive

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
Expand All @@ -60,7 +60,7 @@ jobs:

- if: matrix.python-version == '3.11'
name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.5.0
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
with:
name: Code Coverage for ${{ matrix.package }} on Python ${{ matrix.python-version }}
directory: ${{ matrix.package }}
Expand All @@ -72,14 +72,14 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5
with:
python-version: "3.11"
cache: "pip"

- name: Run pre-commit
uses: pre-commit/[email protected]
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1

sast:
runs-on: ubuntu-latest
Expand All @@ -88,13 +88,13 @@ jobs:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3
with:
languages: python
config-file: ./.github/codeql-config.yml

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3
6 changes: 3 additions & 3 deletions .github/workflows/lint-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
- uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- uses: marocchino/sticky-pull-request-comment@v2
- uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2
# When the previous steps fails, the workflow would stop. By adding this
# condition you can continue the execution with the populated error message.
if: always() && (steps.lint_pr_title.outputs.error_message != null)
Expand All @@ -44,7 +44,7 @@ jobs:

# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2
with:
header: pr-title-lint-error
delete: true
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
pull-requests: write # for googleapis/release-please-action to create release PR
# Release-please creates a PR that tracks all changes
steps:
- uses: googleapis/release-please-action@v4
- uses: googleapis/release-please-action@7987652d64b4581673a76e33ad5e98e3dd56832f # v4
id: release
with:
token: ${{secrets.GITHUB_TOKEN}}
Expand All @@ -46,10 +46,10 @@ jobs:
# IMPORTANT: this permission is mandatory for trusted publishing to pypi
id-token: write
container:
image: "python:3.12"
image: "python:3.13"

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
submodules: recursive

Expand Down
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[submodule "providers/openfeature-provider-flagd/test-harness"]
path = providers/openfeature-provider-flagd/openfeature/test-harness
url = [email protected]:open-feature/flagd-testbed.git
branch = v0.5.18
[submodule "providers/openfeature-provider-flagd/spec"]
path = providers/openfeature-provider-flagd/openfeature/spec
url = https://github.com/open-feature/spec
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
default_stages: [commit]
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.3
rev: v0.8.4
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: check-toml
- id: check-yaml
Expand Down
20 changes: 17 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ We use `pytest` for our unit testing, making use of `parametrized` to inject cas

### Integration tests

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.
The Flagd provider utilizes the [gherkin integration tests](https://github.com/open-feature/test-harness/blob/main/features/evaluation.feature) to validate against a live, seeded Flagd instance.

To run the integration tests you need to have a container runtime, like docker, ranger, etc. installed.

```bash
hatch run test
```

### Type checking

Expand All @@ -52,6 +58,13 @@ Navigate to the repository folder
cd python-sdk-contrib
```

Checkout submodules

```bash
git submodule update --init --recursive
```


Add your fork as an origin

```bash
Expand All @@ -62,15 +75,16 @@ Ensure your development environment is all set up by building and testing

```bash
cd <package>
hatch run test
hatch build
hatch test
```

To start working on a new feature or bugfix, create a new branch and start working on it.

```bash
git checkout -b feat/NAME_OF_FEATURE
# Make your changes
git commit
git commit -s -m "feat: my feature"
git push fork feat/NAME_OF_FEATURE
```

Expand Down
60 changes: 46 additions & 14 deletions providers/openfeature-provider-flagd/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# flagd Provider for OpenFeature

This provider is designed to use flagd's [evaluation protocol](https://github.com/open-feature/schemas/blob/main/protobuf/schema/v1/schema.proto).
This provider is designed to use
flagd's [evaluation protocol](https://github.com/open-feature/schemas/blob/main/protobuf/schema/v1/schema.proto).

## Installation

Expand Down Expand Up @@ -29,7 +30,34 @@ api.set_provider(FlagdProvider())

### In-process resolver

This mode performs flag evaluations locally (in-process).
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.

Consider the following example to create a `FlagdProvider` with in-process evaluations,

```python
from openfeature import api
from openfeature.contrib.provider.flagd import FlagdProvider
from openfeature.contrib.provider.flagd.config import ResolverType

api.set_provider(FlagdProvider(
resolver_type=ResolverType.IN_PROCESS,
))
```

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).

<!--
#### Sync-metadata

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).
The value is updated with every (re)connection to the sync implementation.
This can be used to enrich evaluations with such data.
If the `in-process` mode is not used, and before the provider is ready, the `getSyncMetadata` returns an empty map.
-->
#### Offline mode

In-process resolvers can also work in an offline mode.
To enable this mode, you should provide a valid flag configuration file with the option `offlineFlagSourcePath`.

```python
from openfeature import api
Expand All @@ -42,16 +70,21 @@ api.set_provider(FlagdProvider(
))
```

Provider will attempt to detect file changes using polling.
Polling happens at 5 second intervals and this is currently unconfigurable.
This mode is useful for local development, tests and offline applications.

### Configuration options

The default options can be defined in the FlagdProvider constructor.

| 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 |
| cert_path | FLAGD_SERVER_CERT_PATH | String | null | 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 |
Expand All @@ -64,8 +97,6 @@ The default options can be defined in the FlagdProvider constructor.
<!-- not implemented
| target_uri | FLAGD_TARGET_URI | alternative to host/port, supporting custom name resolution | string | null | rpc & in-process |
| socket_path | FLAGD_SOCKET_PATH | alternative to host port, unix socket | String | null | rpc & in-process |
| cert_path | FLAGD_SERVER_CERT_PATH | tls cert path | String | null | rpc & in-process |
| max_event_stream_retries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | rpc |
| context_enricher | - | sync-metadata to evaluation context mapping function | function | identity function | in-process |
| offline_pollIntervalMs | FLAGD_OFFLINE_POLL_MS | poll interval for reading offlineFlagSourcePath | int | 5000 | in-process |
-->
Expand Down Expand Up @@ -100,17 +131,18 @@ and the evaluation will default.

TLS is available in situations where flagd is running on another host.

<!--

You may optionally supply an X.509 certificate in PEM format. Otherwise, the default certificate store will be used.
```java
FlagdProvider flagdProvider = new FlagdProvider(
FlagdOptions.builder()
.host("myflagdhost")
.tls(true) // use TLS
.certPath("etc/cert/ca.crt") // PEM cert
.build());

```python
from openfeature import api
from openfeature.contrib.provider.flagd import FlagdProvider

api.set_provider(FlagdProvider(
tls=True, # use TLS
cert_path="etc/cert/ca.crt" # PEM cert
))
```
-->

## License

Expand Down
2 changes: 1 addition & 1 deletion providers/openfeature-provider-flagd/openfeature/schemas
2 changes: 1 addition & 1 deletion providers/openfeature-provider-flagd/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ dependencies = [
"pytest-bdd",
"testcontainers",
"asserts",
"grpcio-health-checking==1.60.0",
"grpcio-health-checking==1.68.1",
]
pre-install-commands = [
"hatch build",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ class CacheType(Enum):
DEFAULT_RESOLVER_TYPE = ResolverType.RPC
DEFAULT_RETRY_BACKOFF = 1000
DEFAULT_RETRY_BACKOFF_MAX = 120000
DEFAULT_RETRY_GRACE_ATTEMPTS = 5
DEFAULT_RETRY_GRACE_PERIOD_SECONDS = 5
DEFAULT_STREAM_DEADLINE = 600000
DEFAULT_TLS = False
DEFAULT_TLS_CERT: typing.Optional[str] = None

ENV_VAR_CACHE_SIZE = "FLAGD_MAX_CACHE_SIZE"
ENV_VAR_CACHE_TYPE = "FLAGD_CACHE"
Expand All @@ -41,9 +42,11 @@ class CacheType(Enum):
ENV_VAR_RESOLVER_TYPE = "FLAGD_RESOLVER"
ENV_VAR_RETRY_BACKOFF_MS = "FLAGD_RETRY_BACKOFF_MS"
ENV_VAR_RETRY_BACKOFF_MAX_MS = "FLAGD_RETRY_BACKOFF_MAX_MS"
ENV_VAR_RETRY_GRACE_ATTEMPTS = "FLAGD_RETRY_GRACE_ATTEMPTS"
ENV_VAR_RETRY_GRACE_PERIOD_SECONDS = "FLAGD_RETRY_GRACE_PERIOD"
ENV_VAR_SELECTOR = "FLAGD_SOURCE_SELECTOR"
ENV_VAR_STREAM_DEADLINE_MS = "FLAGD_STREAM_DEADLINE_MS"
ENV_VAR_TLS = "FLAGD_TLS"
ENV_VAR_TLS_CERT = "FLAGD_SERVER_CERT_PATH"

T = typing.TypeVar("T")

Expand Down Expand Up @@ -76,17 +79,19 @@ def __init__( # noqa: PLR0913
host: typing.Optional[str] = None,
port: typing.Optional[int] = None,
tls: typing.Optional[bool] = None,
selector: typing.Optional[str] = None,
resolver: typing.Optional[ResolverType] = None,
offline_flag_source_path: typing.Optional[str] = None,
offline_poll_interval_ms: typing.Optional[int] = None,
retry_backoff_ms: typing.Optional[int] = None,
retry_backoff_max_ms: typing.Optional[int] = None,
retry_grace_attempts: typing.Optional[int] = None,
retry_grace_period: typing.Optional[int] = None,
deadline_ms: typing.Optional[int] = None,
stream_deadline_ms: typing.Optional[int] = None,
keep_alive_time: typing.Optional[int] = None,
cache: typing.Optional[CacheType] = None,
max_cache_size: typing.Optional[int] = None,
cert_path: typing.Optional[str] = None,
):
self.host = env_or_default(ENV_VAR_HOST, DEFAULT_HOST) if host is None else host

Expand Down Expand Up @@ -115,14 +120,16 @@ def __init__( # noqa: PLR0913
else retry_backoff_max_ms
)

self.retry_grace_attempts: int = (
self.retry_grace_period: int = (
int(
env_or_default(
ENV_VAR_RETRY_GRACE_ATTEMPTS, DEFAULT_RETRY_GRACE_ATTEMPTS, cast=int
ENV_VAR_RETRY_GRACE_PERIOD_SECONDS,
DEFAULT_RETRY_GRACE_PERIOD_SECONDS,
cast=int,
)
)
if retry_grace_attempts is None
else retry_grace_attempts
if retry_grace_period is None
else retry_grace_period
)

self.resolver = (
Expand Down Expand Up @@ -198,3 +205,13 @@ def __init__( # noqa: PLR0913
if max_cache_size is None
else max_cache_size
)

self.cert_path = (
env_or_default(ENV_VAR_TLS_CERT, DEFAULT_TLS_CERT)
if cert_path is None
else cert_path
)

self.selector = (
env_or_default(ENV_VAR_SELECTOR, None) if selector is None else selector
)
Loading
Loading