Skip to content

Commit 80f54f1

Browse files
committed
merge main
2 parents 4ec15be + cbace6a commit 80f54f1

21 files changed

+465
-43
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646

4747
- if: matrix.python-version == '3.11'
4848
name: Upload coverage to Codecov
49-
uses: codecov/codecov-action@v4.6.0
49+
uses: codecov/codecov-action@v5.1.2
5050
with:
5151
flags: unittests # optional
5252
name: coverage # optional

.github/workflows/release.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,5 @@ jobs:
5454
run: hatch build
5555

5656
- name: Publish a Python distribution to PyPI
57-
uses: pypa/gh-action-pypi-publish@release/v1
57+
# pinning till fixed https://github.com/pypa/gh-action-pypi-publish/issues/300
58+
uses: pypa/gh-action-pypi-publish@release/v1.11

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
default_stages: [commit]
22
repos:
33
- repo: https://github.com/astral-sh/ruff-pre-commit
4-
rev: v0.7.2
4+
rev: v0.9.1
55
hooks:
66
- id: ruff
77
args: [--fix]
@@ -16,7 +16,7 @@ repos:
1616
- id: check-merge-conflict
1717

1818
- repo: https://github.com/pre-commit/mirrors-mypy
19-
rev: v1.13.0
19+
rev: v1.14.1
2020
hooks:
2121
- id: mypy
2222
files: openfeature

.release-please-manifest.json

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

CHANGELOG.md

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

