chore!: drop Python 3.8 support (#441)

* drop Python 3.8 support

Signed-off-by: gruebel <anton.gruebel@gmail.com>

* pin mypy python version to 3.9

Signed-off-by: gruebel <anton.gruebel@gmail.com>

---------

Signed-off-by: gruebel <anton.gruebel@gmail.com>
Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
This commit is contained in:
Anton Grübel 2025-02-11 22:17:37 +01:00 committed by GitHub
parent d4f53b4de2
commit bcd1a3807e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 57 additions and 52 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -34,7 +34,7 @@
</a>
<a href="https://www.python.org/downloads/">
<img alt="Min python version" src="https://img.shields.io/badge/python->=3.8-blue.svg" />
<img alt="Min python version" src="https://img.shields.io/badge/python->=3.9-blue.svg" />
</a>
<a href="https://www.repostatus.org/#wip">
@ -51,7 +51,7 @@
### Requirements
- Python 3.8+
- Python 3.9+
### Install

View File

@ -2,7 +2,7 @@ from __future__ import annotations
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 @@ if TYPE_CHECKING:
_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))
)

View File

@ -40,7 +40,7 @@ _evaluation_transaction_context_propagator: TransactionContextPropagator = (
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

View File

@ -77,14 +77,14 @@ GetDetailCallableAsync = typing.Union[
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 @@ class OpenFeatureClient:
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 @@ class OpenFeatureClient:
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 @@ class OpenFeatureClient:
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 @@ class OpenFeatureClient:
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:

View File

@ -2,7 +2,7 @@ from __future__ import annotations
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(

View File

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

View File

@ -60,8 +60,8 @@ HookHints = typing.Mapping[
float,
str,
datetime,
typing.List[typing.Any],
typing.Dict[str, typing.Any],
list[typing.Any],
dict[str, typing.Any],
],
]

View File

@ -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

View File

@ -38,7 +38,7 @@ class FeatureProvider(typing.Protocol): # pragma: no cover
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 @@ class AbstractProvider(FeatureProvider):
def get_metadata(self) -> Metadata:
pass
def get_provider_hooks(self) -> typing.List[Hook]:
def get_provider_hooks(self) -> list[Hook]:
return []
@abstractmethod

View File

@ -13,8 +13,8 @@ from openfeature.provider.no_op_provider import NoOpProvider
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()

View File

@ -26,7 +26,7 @@ class InMemoryFlag(typing.Generic[T_co]):
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 @@ class InMemoryFlag(typing.Generic[T_co]):
)
FlagStorage = typing.Dict[str, InMemoryFlag[typing.Any]]
FlagStorage = dict[str, InMemoryFlag[typing.Any]]
V = typing.TypeVar("V")
@ -65,7 +65,7 @@ class InMemoryProvider(AbstractProvider):
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(

View File

@ -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(

View File

@ -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 = [