From bcd1a3807e635dcd80a7894859ae14d54a3dc485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Gr=C3=BCbel?= Date: Tue, 11 Feb 2025 22:17:37 +0100 Subject: [PATCH] chore!: drop Python 3.8 support (#441) * drop Python 3.8 support Signed-off-by: gruebel * pin mypy python version to 3.9 Signed-off-by: gruebel --------- Signed-off-by: gruebel Co-authored-by: Michael Beemer --- .github/workflows/build.yml | 8 ++++---- .pre-commit-config.yaml | 2 +- CONTRIBUTING.md | 4 ++-- README.md | 4 ++-- openfeature/_event_support.py | 6 +++--- openfeature/api.py | 6 +++--- openfeature/client.py | 24 +++++++++++----------- openfeature/event.py | 10 ++++----- openfeature/flag_evaluation.py | 2 +- openfeature/hook/__init__.py | 4 ++-- openfeature/hook/_hook_support.py | 14 ++++++------- openfeature/provider/__init__.py | 4 ++-- openfeature/provider/_registry.py | 4 ++-- openfeature/provider/in_memory_provider.py | 6 +++--- openfeature/provider/no_op_provider.py | 2 +- pyproject.toml | 9 ++++++-- 16 files changed, 57 insertions(+), 52 deletions(-) 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 @@ - Min python version + Min python version @@ -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 = [