3+
## [0.7.4](https://github.com/open-feature/python-sdk/compare/v0.7.3...v0.7.4) (2024-11-25)
4+
5+
6+
### 🧹 Chore
7+
8+
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.8.0 ([#395](https://github.com/open-feature/python-sdk/issues/395)) ([cd737a9](https://github.com/open-feature/python-sdk/commit/cd737a9a6aeb6ff64050f759749aab96e73a8c34))
9+
10+
## [0.7.3](https://github.com/open-feature/python-sdk/compare/v0.7.2...v0.7.3) (2024-11-24)
11+
12+
13+
### ✨ New Features
14+
15+
* implement transaction context ([#389](https://github.com/open-feature/python-sdk/issues/389)) ([9b97130](https://github.com/open-feature/python-sdk/commit/9b97130908c5ec07580d66e0f2aad9adf1607f53))
16+
17+
18+
### 🧹 Chore
19+
20+
* **deps:** update codecov/codecov-action action to v5 ([#386](https://github.com/open-feature/python-sdk/issues/386)) ([6cd570f](https://github.com/open-feature/python-sdk/commit/6cd570f64cb50473e0a046d5928e114157598d78))
21+
* **deps:** update codecov/codecov-action action to v5.0.2 ([#388](https://github.com/open-feature/python-sdk/issues/388)) ([e72e329](https://github.com/open-feature/python-sdk/commit/e72e329a2acda0a0399ef2faa04c9b7f3b29bb65))
22+
* **deps:** update codecov/codecov-action action to v5.0.3 ([#390](https://github.com/open-feature/python-sdk/issues/390)) ([3fb7cbc](https://github.com/open-feature/python-sdk/commit/3fb7cbc3ddcc9016ed51e890d9648f1f96284170))
23+
* **deps:** update codecov/codecov-action action to v5.0.4 ([#391](https://github.com/open-feature/python-sdk/issues/391)) ([9759bfa](https://github.com/open-feature/python-sdk/commit/9759bfaa21ed6d6e5e84c5559304db9a49b61884))
24+
* **deps:** update codecov/codecov-action action to v5.0.5 ([#392](https://github.com/open-feature/python-sdk/issues/392)) ([a652004](https://github.com/open-feature/python-sdk/commit/a652004fde72cb1dcf730f146db36d686589b642))
25+
* **deps:** update codecov/codecov-action action to v5.0.6 ([#393](https://github.com/open-feature/python-sdk/issues/393)) ([24970b5](https://github.com/open-feature/python-sdk/commit/24970b5d0915f533d88e4cd0fb54bea8cd28cab0))
26+
* **deps:** update codecov/codecov-action action to v5.0.7 ([#394](https://github.com/open-feature/python-sdk/issues/394)) ([f024a6f](https://github.com/open-feature/python-sdk/commit/f024a6f3407b3e3ce8ea16a80982a7642c9f2f20))
27+
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.7.2 ([#381](https://github.com/open-feature/python-sdk/issues/381)) ([2d8fadf](https://github.com/open-feature/python-sdk/commit/2d8fadf40afb98e2915b645278358fca367447d7))
28+
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.7.3 ([#384](https://github.com/open-feature/python-sdk/issues/384)) ([1eb6f4c](https://github.com/open-feature/python-sdk/commit/1eb6f4c6558f3dd20cb2389f9650a6476ca20fcb))
29+
* **deps:** update pre-commit hook astral-sh/ruff-pre-commit to v0.7.4 ([#387](https://github.com/open-feature/python-sdk/issues/387)) ([3d77f24](https://github.com/open-feature/python-sdk/commit/3d77f2410751646380f58a77cad0fd5851da30b5))
30+
331
## [0.7.2](https://github.com/open-feature/python-sdk/compare/v0.7.1...v0.7.2) (2024-10-24)
432

533

README.md

Lines changed: 95 additions & 14 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.7.2">
23-
<img alt="Latest version" src="https://img.shields.io/static/v1?label=release&message=v0.7.2&color=blue&style=for-the-badge" />
22+
<a href="https://github.com/open-feature/python-sdk/releases/tag/v0.7.4">
23+
<img alt="Latest version" src="https://img.shields.io/static/v1?label=release&message=v0.7.4&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.7.2
63+
pip install openfeature-sdk==0.7.4
6464
```
6565

6666
#### requirements.txt
6767

6868
```bash
69-
openfeature-sdk==0.7.2
69+
openfeature-sdk==0.7.4
7070
```
7171

7272
```python
@@ -99,16 +99,17 @@ print("Value: " + str(flag_value))
9999

100100
## 🌟 Features
101101

102-
| Status | Features | Description |
103-
| ------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
104-
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
105-
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
106-
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
107-
|| [Logging](#logging) | Integrate with popular logging packages. |
108-
|| [Domains](#domains) | Logically bind clients with providers. |
109-
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
110-
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
111-
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
102+
| Status | Features | Description |
103+
|--------|---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
104+
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
105+
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
106+
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
107+
|| [Logging](#logging) | Integrate with popular logging packages. |
108+
|| [Domains](#domains) | Logically bind clients with providers. |
109+
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
110+
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
111+
|| [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread) |
112+
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
112113

113114
<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
114115

@@ -235,6 +236,86 @@ def on_provider_ready(event_details: EventDetails):
235236
client.add_handler(ProviderEvent.PROVIDER_READY, on_provider_ready)
236237
```
237238

239+
### Transaction Context Propagation
240+
241+
Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP).
242+
Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread).
243+
244+
You can implement a different transaction context propagator by implementing the `TransactionContextPropagator` class exported by the OpenFeature SDK.
245+
In most cases you can use `ContextVarsTransactionContextPropagator` as it works for `threads` and `asyncio` using [Context Variables](https://peps.python.org/pep-0567/).
246+
247+
The following example shows a **multithreaded** Flask application using transaction context propagation to propagate the request ip and user id into request scoped transaction context.
248+
249+
```python
250+
from flask import Flask, request
251+
from openfeature import api
252+
from openfeature.evaluation_context import EvaluationContext
253+
from openfeature.transaction_context import ContextVarsTransactionContextPropagator
254+
255+
# Initialize the Flask app
256+
app = Flask(__name__)
257+
258+
# Set the transaction context propagator
259+
api.set_transaction_context_propagator(ContextVarsTransactionContextPropagator())
260+
261+
# Middleware to set the transaction context
262+
# You can call api.set_transaction_context anywhere you have information,
263+
# you want to have available in the code-paths below the current one.
264+
@app.before_request
265+
def set_request_transaction_context():
266+
ip = request.headers.get("X-Forwarded-For", request.remote_addr)
267+
user_id = request.headers.get("User-Id") # Assuming we're getting the user ID from a header
268+
evaluation_context = EvaluationContext(targeting_key=user_id, attributes={"ipAddress": ip})
269+
api.set_transaction_context(evaluation_context)
270+
271+
def create_response() -> str:
272+
# This method can be anywhere in our code.
273+
# The feature flag evaluation will automatically contain the transaction context merged with other context
274+
new_response = api.get_client().get_string_value("response-message", "Hello User!")
275+
return f"Message from server: {new_response}"
276+
277+
# Example route where we use the transaction context
278+
@app.route('/greeting')
279+
def some_endpoint():
280+
return create_response()
281+
```
282+
283+
This also works for asyncio based implementations e.g. FastApi as seen in the following example:
284+
285+
```python
286+
from fastapi import FastAPI, Request
287+
from openfeature import api
288+
from openfeature.evaluation_context import EvaluationContext
289+
from openfeature.transaction_context import ContextVarsTransactionContextPropagator
290+
291+
# Initialize the FastAPI app
292+
app = FastAPI()
293+
294+
# Set the transaction context propagator
295+
api.set_transaction_context_propagator(ContextVarsTransactionContextPropagator())
296+
297+
# Middleware to set the transaction context
298+
@app.middleware("http")
299+
async def set_request_transaction_context(request: Request, call_next):
300+
ip = request.headers.get("X-Forwarded-For", request.client.host)
301+
user_id = request.headers.get("User-Id") # Assuming we're getting the user ID from a header
302+
evaluation_context = EvaluationContext(targeting_key=user_id, attributes={"ipAddress": ip})
303+
api.set_transaction_context(evaluation_context)
304+
response = await call_next(request)
305+
return response
306+
307+
def create_response() -> str:
308+
# This method can be located anywhere in our code.
309+
# The feature flag evaluation will automatically include the transaction context merged with other context.
310+
new_response = api.get_client().get_string_value("response-message", "Hello User!")
311+
return f"Message from server: {new_response}"
312+
313+
# Example route where we use the transaction context
314+
@app.get('/greeting')
315+
async def some_endpoint():
316+
return create_response()
317+
```
318+
238319
### Shutdown
239320

240321
The OpenFeature API provides a shutdown function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down.

openfeature/api.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,33 @@
1212
from openfeature.provider import FeatureProvider
1313
from openfeature.provider._registry import provider_registry
1414
from openfeature.provider.metadata import Metadata
15+
from openfeature.transaction_context import TransactionContextPropagator
16+
from openfeature.transaction_context.no_op_transaction_context_propagator import (
17+
NoOpTransactionContextPropagator,
18+
)
1519

1620
__all__ = [
17-
"get_client",
18-
"set_provider",
19-
"clear_providers",
20-
"get_provider_metadata",
21-
"get_evaluation_context",
22-
"set_evaluation_context",
21+
"add_handler",
2322
"add_hooks",
2423
"clear_hooks",
24+
"clear_providers",
25+
"get_client",
26+
"get_evaluation_context",
2527
"get_hooks",
26-
"shutdown",
27-
"add_handler",
28+
"get_provider_metadata",
29+
"get_transaction_context",
2830
"remove_handler",
31+
"set_evaluation_context",
32+
"set_provider",
33+
"set_transaction_context",
34+
"set_transaction_context_propagator",
35+
"shutdown",
2936
]
3037

3138
_evaluation_context = EvaluationContext()
39+
_evaluation_transaction_context_propagator: TransactionContextPropagator = (
40+
NoOpTransactionContextPropagator()
41+
)
3242

3343
_hooks: typing.List[Hook] = []
3444

@@ -68,6 +78,24 @@ def set_evaluation_context(evaluation_context: EvaluationContext) -> None:
6878
_evaluation_context = evaluation_context
6979

7080

81+
def set_transaction_context_propagator(
82+
transaction_context_propagator: TransactionContextPropagator,
83+
) -> None:
84+
global _evaluation_transaction_context_propagator
85+
_evaluation_transaction_context_propagator = transaction_context_propagator
86+
87+
88+
def get_transaction_context() -> EvaluationContext:
89+
return _evaluation_transaction_context_propagator.get_transaction_context()
90+
91+
92+
def set_transaction_context(evaluation_context: EvaluationContext) -> None:
93+
global _evaluation_transaction_context_propagator
94+
_evaluation_transaction_context_propagator.set_transaction_context(
95+
evaluation_context
96+
)
97+
98+
7199
def add_hooks(hooks: typing.List[Hook]) -> None:
72100
global _hooks
73101
_hooks = _hooks + hooks

openfeature/client.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,9 +510,12 @@ def _before_hooks_and_merge_context(
510510
if evaluation_context:
511511
invocation_context = invocation_context.merge(ctx2=evaluation_context)
512512

513-
# Requirement 3.2.2 merge: API.context->client.context->invocation.context
513+
# Requirement 3.2.2 merge: API.context->transaction.context->client.context->invocation.context
514514
merged_context = (
515-
api.get_evaluation_context().merge(self.context).merge(invocation_context)
515+
api.get_evaluation_context()
516+
.merge(api.get_transaction_context())
517+
.merge(self.context)
518+
.merge(invocation_context)
516519
)
517520
return merged_context
518521

openfeature/event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from openfeature.exception import ErrorCode
88

9-
__all__ = ["ProviderEvent", "ProviderEventDetails", "EventDetails", "EventHandler"]
9+
__all__ = ["EventDetails", "EventHandler", "ProviderEvent", "ProviderEventDetails"]
1010

1111

1212
class ProviderEvent(Enum):

openfeature/exception.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
from enum import Enum
66

77
__all__ = [
8-
"OpenFeatureError",
9-
"ProviderNotReadyError",
10-
"ProviderFatalError",
8+
"ErrorCode",
119
"FlagNotFoundError",
1210
"GeneralError",
11+
"InvalidContextError",
12+
"OpenFeatureError",
1313
"ParseError",
14-
"TypeMismatchError",
14+
"ProviderFatalError",
15+
"ProviderNotReadyError",
1516
"TargetingKeyMissingError",
16-
"InvalidContextError",
17-
"ErrorCode",
17+
"TypeMismatchError",
1818
]
1919

2020

0 commit comments

Comments
 (0)