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: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pip install aiodi
name = "%env(str:APP_NAME, 'sample')%"
version = "%env(int:APP_VERSION, '1')%"
log_level = "%env(APP_LEVEL, 'INFO')%"
debug = "%env(bool:int:APP_DEBUG, '0')%"
text = "Hello World"

[tool.aiodi.services."_defaults"]
project_dir = "../../.."
Expand Down
2 changes: 1 addition & 1 deletion aiodi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .builder import ContainerBuilder
from .container import Container, ContainerKey

__version__ = '1.1.1'
__version__ = '1.1.2'

__all__ = (
# di
Expand Down
4 changes: 2 additions & 2 deletions aiodi/resolver/__init__.py
Original file line number Diff line number Diff line change
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] = {}) ->
"""

@abstractmethod
def parse_value(self, metadata: Metadata, retries: int = -1, extra: Dict[str, Any] = {}) -> Value:
def parse_value(self, metadata: Metadata, retries: int, extra: Dict[str, Any]) -> Value:
"""
Parse value from metadata

Expand Down
10 changes: 4 additions & 6 deletions aiodi/resolver/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +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] = {} # pylint: disable=W0613
) -> LoaderMetadata:
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 @@ -73,8 +71,8 @@ def extract_metadata(
def parse_value(
self,
metadata: LoaderMetadata,
retries: int = -1, # pylint: disable=W0613
extra: Dict[str, Any] = {}, # pylint: disable=W0613
retries: int, # pylint: disable=W0613
extra: Dict[str, Any], # pylint: disable=W0613
) -> LoadData:
return LoadData.from_metadata(metadata=metadata, data=metadata.decode())

Expand All @@ -83,5 +81,5 @@ 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]]:
return {
'value': (resolver.extract_metadata(data=items), 0),
'value': (resolver.extract_metadata(data=items, extra=extra), 0),
}
10 changes: 4 additions & 6 deletions aiodi/resolver/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,14 @@ def from_metadata(cls, metadata: PathMetadata) -> 'PathData':


class PathResolver(Resolver[PathMetadata, PathData]):
def extract_metadata(
self, data: Dict[str, Any], extra: Dict[str, Any] = {} # pylint: disable=W0613
) -> PathMetadata:
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 = -1, # pylint: disable=W0613
extra: Dict[str, Any] = {}, # pylint: disable=W0613
retries: int, # pylint: disable=W0613
extra: Dict[str, Any], # pylint: disable=W0613
) -> PathData:
return PathData.from_metadata(metadata)

Expand All @@ -65,5 +63,5 @@ 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]]:
return {
'value': (resolver.extract_metadata(data=items), 0),
'value': (resolver.extract_metadata(data=items, extra=extra), 0),
}
22 changes: 13 additions & 9 deletions aiodi/resolver/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def compute_excludes(self) -> List[str]:
return [left] if len(rights) == 0 else rights

def compute_services(
self, resolver: Resolver[Any, Any], resources: List[str], excludes: 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:
Expand All @@ -100,7 +100,8 @@ def compute_services(
autowire=self.autowire,
autoconfigure=self.autoconfigure,
),
}
},
extra=extra,
),
0,
)
Expand Down Expand Up @@ -208,9 +209,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] = {} # pylint: disable=W0613
) -> ServiceMetadata:
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'))
Expand All @@ -233,7 +232,7 @@ def extract_metadata(
defaults=defaults,
)

