Skip to content

Commit 2caa421

Browse files
committed
Merge remote-tracking branch 'origin/main' into fork/balgaly/feat/500-context-merging-e2e
2 parents a4abc37 + 869766b commit 2caa421

21 files changed

Lines changed: 843 additions & 133 deletions

.github/workflows/build.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ permissions:
1717
contents: read
1818

1919
env:
20-
PREK_VERSION: '0.3.x'
20+
PREK_VERSION: '0.4.x'
2121
TARGET_PYTHON_VERSION: "3.14"
2222

2323
jobs:
@@ -33,7 +33,7 @@ jobs:
3333
submodules: recursive
3434

3535
- name: Install uv and set the python version
36-
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
36+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
3737
with:
3838
python-version: ${{ matrix.python-version }}
3939
activate-environment: true
@@ -49,7 +49,7 @@ jobs:
4949

5050
- if: matrix.python-version == env.TARGET_PYTHON_VERSION
5151
name: Upload coverage to Codecov
52-
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
52+
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
5353
with:
5454
flags: unittests # optional
5555
name: coverage # optional
@@ -63,7 +63,7 @@ jobs:
6363
steps:
6464
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
6565
- name: Install uv and set the python version
66-
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
66+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
6767
with:
6868
python-version: ${{ env.TARGET_PYTHON_VERSION }}
6969
ignore-nothing-to-cache: true
@@ -81,7 +81,7 @@ jobs:
8181
steps:
8282
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
8383
- name: Install uv and set the python version
84-
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
84+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
8585
with:
8686
python-version: ${{ env.TARGET_PYTHON_VERSION }}
8787
ignore-nothing-to-cache: true

.github/workflows/release.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@ jobs:
2424
runs-on: ubuntu-latest
2525

2626
steps:
27-
- uses: googleapis/release-please-action@db8f2c60ee802b3748b512940dde88eabd7b7e01 # v3
27+
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
2828
id: release
2929
with:
30-
command: manifest
3130
token: ${{secrets.RELEASE_PLEASE_ACTION_TOKEN}}
32-
default-branch: main
33-
signoff: "OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com>"
31+
target-branch: main
3432
outputs:
3533
release_created: ${{ steps.release.outputs.release_created }}
3634
release_tag_name: ${{ steps.release.outputs.tag_name }}
@@ -48,7 +46,7 @@ jobs:
4846
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
4947

5048
- name: Install uv and set the python version
51-
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
49+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
5250
with:
5351
python-version: ${{ env.TARGET_PYTHON_VERSION }}
5452

.pre-commit-config.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
default_stages: [pre-commit]
22
repos:
33
- repo: https://github.com/astral-sh/ruff-pre-commit
4-
rev: v0.15.7
4+
rev: v0.15.15
55
hooks:
66
- id: ruff-check
77
priority: 1
@@ -22,13 +22,14 @@ repos:
2222
priority: 0
2323

2424
- repo: https://github.com/tox-dev/pyproject-fmt
25-
rev: v2.20.0
25+
rev: v2.21.2
2626
hooks:
2727
- id: pyproject-fmt
2828
priority: 0
29+
additional_dependencies: [toml-fmt-common<1.3.3] # removed after fix https://github.com/tox-dev/toml-fmt/issues/355
2930

3031
- repo: https://github.com/pre-commit/mirrors-mypy
31-
rev: v1.20.0
32+
rev: v2.1.0
3233
hooks:
3334
- id: mypy
3435
priority: 1

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{".":"0.8.4"}
1+
{".":"0.10.0"}

CHANGELOG.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,88 @@
11
# Changelog
22

