From 16f3b24fba1793e3e944264ffdabe17b15263aa3 Mon Sep 17 00:00:00 2001 From: Denis Navarro Date: Sun, 6 Jul 2025 10:48:30 +0200 Subject: [PATCH 1/2] chore(*): Remove Python 3.9 support and bump dev deps --- .editorconfig | 2 +- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- Containerfile | 8 +------ LICENSE | 2 +- README.md | 2 +- aiodi/builder.py | 28 +++++++++++------------ aiodi/container.py | 38 +++++++++++++++---------------- aiodi/resolver/__init__.py | 6 ++--- aiodi/resolver/loader.py | 20 ++++++++--------- aiodi/resolver/path.py | 18 +++++++-------- aiodi/resolver/service.py | 42 +++++++++++++++++------------------ aiodi/resolver/variable.py | 20 ++++++++--------- aiodi/toml.py | 6 ++--- compose.yml | 6 ++--- docs_src/config/en/mkdocs.yml | 2 +- docs_src/config/es/mkdocs.yml | 2 +- docs_src/docs/en/index.md | 2 +- docs_src/docs/es/index.md | 2 +- docs_src/mkdocs.yml | 2 +- pyproject.toml | 3 +-- 21 files changed, 102 insertions(+), 113 deletions(-) diff --git a/.editorconfig b/.editorconfig index 04dc65a..f9fa8b1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,5 +9,5 @@ insert_final_newline = true max_line_length = 120 trim_trailing_whitespace = true -[{*.py, run-script}] +[{*.py,run-script}] indent_size = 4 diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a4c079a..a347cd9 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.13.0-rc.2' + python-version: '3.13' - name: version run: sed -i "s/__version__ = '.*'/__version__ = '$VERSION'/g" aiodi/__init__.py - name: deps diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd3d304..6f4af02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13.0-rc.2' ] + python-version: [ '3.10', '3.11', '3.12', '3.13' ] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/Containerfile b/Containerfile index 7340777..b90c085 100644 --- a/Containerfile +++ b/Containerfile @@ -4,8 +4,7 @@ WORKDIR /app RUN conda install -y --download-only "python=3.12" && \ conda install -y --download-only "python=3.11" && \ - conda install -y --download-only "python=3.10" && \ - conda install -y --download-only "python=3.9" + conda install -y --download-only "python=3.10" COPY . ./ @@ -26,8 +25,3 @@ FROM miniconda3 AS py310 RUN conda install -y "python=3.10" RUN --mount=type=cache,target=/root/.cache/pip python3 run-script dev-install - -FROM miniconda3 AS py39 - -RUN conda install -y "python=3.9" -RUN --mount=type=cache,target=/root/.cache/pip python3 run-script dev-install diff --git a/LICENSE b/LICENSE index bfc7205..57334bb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 - 2024 aiopy +Copyright (c) 2022 - 2025 aiopy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 947fe95..d8576a5 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ if __name__ == '__main__': ## Requirements -- Python >= 3.9 +- Python >= 3.10 ## Contributing diff --git a/aiodi/builder.py b/aiodi/builder.py index 86f8562..27b6475 100644 --- a/aiodi/builder.py +++ b/aiodi/builder.py @@ -1,6 +1,6 @@ from pathlib import Path from random import shuffle -from typing import Any, Callable, Dict, List, MutableMapping, Optional, Tuple, Union +from typing import Any, Callable, MutableMapping from .container import Container from .logger import logger @@ -17,22 +17,22 @@ class ContainerBuilder: - _filenames: List[str] - _cwd: Optional[str] + _filenames: list[str] + _cwd: str | None _debug: bool - _resolvers: Dict[str, Resolver[Any, Any]] - _decoders: Dict[str, Callable[[Union[str, Path]], Union[MutableMapping[str, Any], Dict[str, Any]]]] - _map_items: Callable[[Dict[str, Dict[str, Any]]], List[Tuple[str, Any, Dict[str, Any]]]] + _resolvers: dict[str, Resolver[Any, Any]] + _decoders: dict[str, Callable[[str | Path], MutableMapping[str, Any] | dict[str, Any]]] + _map_items: Callable[[dict[str, dict[str, Any]]], list[tuple[str, Any, dict[str, Any]]]] def __init__( self, - filenames: Optional[List[str]] = None, - cwd: Optional[str] = None, + filenames: list[str] | None = None, + cwd: str | None = None, *, debug: bool = False, tool_key: str = 'aiodi', var_key: str = 'env', # Container retro-compatibility - toml_decoder: Optional[TOMLDecoder] = None, + toml_decoder: TOMLDecoder | None = None, ) -> None: self._filenames = ( [ @@ -58,7 +58,7 @@ def __init__( 'toml': lambda path: (toml_decoder or lazy_toml_decoder())(path).get('tool', {}).get(tool_key, {}), } - def map_items(items: Dict[str, Dict[str, Any]]) -> List[Tuple[str, Any, Dict[str, Any]]]: + def map_items(items: dict[str, dict[str, Any]]) -> list[tuple[str, Any, dict[str, Any]]]: return [ (key, val, {}) for key, val in { @@ -70,7 +70,7 @@ def map_items(items: Dict[str, Dict[str, Any]]) -> List[Tuple[str, Any, Dict[str self._map_items = map_items def load(self) -> Container: - extra: Dict[str, Any] = { + extra: dict[str, Any] = { 'path_data': {}, 'data': {}, '_service_defaults': ServiceDefaults(), @@ -125,9 +125,9 @@ def load(self) -> Container: def _parse_values( self, resolver: Resolver[Any, Any], - storage: Dict[str, Any], - extra: Dict[str, Any], - items: Dict[str, Any], + storage: dict[str, Any], + extra: dict[str, Any], + items: dict[str, Any], ) -> None: limit_retries = pow(len(items.keys()), 3) while len(items.keys()) > 0: diff --git a/aiodi/container.py b/aiodi/container.py index b8b3dcf..a0fb9fd 100644 --- a/aiodi/container.py +++ b/aiodi/container.py @@ -4,9 +4,7 @@ Any, Callable, Dict, - List, Optional, - Tuple, Type, TypeVar, Union, @@ -18,18 +16,18 @@ _T = TypeVar('_T') -ContainerKey = Union[str, Type[Any], object] +ContainerKey = str | Type[Any] | object class Container(Dict[Any, Any]): debug: bool = False - _parameter_resolvers: List[Callable[['Container'], Any]] = [] + _parameter_resolvers: list[Callable[['Container'], Any]] = [] def __init__( self, items: Optional[ Union[ - Dict[str, Any], List[Union[ContainerKey, Tuple[ContainerKey, _T, Dict[str, Any]]]] # hardcoded + dict[str, Any], list[Union[ContainerKey, tuple[ContainerKey, _T, dict[str, Any]]]] # hardcoded ] # magic ] = None, debug: bool = False, @@ -42,12 +40,12 @@ def __init__( super(Container, self).__init__({}) self.resolve(items) - def resolve_parameter(self, fn: Callable[['Container'], Any]) -> Tuple[int, Callable[['Container'], Any]]: + def resolve_parameter(self, fn: Callable[['Container'], Any]) -> tuple[int, Callable[['Container'], Any]]: self._parameter_resolvers.append(fn) return len(self._parameter_resolvers) - 1, fn - def resolve(self, items: List[Union[ContainerKey, Tuple[ContainerKey, _T, Dict[str, Any]]]]) -> None: - items_: List[Any] = list(map(self._sanitize_item_before_resolve, items)) + def resolve(self, items: list[Union[ContainerKey, tuple[ContainerKey, _T, dict[str, Any]]]]) -> None: + items_: list[Any] = list(map(self._sanitize_item_before_resolve, items)) while items_: for index, item in enumerate(items_): # Check if already exist @@ -96,7 +94,7 @@ def set(self, key: ContainerKey, val: _T = ...) -> None: # type: ignore here = here.setdefault(key, {}) here[keys[-1]] = val - def get(self, key: ContainerKey, typ: Optional[Type[_T]] = None, instance_of: bool = False) -> _T: # type: ignore + def get(self, key: ContainerKey, typ: Type[_T] | None = None, instance_of: bool = False) -> _T: # type: ignore """ e.g. 1 container = Container({'config': {'version': '0.1.0'}, 'app.libs.MyClass': '...'}) @@ -152,8 +150,8 @@ def __contains__(self, *o) -> bool: # type: ignore @staticmethod def _sanitize_item_before_resolve( - item: Union[ContainerKey, Tuple[ContainerKey, _T, Dict[str, Any]]] - ) -> Tuple[ContainerKey, _T, Dict[str, Any]]: + item: Union[ContainerKey, tuple[ContainerKey, _T, dict[str, Any]]] + ) -> tuple[ContainerKey, _T, dict[str, Any]]: if not isinstance(item, tuple): return item, item, {} # type: ignore length = len(item) @@ -163,15 +161,15 @@ def _sanitize_item_before_resolve( return item[0], item[1], {} if length >= 3: return item[:3] - raise ValueError('Tuple must be at least of one item') + raise ValueError('tuple must be at least of one item') def _resolve_or_postpone_item( self, - item: Tuple[ContainerKey, _T, Dict[str, Any]], - items: List[Tuple[ContainerKey, _T, Dict[str, Any]]], - ) -> Optional[Dict[str, Any]]: + item: tuple[ContainerKey, _T, dict[str, Any]], + items: list[tuple[ContainerKey, _T, dict[str, Any]]], + ) -> dict[str, Any] | None: parameters = signature(item[1]).parameters.items() # type: ignore - kwargs: Dict[str, Any] = {} + kwargs: dict[str, Any] = {} item[2].update(self._sanitize_item_parameters_before_resolve_or_postpone(parameters, item[2])) for parameter in parameters: name: str = parameter[0] @@ -208,7 +206,7 @@ def _resolve_or_postpone_item( return None @classmethod - def _get_instance_of(cls, items: Dict[str, Any], typ: Type[Any]) -> List[Any]: + def _get_instance_of(cls, items: dict[str, Any], typ: Type[Any]) -> list[Any]: instances = [] for _, val in items.items(): if isinstance(val, typ): @@ -224,7 +222,7 @@ def _resolve_or_postpone_item_parameter( self, name: str, typ: Type[Any], - item: Tuple[ContainerKey, _T, Dict[str, Any]], + item: tuple[ContainerKey, _T, dict[str, Any]], ) -> Any: if name not in item[2]: return None @@ -245,8 +243,8 @@ def _resolve_or_postpone_item_parameter( @staticmethod def _sanitize_item_parameters_before_resolve_or_postpone( - meta_params: AbstractSet[Any], params: Dict[str, Any] - ) -> Dict[str, Any]: + meta_params: AbstractSet[Any], params: dict[str, Any] + ) -> dict[str, Any]: for meta_param in meta_params: name: str = meta_param[0] typ: Type[Any] = meta_param[1].annotation diff --git a/aiodi/resolver/__init__.py b/aiodi/resolver/__init__.py index a7af2c2..10ce430 100644 --- a/aiodi/resolver/__init__.py +++ b/aiodi/resolver/__init__.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Any, Dict, Generic, NamedTuple, TypeVar +from typing import Any, Generic, NamedTuple, TypeVar Metadata = TypeVar('Metadata', bound=NamedTuple) Value = TypeVar('Value', bound=Any) @@ -43,7 +43,7 @@ def times(self) -> int: class Resolver(ABC, Generic[Metadata, Value]): @abstractmethod - def extract_metadata(self, data: Dict[str, Any], extra: Dict[str, Any]) -> Metadata: + def extract_metadata(self, data: dict[str, Any], extra: dict[str, Any]) -> Metadata: """ Extract metadata from data @@ -53,7 +53,7 @@ def extract_metadata(self, data: Dict[str, Any], extra: Dict[str, Any]) -> Metad """ @abstractmethod - def parse_value(self, metadata: Metadata, retries: int, extra: Dict[str, Any]) -> Value: + def parse_value(self, metadata: Metadata, retries: int, extra: dict[str, Any]) -> Value: """ Parse value from metadata diff --git a/aiodi/resolver/loader.py b/aiodi/resolver/loader.py index 5c1f586..6a50a2e 100644 --- a/aiodi/resolver/loader.py +++ b/aiodi/resolver/loader.py @@ -1,17 +1,17 @@ from pathlib import Path -from typing import Any, Callable, Dict, MutableMapping, NamedTuple, Tuple, Union +from typing import Any, Callable, MutableMapping, NamedTuple from . import Resolver from .path import PathData from .service import ServiceDefaults -InputData = Union[str, Path] -OutputData = Union[MutableMapping[str, Any], Dict[str, Any]] +InputData = str | Path +OutputData = MutableMapping[str, Any] | dict[str, Any] class LoaderMetadata(NamedTuple): path_data: PathData - decoders: Dict[str, Callable[[InputData], OutputData]] + decoders: dict[str, Callable[[InputData], OutputData]] def decode(self) -> OutputData: for filepath in self.path_data.filepaths: @@ -29,8 +29,8 @@ def decode(self) -> OutputData: class LoadData(NamedTuple): - variables: Dict[str, Any] - services: Dict[str, Any] + variables: dict[str, Any] + services: dict[str, Any] service_defaults: ServiceDefaults @classmethod @@ -62,7 +62,7 @@ def from_metadata(cls, metadata: LoaderMetadata, data: OutputData) -> 'LoadData' class LoaderResolver(Resolver[LoaderMetadata, LoadData]): - def extract_metadata(self, data: Dict[str, Any], extra: Dict[str, Any]) -> LoaderMetadata: # pylint: disable=W0613 + def extract_metadata(self, data: dict[str, Any], extra: dict[str, Any]) -> LoaderMetadata: # pylint: disable=W0613 return LoaderMetadata( path_data=data['path_data'], decoders=data['decoders'], @@ -72,14 +72,14 @@ def parse_value( self, metadata: LoaderMetadata, retries: int, # pylint: disable=W0613 - extra: Dict[str, Any], # pylint: disable=W0613 + extra: dict[str, Any], # pylint: disable=W0613 ) -> LoadData: return LoadData.from_metadata(metadata=metadata, data=metadata.decode()) def prepare_loader_to_parse( - resolver: Resolver[Any, Any], items: Dict[str, Any], extra: Dict[str, Any] # pylint: disable=W0613 -) -> Dict[str, Tuple[LoaderMetadata, int]]: + resolver: Resolver[Any, Any], items: dict[str, Any], extra: dict[str, Any] # pylint: disable=W0613 +) -> dict[str, tuple[LoaderMetadata, int]]: return { 'value': (resolver.extract_metadata(data=items, extra=extra), 0), } diff --git a/aiodi/resolver/path.py b/aiodi/resolver/path.py index c35014d..674da74 100644 --- a/aiodi/resolver/path.py +++ b/aiodi/resolver/path.py @@ -1,14 +1,14 @@ from os.path import abspath, dirname from pathlib import Path from sys import executable, modules -from typing import Any, Dict, List, NamedTuple, Optional, Tuple +from typing import Any, NamedTuple, Optional from . import Resolver class PathMetadata(NamedTuple): cwd: Optional[str] - filenames: List[str] + filenames: list[str] def compute_cwd(self) -> Path: if self.cwd: @@ -19,8 +19,8 @@ def compute_cwd(self) -> Path: main_file = executable return Path(dirname(main_file)) # type: ignore - def compute_filepaths(self, cwd: Path) -> List[Path]: - filepaths: List[Path] = [] + def compute_filepaths(self, cwd: Path) -> list[Path]: + filepaths: list[Path] = [] for filename in self.filenames: parts_to_remove = len(([part for part in Path(filename).parts if part == '..'])) filename_ = '/'.join( @@ -37,7 +37,7 @@ def compute_filepaths(self, cwd: Path) -> List[Path]: class PathData(NamedTuple): cwd: Path - filepaths: List[Path] + filepaths: list[Path] @classmethod def from_metadata(cls, metadata: PathMetadata) -> 'PathData': @@ -47,21 +47,21 @@ def from_metadata(cls, metadata: PathMetadata) -> 'PathData': class PathResolver(Resolver[PathMetadata, PathData]): - def extract_metadata(self, data: Dict[str, Any], extra: Dict[str, Any]) -> PathMetadata: # pylint: disable=W0613 + def extract_metadata(self, data: dict[str, Any], extra: dict[str, Any]) -> PathMetadata: # pylint: disable=W0613 return PathMetadata(cwd=data.get('cwd', None), filenames=data.get('filenames', [])) def parse_value( self, metadata: PathMetadata, retries: int, # pylint: disable=W0613 - extra: Dict[str, Any], # pylint: disable=W0613 + extra: dict[str, Any], # pylint: disable=W0613 ) -> PathData: return PathData.from_metadata(metadata) def prepare_path_to_parse( - resolver: Resolver[Any, Any], items: Dict[str, Any], extra: Dict[str, Any] # pylint: disable=W0613 -) -> Dict[str, Tuple[PathMetadata, int]]: + resolver: Resolver[Any, Any], items: dict[str, Any], extra: dict[str, Any] # pylint: disable=W0613 +) -> dict[str, tuple[PathMetadata, int]]: return { 'value': (resolver.extract_metadata(data=items, extra=extra), 0), } diff --git a/aiodi/resolver/service.py b/aiodi/resolver/service.py index 4fbdfea..ea932c4 100644 --- a/aiodi/resolver/service.py +++ b/aiodi/resolver/service.py @@ -2,7 +2,7 @@ from glob import glob from inspect import Parameter, signature from pathlib import Path -from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Type, cast +from typing import Any, NamedTuple, Optional, Type, cast from ..helpers import ( import_module_and_get_attr, @@ -21,7 +21,7 @@ class ServiceDefaults(NamedTuple): project_dir: str = '' autowire: bool = True autoconfigure: bool = True - autoregistration: Dict[str, Optional[str]] = { + autoregistration: dict[str, Optional[str]] = { 'resource': None, 'exclude': None, } @@ -37,7 +37,7 @@ def has_resources(self) -> bool: return False return True - def compute_resources(self) -> List[str]: + def compute_resources(self) -> list[str]: resources = [self.resource()] if not resources[0]: return [] @@ -51,7 +51,7 @@ def compute_resources(self) -> List[str]: return resources - def compute_excludes(self) -> List[str]: + def compute_excludes(self) -> list[str]: raw_exclude: str = self.exclude() if not raw_exclude: @@ -66,16 +66,16 @@ def compute_excludes(self) -> List[str]: left = self.project_dir if exclude_groups[0] is None else self.project_dir + '/' + exclude_groups[0] left = '/'.join(list(Path(str(Path(left).absolute()).replace('../', '')).parts[-len(Path(left).parts) :]))[1:] - rights: List[str] = [] + rights: list[str] = [] for right in ('{}' if exclude_groups[1] is None else exclude_groups[1])[1:-1].split(','): rights += glob(left + '/' + right) return [left] if len(rights) == 0 else rights def compute_services( - self, resolver: Resolver[Any, Any], resources: List[str], excludes: List[str], extra: Dict[str, Any] - ) -> Dict[str, Tuple['ServiceMetadata', int]]: - names: List[str] = [] + self, resolver: Resolver[Any, Any], resources: list[str], excludes: list[str], extra: dict[str, Any] + ) -> dict[str, tuple['ServiceMetadata', int]]: + names: list[str] = [] for include in resources: names += [ name @@ -83,7 +83,7 @@ def compute_services( if hasattr(mod, '__mro__') and not mod.__mro__[1:][0] is ABC # avoid loading interfaces ] - services: Dict[str, Tuple['ServiceMetadata', int]] = {} + services: dict[str, tuple['ServiceMetadata', int]] = {} for name in set(names): services[name] = ( resolver.extract_metadata( @@ -135,8 +135,8 @@ class ServiceMetadata(NamedTuple): name: str type: Type[Any] clazz: Type[Any] - arguments: Dict[str, Any] - params: List[Any] + arguments: dict[str, Any] + params: list[Any] defaults: ServiceDefaults class ParameterMetadata(NamedTuple): # type: ignore @@ -147,7 +147,7 @@ class ParameterMetadata(NamedTuple): # type: ignore @classmethod def from_param_inspected_and_args( - cls, param: Tuple[str, Parameter], arguments: Dict[str, Any] + cls, param: tuple[str, Parameter], arguments: dict[str, Any] ) -> 'ServiceMetadata.ParameterMetadata': return cls( name=str(param[0]), @@ -184,7 +184,7 @@ class ServiceResolutionPostponed(ValueResolutionPostponed[ServiceMetadata]): class ServiceResolver(Resolver[ServiceMetadata, Any]): @staticmethod - def _define_service_type(name: str, typ: str, cls: str) -> Tuple[Type[Any], Type[Any]]: + def _define_service_type(name: str, typ: str, cls: str) -> tuple[Type[Any], Type[Any]]: if typ is _SVC_DEFAULTS and cls is _SVC_DEFAULTS: # type: ignore cls = typ = import_module_and_get_attr(name=name) return typ, cls @@ -207,7 +207,7 @@ def _define_service_type(name: str, typ: str, cls: str) -> Tuple[Type[Any], Type return typ, cls # type: ignore - def extract_metadata(self, data: Dict[str, Any], extra: Dict[str, Any]) -> ServiceMetadata: # pylint: disable=W0613 + def extract_metadata(self, data: dict[str, Any], extra: dict[str, Any]) -> ServiceMetadata: # pylint: disable=W0613 key = cast(str, data.get('key')) val = data.get('val') defaults = cast(ServiceDefaults, data.get('defaults')) @@ -230,12 +230,12 @@ def extract_metadata(self, data: Dict[str, Any], extra: Dict[str, Any]) -> Servi defaults=defaults, ) - def parse_value(self, metadata: ServiceMetadata, retries: int, extra: Dict[str, Any]) -> Any: - _variables = cast(Dict[str, Any], extra.get('variables')) - _services = cast(Dict[str, Any], extra.get('services')) + def parse_value(self, metadata: ServiceMetadata, retries: int, extra: dict[str, Any]) -> Any: + _variables = cast(dict[str, Any], extra.get('variables')) + _services = cast(dict[str, Any], extra.get('services')) variable_resolver = cast(Resolver[Any, Any], extra.get('resolvers', {}).get('variable')) - parameters: Dict[str, Any] = {} + parameters: dict[str, Any] = {} for param in metadata.params: param_val = param.default # extract raw value @@ -276,11 +276,11 @@ def parse_value(self, metadata: ServiceMetadata, retries: int, extra: Dict[str, def prepare_services_to_parse( - resolver: Resolver[Any, Any], items: Dict[str, Any], extra: Dict[str, Any] -) -> Dict[str, Tuple['ServiceMetadata', int]]: + resolver: Resolver[Any, Any], items: dict[str, Any], extra: dict[str, Any] +) -> dict[str, tuple['ServiceMetadata', int]]: _service_defaults = cast(ServiceDefaults, extra.get('_service_defaults')) - services: Dict[str, Tuple['ServiceMetadata', int]] = {} + services: dict[str, tuple['ServiceMetadata', int]] = {} for key, val in items.items(): defaults = ServiceDefaults.from_value(val=val, defaults=_service_defaults) if defaults.has_resources(): diff --git a/aiodi/resolver/variable.py b/aiodi/resolver/variable.py index 848f670..61de72b 100644 --- a/aiodi/resolver/variable.py +++ b/aiodi/resolver/variable.py @@ -1,5 +1,5 @@ from os import getenv -from typing import Any, Dict, List, Match, NamedTuple, Tuple, Type +from typing import Any, Match, NamedTuple, Type from ..helpers import raise_, re_finditer from . import Resolver, ValueNotFound, ValueResolutionPostponed @@ -12,11 +12,11 @@ class VariableMetadata(NamedTuple): name: str value: Any - matches: List['VariableMetadata.MatchMetadata'] # type: ignore + matches: list['VariableMetadata.MatchMetadata'] # type: ignore class MatchMetadata(NamedTuple): # type: ignore source_kind: str - types: List[Type[Any]] + types: list[Type[Any]] source_name: str default: Any match: Match[Any] @@ -54,14 +54,14 @@ class VariableResolutionPostponed(ValueResolutionPostponed[VariableMetadata]): class VariableResolver(Resolver[VariableMetadata, Any]): @staticmethod - def _metadata_matches(key: str, val: Any) -> List[Match[Any]]: - def __call__(string: Any) -> List[Match[Any]]: + def _metadata_matches(key: str, val: Any) -> list[Match[Any]]: + def __call__(string: Any) -> list[Match[Any]]: return re_finditer(pattern=REGEX, string=string) return __call__(string=val) or __call__(string=STATIC_TEMPLATE.format(type(val).__name__, key, val)) def extract_metadata( - self, data: Dict[str, Any], extra: Dict[str, Any] # pylint: disable=W0613 + self, data: dict[str, Any], extra: dict[str, Any] # pylint: disable=W0613 ) -> VariableMetadata: key: str = data.get('key') or raise_(KeyError('Missing key "key" to extract variable metadata')) # type: ignore val: Any = data.get('val') or raise_(KeyError('Missing key "val" to extract variable metadata')) @@ -76,14 +76,14 @@ def extract_metadata( ) def parse_value( - self, metadata: VariableMetadata, retries: int, extra: Dict[str, Any] # pylint: disable=W0613 + self, metadata: VariableMetadata, retries: int, extra: dict[str, Any] # pylint: disable=W0613 ) -> Any: extra = {} if extra is None or not isinstance(extra, dict) else extra _variables: Dict[str, Any] = extra.get('variables') # type: ignore if _variables is None: raise KeyError('Missing key "variables" to parse variable value') - values: List[str] = [] + values: list[str] = [] for idx, metadata_ in enumerate(metadata.matches): typ_val: str = '' if metadata_.source_kind == 'static': @@ -118,8 +118,8 @@ def parse_value( def prepare_variables_to_parse( - resolver: Resolver[Any, Any], items: Dict[str, Any], extra: Dict[str, Any] # pylint: disable=W0613 -) -> Dict[str, Tuple[VariableMetadata, int]]: + resolver: Resolver[Any, Any], items: dict[str, Any], extra: dict[str, Any] # pylint: disable=W0613 +) -> dict[str, tuple[VariableMetadata, int]]: return dict( [ (key, (resolver.extract_metadata(data={'key': key, 'val': val}, extra=extra), 0)) diff --git a/aiodi/toml.py b/aiodi/toml.py index 0fba4e6..6a7539f 100644 --- a/aiodi/toml.py +++ b/aiodi/toml.py @@ -1,8 +1,8 @@ from pathlib import Path -from typing import Any, Callable, Dict, MutableMapping, Union, cast +from typing import Any, Callable, MutableMapping, cast -TOMLDecoded = Union[MutableMapping[str, Any], Dict[str, Any]] -TOMLPath = Union[str, Path] +TOMLDecoded = MutableMapping[str, Any] | dict[str, Any] +TOMLPath = str | Path TOMLDecoder = Callable[[TOMLPath], TOMLDecoded] diff --git a/compose.yml b/compose.yml index 2d24486..1280382 100644 --- a/compose.yml +++ b/compose.yml @@ -3,13 +3,11 @@ services: build: context: . dockerfile: Containerfile - target: py39 # target: py310 # target: py311 -# target: py312 - image: ghcr.io/aiopy/python-aiodi:py39-${VERSION:-latest} + target: py312 # image: ghcr.io/aiopy/python-aiodi:py310-${VERSION:-latest} # image: ghcr.io/aiopy/python-aiodi:py311-${VERSION:-latest} -# image: ghcr.io/aiopy/python-aiodi:py312-${VERSION:-latest} + image: ghcr.io/aiopy/python-aiodi:py312-${VERSION:-latest} volumes: - .:/app diff --git a/docs_src/config/en/mkdocs.yml b/docs_src/config/en/mkdocs.yml index ff4739f..93f7af5 100644 --- a/docs_src/config/en/mkdocs.yml +++ b/docs_src/config/en/mkdocs.yml @@ -1,4 +1,4 @@ -copyright: 'Copyright © 2022' +copyright: 'Copyright © 2022 - 2025' docs_dir: '../../docs/en' diff --git a/docs_src/config/es/mkdocs.yml b/docs_src/config/es/mkdocs.yml index 33fab56..dfc561f 100644 --- a/docs_src/config/es/mkdocs.yml +++ b/docs_src/config/es/mkdocs.yml @@ -1,4 +1,4 @@ -copyright: 'Copyright © 2022' +copyright: 'Copyright © 2022 - 2025' docs_dir: '../../docs/es' diff --git a/docs_src/docs/en/index.md b/docs_src/docs/en/index.md index 2fc4e1f..1971820 100644 --- a/docs_src/docs/en/index.md +++ b/docs_src/docs/en/index.md @@ -10,7 +10,7 @@ Key Features: ## Requirements -- Python 3.9+ +- Python 3.10+ ## Installation diff --git a/docs_src/docs/es/index.md b/docs_src/docs/es/index.md index 5a0f0d2..b39d223 100644 --- a/docs_src/docs/es/index.md +++ b/docs_src/docs/es/index.md @@ -10,7 +10,7 @@ Funcionalidades clave: ## Requisitos -- Python 3.9+ +- Python 3.10+ ## Instalación diff --git a/docs_src/mkdocs.yml b/docs_src/mkdocs.yml index 8188a48..536cab5 100644 --- a/docs_src/mkdocs.yml +++ b/docs_src/mkdocs.yml @@ -31,7 +31,7 @@ extra: - link: /es/ name: es - EspaƱol generator: false -copyright: Copyright © 2022 +copyright: Copyright © 2022 - 2025 markdown_extensions: - pymdownx.highlight - pymdownx.superfences diff --git a/pyproject.toml b/pyproject.toml index 967a58e..9fd0924 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,6 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -38,7 +37,7 @@ keywords = ["di", "dependency", "container", "aio", "async"] license = { text = "MIT" } name = "aiodi" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" [project.optional-dependencies] dev = [ From 8e96f5032bf4c165ca23ef2fc5426de4b8dd7bf8 Mon Sep 17 00:00:00 2001 From: Denis Navarro Date: Sun, 6 Jul 2025 11:00:19 +0200 Subject: [PATCH 2/2] chore(*): Remove Python 3.9 support and bump dev deps --- aiodi/resolver/service.py | 10 +++++----- aiodi/resolver/variable.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aiodi/resolver/service.py b/aiodi/resolver/service.py index ea932c4..501f3f7 100644 --- a/aiodi/resolver/service.py +++ b/aiodi/resolver/service.py @@ -2,7 +2,7 @@ from glob import glob from inspect import Parameter, signature from pathlib import Path -from typing import Any, NamedTuple, Optional, Type, cast +from typing import Any, NamedTuple, Type, cast from ..helpers import ( import_module_and_get_attr, @@ -21,7 +21,7 @@ class ServiceDefaults(NamedTuple): project_dir: str = '' autowire: bool = True autoconfigure: bool = True - autoregistration: dict[str, Optional[str]] = { + autoregistration: dict[str, str | None] = { 'resource': None, 'exclude': None, } @@ -108,7 +108,7 @@ def compute_services( return services @classmethod - def from_value(cls, val: Any, defaults: Optional['ServiceDefaults'] = None) -> 'ServiceDefaults': + def from_value(cls, val: Any, defaults: Any = None) -> 'ServiceDefaults': if not defaults: defaults = cls() has_defaults = isinstance(val, dict) and '_defaults' in val @@ -214,8 +214,8 @@ def extract_metadata(self, data: dict[str, Any], extra: dict[str, Any]) -> Servi typ, clazz = self._define_service_type( name=key, - typ=val['type'] if isinstance(val, dict) and 'type' in val else _SVC_DEFAULTS, - cls=val['class'] if isinstance(val, dict) and 'class' in val else _SVC_DEFAULTS, + typ=val['type'] if isinstance(val, dict) and 'type' in val else _SVC_DEFAULTS, # type: ignore + cls=val['class'] if isinstance(val, dict) and 'class' in val else _SVC_DEFAULTS, # type: ignore ) kwargs = val['arguments'] if isinstance(val, dict) and 'arguments' in val else {} return ServiceMetadata( diff --git a/aiodi/resolver/variable.py b/aiodi/resolver/variable.py index 61de72b..fc398bb 100644 --- a/aiodi/resolver/variable.py +++ b/aiodi/resolver/variable.py @@ -79,7 +79,7 @@ def parse_value( self, metadata: VariableMetadata, retries: int, extra: dict[str, Any] # pylint: disable=W0613 ) -> Any: extra = {} if extra is None or not isinstance(extra, dict) else extra - _variables: Dict[str, Any] = extra.get('variables') # type: ignore + _variables: dict[str, Any] = extra.get('variables') # type: ignore if _variables is None: raise KeyError('Missing key "variables" to parse variable value')