Skip to content

fix: fix cycle dependency between api and client #480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
65 changes: 9 additions & 56 deletions openfeature/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@

from openfeature import _event_support
from openfeature.client import OpenFeatureClient
from openfeature.evaluation_context import EvaluationContext
from openfeature.evaluation_context import (
get_evaluation_context,
set_evaluation_context,
)
from openfeature.event import (
EventHandler,
ProviderEvent,
)
from openfeature.exception import GeneralError
from openfeature.hook import Hook
from openfeature.hook import add_hooks, clear_hooks, get_hooks
from openfeature.provider import FeatureProvider
from openfeature.provider._registry import provider_registry
from openfeature.provider.metadata import Metadata
from openfeature.transaction_context import TransactionContextPropagator
from openfeature.transaction_context.no_op_transaction_context_propagator import (
NoOpTransactionContextPropagator,
from openfeature.transaction_context import (
get_transaction_context,
set_transaction_context,
set_transaction_context_propagator,
)

__all__ = [
Expand All @@ -35,13 +38,6 @@
"shutdown",
]

_evaluation_context = EvaluationContext()
_evaluation_transaction_context_propagator: TransactionContextPropagator = (
NoOpTransactionContextPropagator()
)

_hooks: list[Hook] = []


def get_client(
domain: typing.Optional[str] = None, version: typing.Optional[str] = None
Expand All @@ -67,49 +63,6 @@ def get_provider_metadata(domain: typing.Optional[str] = None) -> Metadata:
return provider_registry.get_provider(domain).get_metadata()


def get_evaluation_context() -> EvaluationContext:
return _evaluation_context


def set_evaluation_context(evaluation_context: EvaluationContext) -> None:
global _evaluation_context
if evaluation_context is None:
raise GeneralError(error_message="No api level evaluation context")
_evaluation_context = evaluation_context


def set_transaction_context_propagator(
transaction_context_propagator: TransactionContextPropagator,
) -> None:
global _evaluation_transaction_context_propagator
_evaluation_transaction_context_propagator = transaction_context_propagator


def get_transaction_context() -> EvaluationContext:
return _evaluation_transaction_context_propagator.get_transaction_context()


def set_transaction_context(evaluation_context: EvaluationContext) -> None:
global _evaluation_transaction_context_propagator
_evaluation_transaction_context_propagator.set_transaction_context(
evaluation_context
)


def add_hooks(hooks: list[Hook]) -> None:
global _hooks
_hooks = _hooks + hooks


def clear_hooks() -> None:
global _hooks
_hooks = []


def get_hooks() -> list[Hook]:
return _hooks


def shutdown() -> None:
provider_registry.shutdown()

Expand Down
16 changes: 7 additions & 9 deletions openfeature/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from collections.abc import Awaitable
from dataclasses import dataclass

from openfeature import _event_support, api
from openfeature.evaluation_context import EvaluationContext
from openfeature import _event_support
from openfeature.evaluation_context import EvaluationContext, get_evaluation_context
from openfeature.event import EventHandler, ProviderEvent
from openfeature.exception import (
ErrorCode,
Expand All @@ -21,7 +21,7 @@
FlagType,
Reason,
)
from openfeature.hook import Hook, HookContext, HookHints
from openfeature.hook import Hook, HookContext, HookHints, get_hooks
from openfeature.hook._hook_support import (
after_all_hooks,
after_hooks,
Expand All @@ -30,6 +30,7 @@
)
from openfeature.provider import FeatureProvider, ProviderStatus
from openfeature.provider._registry import provider_registry
from openfeature.transaction_context import get_transaction_context

__all__ = [
"ClientMetadata",
Expand Down Expand Up @@ -433,10 +434,7 @@ def _establish_hooks_and_provider(
# in the flag evaluation
# before: API, Client, Invocation, Provider
merged_hooks = (
api.get_hooks()
+ self.hooks
+ evaluation_hooks
+ provider.get_provider_hooks()
get_hooks() + self.hooks + evaluation_hooks + provider.get_provider_hooks()
)
# after, error, finally: Provider, Invocation, Client, API
reversed_merged_hooks = merged_hooks[:]
Expand Down Expand Up @@ -474,8 +472,8 @@ def _before_hooks_and_merge_context(

# Requirement 3.2.2 merge: API.context->transaction.context->client.context->invocation.context
merged_context = (
api.get_evaluation_context()
.merge(api.get_transaction_context())
get_evaluation_context()
.merge(get_transaction_context())
.merge(self.context)
.merge(invocation_context)
)
Expand Down
19 changes: 0 additions & 19 deletions openfeature/evaluation_context.py

This file was deleted.

38 changes: 38 additions & 0 deletions openfeature/evaluation_context/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations

import typing
from dataclasses import dataclass, field

from openfeature.exception import GeneralError

__all__ = ["EvaluationContext", "get_evaluation_context", "set_evaluation_context"]


@dataclass
class EvaluationContext:
targeting_key: typing.Optional[str] = None
attributes: dict = field(default_factory=dict)

def merge(self, ctx2: EvaluationContext) -> EvaluationContext:
if not (self and ctx2):
return self or ctx2

Check warning on line 18 in openfeature/evaluation_context/__init__.py

View check run for this annotation

Codecov / codecov/patch

openfeature/evaluation_context/__init__.py#L18

Added line #L18 was not covered by tests

attributes = {**self.attributes, **ctx2.attributes}
targeting_key = ctx2.targeting_key or self.targeting_key

return EvaluationContext(targeting_key=targeting_key, attributes=attributes)


def get_evaluation_context() -> EvaluationContext:
return _evaluation_context


def set_evaluation_context(evaluation_context: EvaluationContext) -> None:
global _evaluation_context
if evaluation_context is None:
raise GeneralError(error_message="No api level evaluation context")
_evaluation_context = evaluation_context


# need to be at the bottom, because of the definition order
_evaluation_context = EvaluationContext()
26 changes: 25 additions & 1 deletion openfeature/hook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@
from openfeature.client import ClientMetadata
from openfeature.provider.metadata import Metadata

__all__ = ["Hook", "HookContext", "HookHints", "HookType"]
__all__ = [
"Hook",
"HookContext",
"HookHints",
"HookType",
"add_hooks",
"clear_hooks",
"get_hooks",
]

_hooks: list[Hook] = []


class HookType(Enum):
Expand Down Expand Up @@ -133,3 +143,17 @@ def supports_flag_value_type(self, flag_type: FlagType) -> bool:
or not (False)
"""
return True


def add_hooks(hooks: list[Hook]) -> None:
global _hooks
_hooks = _hooks + hooks


def clear_hooks() -> None:
global _hooks
_hooks = []


def get_hooks() -> list[Hook]:
return _hooks
5 changes: 1 addition & 4 deletions openfeature/provider/_registry.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import typing

from openfeature._event_support import run_handlers_for_provider
from openfeature.evaluation_context import EvaluationContext
from openfeature.evaluation_context import EvaluationContext, get_evaluation_context
from openfeature.event import (
ProviderEvent,
ProviderEventDetails,
Expand Down Expand Up @@ -65,9 +65,6 @@ def shutdown(self) -> None:
self._shutdown_provider(provider)

def _get_evaluation_context(self) -> EvaluationContext:
# imported here to avoid circular imports
from openfeature.api import get_evaluation_context

return get_evaluation_context()

def _initialize_provider(self, provider: FeatureProvider) -> None:
Expand Down
29 changes: 29 additions & 0 deletions openfeature/transaction_context/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
from openfeature.evaluation_context import EvaluationContext
from openfeature.transaction_context.context_var_transaction_context_propagator import (
ContextVarsTransactionContextPropagator,
)
from openfeature.transaction_context.no_op_transaction_context_propagator import (
NoOpTransactionContextPropagator,
)
from openfeature.transaction_context.transaction_context_propagator import (
TransactionContextPropagator,
)

__all__ = [
"ContextVarsTransactionContextPropagator",
"TransactionContextPropagator",
"get_transaction_context",
"set_transaction_context",
"set_transaction_context_propagator",
]

_evaluation_transaction_context_propagator: TransactionContextPropagator = (
NoOpTransactionContextPropagator()
)


def set_transaction_context_propagator(
transaction_context_propagator: TransactionContextPropagator,
) -> None:
global _evaluation_transaction_context_propagator
_evaluation_transaction_context_propagator = transaction_context_propagator


def get_transaction_context() -> EvaluationContext:
return _evaluation_transaction_context_propagator.get_transaction_context()


def set_transaction_context(evaluation_context: EvaluationContext) -> None:
global _evaluation_transaction_context_propagator
_evaluation_transaction_context_propagator.set_transaction_context(
evaluation_context
)