3+
## [0.10.0](https://github.com/open-feature/python-sdk/compare/v0.9.0...v0.10.0) (2026-06-01)
4+
5+
6+
### ⚠ BREAKING CHANGES
7+
8+
* make set_provider non-blocking, add set_provider_and_wait ([#595](https://github.com/open-feature/python-sdk/issues/595))
9+
10+
> [!IMPORTANT]
11+
> `set_provider()` no longer blocks until the provider is ready; provider initialization runs in the background. If your workload needs to wait until the `PROVIDER_READY` event has fired before proceeding (for example, to avoid `PROVIDER_NOT_READY` evaluations during startup), use `set_provider_and_wait()` instead.
12+
13+
### 🐛 Bug Fixes
14+
15+
* isolate provider event handler dispatch ([#599](https://github.com/open-feature/python-sdk/issues/599)) ([0a96426](https://github.com/open-feature/python-sdk/commit/0a96426a3e1df497f376c08a499cfa9227c7e2c2))
16+
17+
18+
### ✨ New Features
19+
20+
* make set_provider non-blocking, add set_provider_and_wait ([#595](https://github.com/open-feature/python-sdk/issues/595)) ([cbacef0](https://github.com/open-feature/python-sdk/commit/cbacef0093c0ef44bc2ba8ad36de1c5497cf0cff))
21+
22+
23+
### 🧹 Chore
24+
25+
* **deps:** update astral-sh/setup-uv action to v8 ([#603](https://github.com/open-feature/python-sdk/issues/603)) ([4422fad](https://github.com/open-feature/python-sdk/commit/4422fad635199d11bfd820425bf8938a68cec1ee))
26+
* **deps:** update codecov/codecov-action action to v6 ([#604](https://github.com/open-feature/python-sdk/issues/604)) ([e00fab3](https://github.com/open-feature/python-sdk/commit/e00fab3f04d52b3d35258d87c68e3c8d3954af31))
27+
* **deps:** update dependency prek to &gt;=0.4.3,&lt;0.5.0 ([#607](https://github.com/open-feature/python-sdk/issues/607)) ([760d808](https://github.com/open-feature/python-sdk/commit/760d808009e4eddd2984723d8bb3e3382df180ec))
28+
* **deps:** update googleapis/release-please-action action to v5 ([#605](https://github.com/open-feature/python-sdk/issues/605)) ([aa1366a](https://github.com/open-feature/python-sdk/commit/aa1366abeccf8d4463e933fbba035891e0531125))
29+
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.15.15 ([#608](https://github.com/open-feature/python-sdk/issues/608)) ([5f4e42d](https://github.com/open-feature/python-sdk/commit/5f4e42dee5fad734a39b459b08b94045a7210ec3))
30+
* **deps:** update pre-commit hook pre-commit/mirrors-mypy to v2 ([#606](https://github.com/open-feature/python-sdk/issues/606)) ([d18beef](https://github.com/open-feature/python-sdk/commit/d18beefeaebb717102c40b02acbedf6b3c0c16a8))
31+
* **deps:** update pre-commit hook tox-dev/pyproject-fmt to v2.21.2 ([#601](https://github.com/open-feature/python-sdk/issues/601)) ([5cc9534](https://github.com/open-feature/python-sdk/commit/5cc9534661a1901c5d3a0b328d65df6495c46b92))
32+
33+
## [0.9.0](https://github.com/open-feature/python-sdk/compare/v0.8.4...v0.9.0) (2026-04-17)
34+
35+
36+
### ⚠ BREAKING CHANGES
37+
38+
* remove deprecated openfeature.provider.provider module ([#558](https://github.com/open-feature/python-sdk/issues/558))
39+
* Drop python 3.9 support ([#554](https://github.com/open-feature/python-sdk/issues/554))
40+
41+
### 🐛 Bug Fixes
42+
43+
* correctly reset api state on shutdown ([#589](https://github.com/open-feature/python-sdk/issues/589)) ([eaa195b](https://github.com/open-feature/python-sdk/commit/eaa195b1761101a04c767ffbd235d809c9f724ca))
44+
* Prevent providers from being initialized multiple times ([45f7fd1](https://github.com/open-feature/python-sdk/commit/45f7fd1fd3dd39240282e23bf0ce587435f3ff2f))
45+
* Prevent providers from being shutdown multiple times ([45f7fd1](https://github.com/open-feature/python-sdk/commit/45f7fd1fd3dd39240282e23bf0ce587435f3ff2f))
46+
* replace project.scripts with poethepoet ([#563](https://github.com/open-feature/python-sdk/issues/563)) ([05382aa](https://github.com/open-feature/python-sdk/commit/05382aad368800d593209ece7966045b9daf9756))
47+
* validate domain is present when calling `set_provider` on registry ([#561](https://github.com/open-feature/python-sdk/issues/561)) ([9fc6121](https://github.com/open-feature/python-sdk/commit/9fc612180a3ea8d26820394b329abd24804d2df4))
48+
49+
50+
### ✨ New Features
51+
52+
* add logging hook ([#577](https://github.com/open-feature/python-sdk/issues/577)) ([ae59756](https://github.com/open-feature/python-sdk/commit/ae59756109f39f4a0d0551cdd80aeaa5aaada571))
53+
* Drop python 3.9 support ([04106f5](https://github.com/open-feature/python-sdk/commit/04106f532d3a92d9dd70fd2160a07672ca4404f8))
54+
* Implement tracking ([#564](https://github.com/open-feature/python-sdk/issues/564)) ([b81ee21](https://github.com/open-feature/python-sdk/commit/b81ee21c5bda6408bf41be5f22f4c5f7b280dfee))
55+
56+
57+
### 🧹 Chore
58+
59+
* add pyproject-fmt hook ([#578](https://github.com/open-feature/python-sdk/issues/578)) ([fafd902](https://github.com/open-feature/python-sdk/commit/fafd9023c7c1c82656b5af99c998124e88160fdf))
60+
* **deps:** pin pypa/gh-action-pypi-publish action to ed0c539 ([#582](https://github.com/open-feature/python-sdk/issues/582)) ([c7ada5a](https://github.com/open-feature/python-sdk/commit/c7ada5a13eab9efcff93de7dbf4c3d8291407599))
61+
* **deps:** update actions/checkout digest to de0fac2 ([#572](https://github.com/open-feature/python-sdk/issues/572)) ([e9dfeac](https://github.com/open-feature/python-sdk/commit/e9dfeac1e696ec038af3b62ed5a6bb6d11453696))
62+
* **deps:** update astral-sh/setup-uv digest to 37802ad ([#581](https://github.com/open-feature/python-sdk/issues/581)) ([ddb8fd1](https://github.com/open-feature/python-sdk/commit/ddb8fd1296714f4849c821db90d0832337d1f9bb))
63+
* **deps:** update astral-sh/setup-uv digest to e06108d ([#573](https://github.com/open-feature/python-sdk/issues/573)) ([c35a1e5](https://github.com/open-feature/python-sdk/commit/c35a1e5664613da36e1264ec348b540e1aa0c8da))
64+
* **deps:** update dependency uv_build to ~=0.10.0 ([#571](https://github.com/open-feature/python-sdk/issues/571)) ([a6366e2](https://github.com/open-feature/python-sdk/commit/a6366e21c41f1c7560ca245840ece09f31c6938e))
65+
* **deps:** update github/codeql-action digest to 0d579ff ([#574](https://github.com/open-feature/python-sdk/issues/574)) ([0696033](https://github.com/open-feature/python-sdk/commit/0696033b30788bed6d7e0c0360192c2913f130cd))
66+
* **deps:** update github/codeql-action digest to c10b806 ([#583](https://github.com/open-feature/python-sdk/issues/583)) ([9478ea0](https://github.com/open-feature/python-sdk/commit/9478ea0c0188a997e7396083e9d9b6983604985a))
67+
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.15.6 ([#575](https://github.com/open-feature/python-sdk/issues/575)) ([d0d7d8f](https://github.com/open-feature/python-sdk/commit/d0d7d8f6a2c83cf9488cb2185ebb2cb9cc32fbfe))
68+
* **deps:** update pre-commit hook pre-commit/mirrors-mypy to v1.19.1 ([#576](https://github.com/open-feature/python-sdk/issues/576)) ([ade3870](https://github.com/open-feature/python-sdk/commit/ade3870dcd734722df357a11771bfc584f128093))
69+
* **deps:** update pre-commit hook pre-commit/mirrors-mypy to v1.20.0 ([#588](https://github.com/open-feature/python-sdk/issues/588)) ([398d140](https://github.com/open-feature/python-sdk/commit/398d140883b9a16e866fc820d49442af1a5b7e69))
70+
* Drop python 3.9 support ([#554](https://github.com/open-feature/python-sdk/issues/554)) ([04106f5](https://github.com/open-feature/python-sdk/commit/04106f532d3a92d9dd70fd2160a07672ca4404f8))
71+
* replace pre-commit with prek ([#580](https://github.com/open-feature/python-sdk/issues/580)) ([d1e0cde](https://github.com/open-feature/python-sdk/commit/d1e0cdee150bebd63ea74cda917df44846c016c3))
72+
* update pytest to v9 ([#559](https://github.com/open-feature/python-sdk/issues/559)) ([71ebb9f](https://github.com/open-feature/python-sdk/commit/71ebb9fe961acb2ed256f12695123864fcf69cd5))
73+
* update uv to 0.11 and relax version requirement ([#587](https://github.com/open-feature/python-sdk/issues/587)) ([7d9229f](https://github.com/open-feature/python-sdk/commit/7d9229fd4f039ccd94b2a8a7ebfc0c4f0476294d))
74+
75+
76+
### 📚 Documentation
77+
78+
* fix inaccuracies in README code examples ([#592](https://github.com/open-feature/python-sdk/issues/592)) ([17d62c4](https://github.com/open-feature/python-sdk/commit/17d62c4946c81985577b0372e6a6344b7b13470d))
79+
80+
81+
### 🔄 Refactoring
82+
83+
* Delete provider status instead of marking as NOT_READY ([45f7fd1](https://github.com/open-feature/python-sdk/commit/45f7fd1fd3dd39240282e23bf0ce587435f3ff2f))
84+
* remove deprecated openfeature.provider.provider module ([#558](https://github.com/open-feature/python-sdk/issues/558)) ([7c27c7a](https://github.com/open-feature/python-sdk/commit/7c27c7ae58ab498fdc85ab508a76f7d5261f02f1))
85+
386
## [0.8.4](https://github.com/open-feature/python-sdk/compare/v0.8.3...v0.8.4) (2025-12-08)
487

588

README.md

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
<!-- x-release-please-start-version -->
2121

22-
<a href="https://github.com/open-feature/python-sdk/releases/tag/v0.8.4">
23-
<img alt="Latest version" src="https://img.shields.io/static/v1?label=release&message=v0.8.4&color=blue&style=for-the-badge" />
22+
<a href="https://github.com/open-feature/python-sdk/releases/tag/v0.10.0">
23+
<img alt="Latest version" src="https://img.shields.io/static/v1?label=release&message=v0.10.0&color=blue&style=for-the-badge" />
2424
</a>
2525

2626
<!-- x-release-please-end -->
@@ -60,13 +60,13 @@
6060
#### Pip install
6161

6262
```bash
63-
pip install openfeature-sdk==0.8.4
63+
pip install openfeature-sdk==0.10.0
6464
```
6565

6666
#### requirements.txt
6767

6868
```bash
69-
openfeature-sdk==0.8.4
69+
openfeature-sdk==0.10.0
7070
```
7171

7272
```python
@@ -130,6 +130,15 @@ api.set_provider(NoOpProvider())
130130
open_feature_client = api.get_client()
131131
```
132132

133+
`set_provider()` is non-blocking: it registers the provider immediately and runs initialization in a background thread.
134+
Flag evaluations during the initialization window return the default value with a `PROVIDER_NOT_READY` error code.
135+
Use `set_provider_and_wait()` if you need to ensure the provider is ready before proceeding:
136+
137+
```python
138+
# blocks until the provider is initialized (or raises on failure)
139+
api.set_provider_and_wait(NoOpProvider())
140+
```
141+
133142
In some situations, it may be beneficial to register multiple providers in the same application.
134143
This is possible using [domains](#domains), which is covered in more detail below.
135144

@@ -142,11 +151,11 @@ If the flag management system you're using supports targeting, you can provide t
142151
```python
143152
from openfeature.api import (
144153
get_client,
145-
get_provider,
146154
set_provider,
147155
get_evaluation_context,
148156
set_evaluation_context,
149157
)
158+
from openfeature.evaluation_context import EvaluationContext
150159

151160
global_context = EvaluationContext(
152161
targeting_key="targeting_key1", attributes={"application": "value1"}
@@ -159,7 +168,7 @@ request_context = EvaluationContext(
159168
set_evaluation_context(global_context)
160169

161170
# merge second context
162-
client = get_client(name="No-op Provider")
171+
client = get_client(domain="No-op Provider")
163172
client.get_string_value("email", "fallback", request_context)
164173
```
165174

@@ -172,19 +181,20 @@ If the hook you're looking for hasn't been created yet, see the [develop a hook]
172181
Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level.
173182

174183
```python
184+
from openfeature import api
175185
from openfeature.api import add_hooks
176186
from openfeature.flag_evaluation import FlagEvaluationOptions
177187

178188
# set global hooks at the API-level
179189
add_hooks([MyHook()])
180190

181191
# or configure them in the client
182-
client = OpenFeatureClient()
192+
client = api.get_client()
183193
client.add_hooks([MyHook()])
184194

185195
# or at the invocation-level
186196
options = FlagEvaluationOptions(hooks=[MyHook()])
187-
client.get_boolean_flag("my-flag", False, flag_evaluation_options=options)
197+
client.get_boolean_value("my-flag", False, flag_evaluation_options=options)
188198
```
189199
### Tracking
190200

@@ -254,7 +264,7 @@ Please refer to the documentation of the provider you're using to see what event
254264

255265
```python
256266
from openfeature import api
257-
from openfeature.provider import ProviderEvent
267+
from openfeature.event import EventDetails, ProviderEvent
258268

259269
def on_provider_ready(event_details: EventDetails):
260270
print(f"Provider {event_details.provider_name} is ready")
@@ -503,10 +513,11 @@ Implement your own hook by creating a hook that inherits from the `Hook` class.
503513
Any of the evaluation life-cycle stages (`before`/`after`/`error`/`finally_after`) can be override to add the desired business logic.
504514

505515
```python
506-
from openfeature.hook import Hook
516+
from openfeature.hook import Hook, HookContext, HookHints
517+
from openfeature.flag_evaluation import FlagEvaluationDetails, FlagValueType
507518

508519
class MyHook(Hook):
509-
def after(self, hook_context: HookContext, details: FlagEvaluationDetails, hints: dict):
520+
def after(self, hook_context: HookContext, details: FlagEvaluationDetails[FlagValueType], hints: HookHints):
510521
print("This runs after the flag has been evaluated")
511522

512523
```

openfeature/_event_support.py

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from __future__ import annotations
22

3+
import atexit
34
import threading
45
import typing
56
from collections import defaultdict
7+
from concurrent.futures import ThreadPoolExecutor
8+
from logging import getLogger
69

710
from openfeature.event import (
811
EventDetails,
@@ -16,6 +19,10 @@
1619
from openfeature.client import OpenFeatureClient
1720

1821

22+
logger = getLogger("openfeature")
23+
_event_executor = ThreadPoolExecutor(thread_name_prefix="openfeature-event-handler")
24+
atexit.register(_event_executor.shutdown, wait=True)
25+
1926
_global_lock = threading.RLock()
2027
_global_handlers: dict[ProviderEvent, list[EventHandler]] = defaultdict(list)
2128

@@ -29,14 +36,22 @@ def run_client_handlers(
2936
client: OpenFeatureClient, event: ProviderEvent, details: EventDetails
3037
) -> None:
3138
with _client_lock:
32-
for handler in _client_handlers[client][event]:
33-
handler(details)
39+
handlers_by_event = _client_handlers.get(client)
40+
if handlers_by_event is None:
41+
return
42+
43+
handlers = tuple(handlers_by_event.get(event, ()))
44+
45+
for handler in handlers:
46+
_submit_handler(handler, details)
3447

3548

3649
def run_global_handlers(event: ProviderEvent, details: EventDetails) -> None:
3750
with _global_lock:
38-
for handler in _global_handlers[event]:
39-
handler(details)
51+
handlers = tuple(_global_handlers.get(event, ()))
52+
53+
for handler in handlers:
54+
_submit_handler(handler, details)
4055

4156

4257
def add_client_handler(
@@ -83,9 +98,12 @@ def run_handlers_for_provider(
8398
run_global_handlers(event, details)
8499
# run the handlers for clients associated to this provider
85100
with _client_lock:
86-
for client in _client_handlers:
87-
if client.provider == provider:
88-
run_client_handlers(client, event, details)
101+
clients = tuple(
102+
client for client in _client_handlers if client.provider == provider
103+
)
104+
105+
for client in clients:
106+
run_client_handlers(client, event, details)
89107

90108

91109
def _run_immediate_handler(
@@ -98,7 +116,20 @@ def _run_immediate_handler(
98116
ProviderStatus.STALE: ProviderEvent.PROVIDER_STALE,
99117
}
100118
if event == status_to_event.get(client.get_provider_status()):
101-
handler(EventDetails(provider_name=client.provider.get_metadata().name))
119+
_submit_handler(
120+
handler, EventDetails(provider_name=client.provider.get_metadata().name)
121+
)
122+
123+
124+
def _submit_handler(handler: EventHandler, details: EventDetails) -> None:
125+
_event_executor.submit(_run_handler, handler, details)
126+
127+
128+
def _run_handler(handler: EventHandler, details: EventDetails) -> None:
129+
try:
130+
handler(details)
131+
except Exception:
132+
logger.exception("Unhandled exception in OpenFeature event handler")
102133

103134

104135
def clear() -> None:

0 commit comments

Comments
 (0)