def parse_value(self, metadata: ServiceMetadata, retries: int = -1, extra: Dict[str, Any] = {}) -> Any:
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, extra.get('resolvers', {}).get('variable'))
Expand All @@ -248,8 +247,10 @@ def parse_value(self, metadata: ServiceMetadata, retries: int = -1, extra: Dict[
data={
'key': '@{0}:{1}'.format(metadata.name, param.name),
'val': metadata.arguments[param.name],
}
},
extra=extra,
),
retries=-1,
extra={'variables': _variables},
)
elif param.source_kind == 'svc':
Expand Down Expand Up @@ -287,11 +288,14 @@ def prepare_services_to_parse(
if defaults.has_resources():
services.update(
defaults.compute_services(
resolver=resolver, resources=defaults.compute_resources(), excludes=defaults.compute_excludes()
resolver=resolver,
resources=defaults.compute_resources(),
excludes=defaults.compute_excludes(),
extra=extra,
)
)
else:
metadata = resolver.extract_metadata(data={'key': key, 'val': val, 'defaults': defaults})
metadata = resolver.extract_metadata(data={'key': key, 'val': val, 'defaults': defaults}, extra=extra)
if is_abstract(metadata.type):
raise TypeError('Can not instantiate abstract class <{0}>!'.format(metadata.name))
services[key] = (metadata, 0)
Expand Down
45 changes: 35 additions & 10 deletions aiodi/resolver/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,36 @@
from ..helpers import raise_, re_finditer
from . import Resolver, ValueNotFound, ValueResolutionPostponed

REGEX = r"%(static|env|var)\((str:|int:|float:|bool:)?([\w]+)(,\s{1}'.*?')?\)%"
REGEX = r"%(static|env|var)\(([str:int:float:bool:]*?)([\w]+)(,\s{1}'.*?')?\)%"
STATIC_TEMPLATE: str = "%static({0}:{1}, '{2}')%"
_VAR_DEFAULTS = ...


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
type: Type[Any]
types: List[Type[Any]]
source_name: str
default: Any
match: Match

@classmethod
def from_match(cls, match: Match) -> 'VariableMetadata.MatchMetadata':
raw_types = (
['str']
if match.groups()[1] is None or len(str(match.groups()[1])) == 0
else [s for s in str(match.groups()[1]).split(':') if s.strip() != '']
)
raw_default = _VAR_DEFAULTS if match.groups()[3] is None else str(match.groups()[3])[3:-1]
return cls(
source_kind=str(match.groups()[0]),
type=str if match.groups()[1] is None else globals()['__builtins__'][str(match.groups()[1])[:-1]],
types=[globals()['__builtins__'][raw_type] for raw_type in raw_types],
source_name=str(match.groups()[2]),
default=None if match.groups()[3] is None else str(match.groups()[3])[3:-1],
default=None if isinstance(raw_default, str) and raw_default == 'None' else raw_default,
match=match,
)

Expand All @@ -36,6 +43,11 @@ def __init__(self, name: str) -> None:
super().__init__(kind='Variable', name=name)


class EnvironmentVariableNotFound(ValueNotFound):
def __init__(self, name: str) -> None:
super().__init__(kind='EnvironmentVariable', name=name)


class VariableResolutionPostponed(ValueResolutionPostponed[VariableMetadata]):
pass

Expand All @@ -49,7 +61,7 @@ def __call__(string: Any) -> List[Match]:
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'))
Expand All @@ -64,8 +76,9 @@ def extract_metadata(
)

def parse_value(
self, metadata: VariableMetadata, retries: int = -1, 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')
Expand All @@ -76,7 +89,12 @@ def parse_value(
if metadata_.source_kind == 'static':
typ_val = metadata_.default
elif metadata_.source_kind == 'env':
typ_val = getenv(key=metadata_.source_name, default=metadata_.default or '')
typ_val = getenv(key=metadata_.source_name, default=metadata_.default)
if typ_val is None:
# can only concatenate str to str
return typ_val
if metadata_.default is _VAR_DEFAULTS and typ_val == metadata_.default:
raise EnvironmentVariableNotFound(name=metadata_.source_name)
elif metadata_.source_kind == 'var':
if metadata_.source_name not in _variables:
if retries != -1:
Expand All @@ -93,11 +111,18 @@ def parse_value(
values += metadata.value[metadata_.match.end() :]
value: Any = ''.join(values)
if len(metadata.matches) == 1:
value = metadata.matches[0].type(value)
# multiple casting
for type_ in reversed(metadata.matches[0].types):
value = type_(value)
return 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]]:
return dict([(key, (resolver.extract_metadata(data={'key': key, 'val': val}), 0)) for key, val in items.items()])
return dict(
[
(key, (resolver.extract_metadata(data={'key': key, 'val': val}, extra=extra), 0))
for key, val in items.items()
]
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = "aiodi"
version = "1.1.1"
version = "1.1.2"
description = "Container for the Dependency Injection in Python."
license = "MIT"
authors = ["ticdenis <denisnavarroalcaide@outlook.es>"]
Expand Down
6 changes: 3 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ autoflake==1.4
# fixed due to Python 3.6 support
bandit==1.7.1
black==22.1.0
commitizen==2.20.5
commitizen==2.21.0
isort==5.10.1
liccheck==0.6.5
mkdocs==1.2.3
mkdocs-material==8.1.10
mkdocs-material==8.2.1
mypy==0.931
pre-commit==2.17.0
psutil==5.9.0
Expand All @@ -23,5 +23,5 @@ setuptools==59.6.0
# fixed due to Python 3.6 support
shellcheck-py==0.8.0.3
twine==3.8.0
types-toml==0.10.3
types-toml==0.10.4
wheel==0.37.1
2 changes: 2 additions & 0 deletions sample/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
name = "%env(str:APP_NAME, 'sample')%"
version = "%env(int:APP_VERSION, '1')%"
log_level = "%env(APP_LEVEL, 'INFO')%"
debug = "%env(bool:int:APP_DEBUG, '0')%"
text = "Hello World"

[tool.aiodi.services."_defaults"]
project_dir = "../../.."
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/aiodi/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ def test_container() -> None:
assert 'env.log_level' in di and di.get('env.log_level', typ=str) == 'INFO'
assert 'env.name' in di and di.get('env.name', typ=str) == 'sample'
assert 'env.version' in di and di.get('env.version', typ=int) == 1
assert 'env.debug' in di and di.get('env.debug', typ=bool) is False
assert 'env.text' in di and di.get('env.text', typ=str) == 'Hello World'

assert 'UserLogger' in di and di.get('UserLogger', typ=InMemoryUserLogger)
assert 'logging.Logger' in di and di.get(Logger)
Expand Down