diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8b7a61f5..7023c114 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -44,7 +44,7 @@ jobs:
- name: Run E2E tests with behave
run: hatch run e2e
- - if: matrix.python-version == '3.11'
+ - if: matrix.python-version == '3.13'
name: Upload coverage to Codecov
uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
with:
@@ -61,7 +61,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
with:
- python-version: "3.11"
+ python-version: "3.13"
cache: "pip"
- name: Run pre-commit
@@ -77,7 +77,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
with:
- python-version: "3.11"
+ python-version: "3.13"
- name: Initialize CodeQL
uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5a19fbad..197d17b5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,4 +1,4 @@
-default_stages: [commit]
+default_stages: [pre-commit]
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.6
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c5c99761..2660e01f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,11 +4,11 @@
### System Requirements
-Python 3.8 and above are required.
+Python 3.9 and above are required.
### Target version(s)
-Python 3.8 and above are supported by the SDK.
+Python 3.9 and above are supported by the SDK.
### Installation and Dependencies
diff --git a/README.md b/README.md
index 190cda23..48ad8740 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@
-
+
@@ -51,7 +51,7 @@
### Requirements
-- Python 3.8+
+- Python 3.9+
### Install
diff --git a/openfeature/_event_support.py b/openfeature/_event_support.py
index 42e6250b..58c72d4e 100644
--- a/openfeature/_event_support.py
+++ b/openfeature/_event_support.py
@@ -2,7 +2,7 @@
import threading
from collections import defaultdict
-from typing import TYPE_CHECKING, Dict, List
+from typing import TYPE_CHECKING
from openfeature.event import (
EventDetails,
@@ -17,10 +17,10 @@
_global_lock = threading.RLock()
-_global_handlers: Dict[ProviderEvent, List[EventHandler]] = defaultdict(list)
+_global_handlers: dict[ProviderEvent, list[EventHandler]] = defaultdict(list)
_client_lock = threading.RLock()
-_client_handlers: Dict[OpenFeatureClient, Dict[ProviderEvent, List[EventHandler]]] = (
+_client_handlers: dict[OpenFeatureClient, dict[ProviderEvent, list[EventHandler]]] = (
defaultdict(lambda: defaultdict(list))
)
diff --git a/openfeature/api.py b/openfeature/api.py
index 4460b695..36432d0c 100644
--- a/openfeature/api.py
+++ b/openfeature/api.py
@@ -40,7 +40,7 @@
NoOpTransactionContextPropagator()
)
-_hooks: typing.List[Hook] = []
+_hooks: list[Hook] = []
def get_client(
@@ -96,7 +96,7 @@ def set_transaction_context(evaluation_context: EvaluationContext) -> None:
)
-def add_hooks(hooks: typing.List[Hook]) -> None:
+def add_hooks(hooks: list[Hook]) -> None:
global _hooks
_hooks = _hooks + hooks
@@ -106,7 +106,7 @@ def clear_hooks() -> None:
_hooks = []
-def get_hooks() -> typing.List[Hook]:
+def get_hooks() -> list[Hook]:
return _hooks
diff --git a/openfeature/client.py b/openfeature/client.py
index 326359eb..7d1f26df 100644
--- a/openfeature/client.py
+++ b/openfeature/client.py
@@ -77,14 +77,14 @@
typing.Awaitable[FlagResolutionDetails[typing.Union[dict, list]]],
],
]
-TypeMap = typing.Dict[
+TypeMap = dict[
FlagType,
typing.Union[
- typing.Type[bool],
- typing.Type[int],
- typing.Type[float],
- typing.Type[str],
- typing.Tuple[typing.Type[dict], typing.Type[list]],
+ type[bool],
+ type[int],
+ type[float],
+ type[str],
+ tuple[type[dict], type[list]],
],
]
@@ -101,7 +101,7 @@ def __init__(
domain: typing.Optional[str],
version: typing.Optional[str],
context: typing.Optional[EvaluationContext] = None,
- hooks: typing.Optional[typing.List[Hook]] = None,
+ hooks: typing.Optional[list[Hook]] = None,
) -> None:
self.domain = domain
self.version = version
@@ -118,7 +118,7 @@ def get_provider_status(self) -> ProviderStatus:
def get_metadata(self) -> ClientMetadata:
return ClientMetadata(domain=self.domain)
- def add_hooks(self, hooks: typing.List[Hook]) -> None:
+ def add_hooks(self, hooks: list[Hook]) -> None:
self.hooks = self.hooks + hooks
def get_boolean_value(
@@ -423,12 +423,12 @@ def _establish_hooks_and_provider(
default_value: typing.Any,
evaluation_context: typing.Optional[EvaluationContext],
flag_evaluation_options: typing.Optional[FlagEvaluationOptions],
- ) -> typing.Tuple[
+ ) -> tuple[
FeatureProvider,
HookContext,
HookHints,
- typing.List[Hook],
- typing.List[Hook],
+ list[Hook],
+ list[Hook],
]:
if evaluation_context is None:
evaluation_context = EvaluationContext()
@@ -477,7 +477,7 @@ def _before_hooks_and_merge_context(
self,
flag_type: FlagType,
hook_context: HookContext,
- merged_hooks: typing.List[Hook],
+ merged_hooks: list[Hook],
hook_hints: HookHints,
evaluation_context: typing.Optional[EvaluationContext],
) -> EvaluationContext:
diff --git a/openfeature/event.py b/openfeature/event.py
index 23210771..ec8d3ce6 100644
--- a/openfeature/event.py
+++ b/openfeature/event.py
@@ -2,7 +2,7 @@
from dataclasses import dataclass, field
from enum import Enum
-from typing import Callable, Dict, List, Optional, Union
+from typing import Callable, Optional, Union
from openfeature.exception import ErrorCode
@@ -18,19 +18,19 @@ class ProviderEvent(Enum):
@dataclass
class ProviderEventDetails:
- flags_changed: Optional[List[str]] = None
+ flags_changed: Optional[list[str]] = None
message: Optional[str] = None
error_code: Optional[ErrorCode] = None
- metadata: Dict[str, Union[bool, str, int, float]] = field(default_factory=dict)
+ metadata: dict[str, Union[bool, str, int, float]] = field(default_factory=dict)
@dataclass
class EventDetails(ProviderEventDetails):
provider_name: str = ""
- flags_changed: Optional[List[str]] = None
+ flags_changed: Optional[list[str]] = None
message: Optional[str] = None
error_code: Optional[ErrorCode] = None
- metadata: Dict[str, Union[bool, str, int, float]] = field(default_factory=dict)
+ metadata: dict[str, Union[bool, str, int, float]] = field(default_factory=dict)
@classmethod
def from_provider_event_details(
diff --git a/openfeature/flag_evaluation.py b/openfeature/flag_evaluation.py
index 1f83fd1b..5cab623c 100644
--- a/openfeature/flag_evaluation.py
+++ b/openfeature/flag_evaluation.py
@@ -58,7 +58,7 @@ class FlagEvaluationDetails(typing.Generic[T_co]):
@dataclass
class FlagEvaluationOptions:
- hooks: typing.List[Hook] = field(default_factory=list)
+ hooks: list[Hook] = field(default_factory=list)
hook_hints: HookHints = field(default_factory=dict)
diff --git a/openfeature/hook/__init__.py b/openfeature/hook/__init__.py
index 03d8c865..16fa4bdd 100644
--- a/openfeature/hook/__init__.py
+++ b/openfeature/hook/__init__.py
@@ -60,8 +60,8 @@ def __setattr__(self, key: str, value: typing.Any) -> None:
float,
str,
datetime,
- typing.List[typing.Any],
- typing.Dict[str, typing.Any],
+ list[typing.Any],
+ dict[str, typing.Any],
],
]
diff --git a/openfeature/hook/_hook_support.py b/openfeature/hook/_hook_support.py
index 2c151ae1..37b7e5b4 100644
--- a/openfeature/hook/_hook_support.py
+++ b/openfeature/hook/_hook_support.py
@@ -13,7 +13,7 @@ def error_hooks(
flag_type: FlagType,
hook_context: HookContext,
exception: Exception,
- hooks: typing.List[Hook],
+ hooks: list[Hook],
hints: typing.Optional[HookHints] = None,
) -> None:
kwargs = {"hook_context": hook_context, "exception": exception, "hints": hints}
@@ -26,7 +26,7 @@ def after_all_hooks(
flag_type: FlagType,
hook_context: HookContext,
details: FlagEvaluationDetails[typing.Any],
- hooks: typing.List[Hook],
+ hooks: list[Hook],
hints: typing.Optional[HookHints] = None,
) -> None:
kwargs = {"hook_context": hook_context, "details": details, "hints": hints}
@@ -39,7 +39,7 @@ def after_hooks(
flag_type: FlagType,
hook_context: HookContext,
details: FlagEvaluationDetails[typing.Any],
- hooks: typing.List[Hook],
+ hooks: list[Hook],
hints: typing.Optional[HookHints] = None,
) -> None:
kwargs = {"hook_context": hook_context, "details": details, "hints": hints}
@@ -51,7 +51,7 @@ def after_hooks(
def before_hooks(
flag_type: FlagType,
hook_context: HookContext,
- hooks: typing.List[Hook],
+ hooks: list[Hook],
hints: typing.Optional[HookHints] = None,
) -> EvaluationContext:
kwargs = {"hook_context": hook_context, "hints": hints}
@@ -68,7 +68,7 @@ def before_hooks(
def _execute_hooks(
flag_type: FlagType,
- hooks: typing.List[Hook],
+ hooks: list[Hook],
hook_method: HookType,
**kwargs: typing.Any,
) -> list:
@@ -91,10 +91,10 @@ def _execute_hooks(
def _execute_hooks_unchecked(
flag_type: FlagType,
- hooks: typing.List[Hook],
+ hooks: list[Hook],
hook_method: HookType,
**kwargs: typing.Any,
-) -> typing.List[typing.Optional[EvaluationContext]]:
+) -> list[typing.Optional[EvaluationContext]]:
"""
Execute a single hook without checking whether an exception is thrown. This is
used in the before and after hooks since any exception will be caught in the
diff --git a/openfeature/provider/__init__.py b/openfeature/provider/__init__.py
index 6a782635..5b9ffd09 100644
--- a/openfeature/provider/__init__.py
+++ b/openfeature/provider/__init__.py
@@ -38,7 +38,7 @@ def shutdown(self) -> None: ...
def get_metadata(self) -> Metadata: ...
- def get_provider_hooks(self) -> typing.List[Hook]: ...
+ def get_provider_hooks(self) -> list[Hook]: ...
def resolve_boolean_details(
self,
@@ -134,7 +134,7 @@ def shutdown(self) -> None:
def get_metadata(self) -> Metadata:
pass
- def get_provider_hooks(self) -> typing.List[Hook]:
+ def get_provider_hooks(self) -> list[Hook]:
return []
@abstractmethod
diff --git a/openfeature/provider/_registry.py b/openfeature/provider/_registry.py
index e2ec2e53..78412f1e 100644
--- a/openfeature/provider/_registry.py
+++ b/openfeature/provider/_registry.py
@@ -13,8 +13,8 @@
class ProviderRegistry:
_default_provider: FeatureProvider
- _providers: typing.Dict[str, FeatureProvider]
- _provider_status: typing.Dict[FeatureProvider, ProviderStatus]
+ _providers: dict[str, FeatureProvider]
+ _provider_status: dict[FeatureProvider, ProviderStatus]
def __init__(self) -> None:
self._default_provider = NoOpProvider()
diff --git a/openfeature/provider/in_memory_provider.py b/openfeature/provider/in_memory_provider.py
index d64a7735..3bd3fa1b 100644
--- a/openfeature/provider/in_memory_provider.py
+++ b/openfeature/provider/in_memory_provider.py
@@ -26,7 +26,7 @@ class State(StrEnum):
DISABLED = "DISABLED"
default_variant: str
- variants: typing.Dict[str, T_co]
+ variants: dict[str, T_co]
flag_metadata: FlagMetadata = field(default_factory=dict)
state: State = State.ENABLED
context_evaluator: typing.Optional[
@@ -51,7 +51,7 @@ def resolve(
)
-FlagStorage = typing.Dict[str, InMemoryFlag[typing.Any]]
+FlagStorage = dict[str, InMemoryFlag[typing.Any]]
V = typing.TypeVar("V")
@@ -65,7 +65,7 @@ def __init__(self, flags: FlagStorage) -> None:
def get_metadata(self) -> Metadata:
return InMemoryMetadata()
- def get_provider_hooks(self) -> typing.List[Hook]:
+ def get_provider_hooks(self) -> list[Hook]:
return []
def resolve_boolean_details(
diff --git a/openfeature/provider/no_op_provider.py b/openfeature/provider/no_op_provider.py
index 070945c9..68de7167 100644
--- a/openfeature/provider/no_op_provider.py
+++ b/openfeature/provider/no_op_provider.py
@@ -13,7 +13,7 @@ class NoOpProvider(AbstractProvider):
def get_metadata(self) -> Metadata:
return NoOpMetadata()
- def get_provider_hooks(self) -> typing.List[Hook]:
+ def get_provider_hooks(self) -> list[Hook]:
return []
def resolve_boolean_details(
diff --git a/pyproject.toml b/pyproject.toml
index 03180743..73a40424 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,7 +22,7 @@ keywords = [
"toggles",
]
dependencies = []
-requires-python = ">=3.8"
+requires-python = ">=3.9"
[project.urls]
Homepage = "https://github.com/open-feature/python-sdk"
@@ -66,6 +66,8 @@ packages = ["openfeature"]
[tool.mypy]
files = "openfeature"
+
+python_version = "3.9" # should be identical to the minimum supported version
namespace_packages = true
explicit_package_bases = true
local_partial_types = true # will become the new default from version 2
@@ -73,6 +75,9 @@ pretty = true
strict = true
disallow_any_generics = false
+[tool.pytest.ini_options]
+asyncio_default_fixture_loop_scope = "function"
+
[tool.ruff]
exclude = [
".git",
@@ -80,7 +85,7 @@ exclude = [
"__pycache__",
"venv",
]
-target-version = "py38"
+target-version = "py39"
[tool.ruff.lint]
select = [