Skip to content
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
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 1 addition & 7 deletions Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 . ./

Expand All @@ -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
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ if __name__ == '__main__':

## Requirements

- Python >= 3.9
- Python >= 3.10

## Contributing

Expand Down
28 changes: 14 additions & 14 deletions aiodi/builder.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 = (
[
Expand All @@ -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 {
Expand All @@ -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(),
Expand Down Expand Up @@ -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:
Expand Down
38 changes: 18 additions & 20 deletions aiodi/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
Any,
Callable,
Dict,
List,
Optional,
Tuple,
Type,
TypeVar,
Union,
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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': '...'})
Expand Down Expand Up @@ -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)
Expand All @@ -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]
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions aiodi/resolver/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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

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

Expand Down
20 changes: 10 additions & 10 deletions aiodi/resolver/loader.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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'],
Expand All @@ -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),
}
18 changes: 9 additions & 9 deletions aiodi/resolver/path.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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(
Expand All @@ -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':
Expand All @@ -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),
}
Loading