-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'container-validators' into 'main'
Add container validators See merge request ThirVondukr/aioinject!7
- Loading branch information
Showing
12 changed files
with
251 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ dist/ | |
|
||
# Coverage | ||
.coverage | ||
coverage.xml | ||
|
||
# Cache | ||
.pytest_cache | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,4 +43,4 @@ tasks: | |
cmds: | ||
- task: format | ||
- task: typecheck | ||
- task: test | ||
- task: testcov |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from collections.abc import Sequence | ||
|
||
from aioinject import Callable, Singleton | ||
from aioinject.validation._builtin import ( | ||
ForbidDependency, | ||
all_dependencies_are_present, | ||
) | ||
from aioinject.validation._validate import validate_container | ||
from aioinject.validation.abc import ContainerValidator | ||
|
||
|
||
DEFAULT_VALIDATORS: Sequence[ContainerValidator] = [ | ||
all_dependencies_are_present, | ||
ForbidDependency( | ||
dependant=lambda p: isinstance(p, Singleton), | ||
dependency=lambda p: isinstance(p, Callable) | ||
and not isinstance(p, Singleton), | ||
), | ||
] | ||
|
||
__all__ = [ | ||
"DEFAULT_VALIDATORS", | ||
"ContainerValidator", | ||
"all_dependencies_are_present", | ||
"ForbidDependency", | ||
"validate_container", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
from collections.abc import Callable, Sequence | ||
from typing import Any | ||
|
||
import aioinject | ||
from aioinject import Provider | ||
from aioinject.validation.abc import ContainerValidator | ||
from aioinject.validation.error import ( | ||
ContainerValidationError, | ||
DependencyNotFoundError, | ||
) | ||
|
||
|
||
def all_dependencies_are_present( | ||
container: aioinject.Container, | ||
) -> Sequence[ContainerValidationError]: | ||
errors = [] | ||
for providers in container.providers.values(): | ||
for provider in providers: | ||
for dependency in provider.dependencies: | ||
if dependency.type_ not in container.providers: | ||
error = DependencyNotFoundError( | ||
message=f"Provider for type {dependency.type_} not found", | ||
dependency=dependency.type_, | ||
) | ||
errors.append(error) | ||
|
||
return errors | ||
|
||
|
||
class ForbidDependency(ContainerValidator): | ||
def __init__( | ||
self, | ||
dependant: Callable[[Provider[Any]], bool], | ||
dependency: Callable[[Provider[Any]], bool], | ||
) -> None: | ||
self.dependant = dependant | ||
self.dependency = dependency | ||
|
||
def __call__( | ||
self, | ||
container: aioinject.Container, | ||
) -> Sequence[ContainerValidationError]: | ||
errors = [] | ||
for providers in container.providers.values(): | ||
for provider in providers: | ||
if not self.dependant(provider): | ||
continue | ||
|
||
for dependency in provider.dependencies: | ||
dependency_provider = container.get_provider( | ||
type_=dependency.type_, | ||
impl=dependency.implementation, | ||
) | ||
if self.dependency(dependency_provider): | ||
msg = f"Provider {provider!r} cannot depend on {dependency_provider!r}" | ||
errors.append(ContainerValidationError(msg)) | ||
return errors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from collections.abc import Iterable | ||
|
||
import aioinject | ||
from aioinject.validation.abc import ContainerValidator | ||
from aioinject.validation.error import ( | ||
ContainerValidationError, | ||
ContainerValidationErrorGroup, | ||
) | ||
|
||
|
||
def validate_container( | ||
container: aioinject.Container, | ||
validators: Iterable[ContainerValidator], | ||
) -> None: | ||
errors: list[ContainerValidationError] = [] | ||
for validator in validators: | ||
errors.extend(validator.__call__(container)) | ||
|
||
if errors: | ||
raise ContainerValidationErrorGroup(errors=errors) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from collections.abc import Sequence | ||
from typing import Protocol | ||
|
||
import aioinject | ||
from aioinject.validation.error import ContainerValidationError | ||
|
||
|
||
class ContainerValidator(Protocol): | ||
def __call__( | ||
self, | ||
container: aioinject.Container, | ||
) -> Sequence[ContainerValidationError]: | ||
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import dataclasses | ||
from collections.abc import Sequence | ||
from typing import Any | ||
|
||
|
||
@dataclasses.dataclass(slots=True, frozen=True) | ||
class ContainerValidationError: | ||
message: str | ||
|
||
|
||
@dataclasses.dataclass(slots=True, frozen=True) | ||
class DependencyNotFoundError(ContainerValidationError): | ||
dependency: type[Any] | ||
|
||
|
||
@dataclasses.dataclass | ||
class ContainerValidationErrorGroup(Exception): # noqa: N818 | ||
# Exception group could be used instead, but it's added in python 3.11 | ||
errors: Sequence[ContainerValidationError] | ||
|
||
def __str__(self) -> str: | ||
return repr(self) # pragma: no cover |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from collections.abc import Sequence | ||
from typing import Any | ||
|
||
import pytest | ||
|
||
from aioinject import Callable, Container, Provider | ||
from aioinject.validation import ( | ||
all_dependencies_are_present, | ||
validate_container, | ||
) | ||
from aioinject.validation.error import ( | ||
ContainerValidationErrorGroup, | ||
DependencyNotFoundError, | ||
) | ||
|
||
|
||
_VALIDATORS = [all_dependencies_are_present] | ||
|
||
|
||
def _str_dependency(number: int) -> str: | ||
return str(number) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"providers", | ||
[ | ||
[Callable(int)], | ||
[Callable(_str_dependency), Callable(int)], | ||
], | ||
) | ||
def test_ok(providers: Sequence[Provider[Any]]) -> None: | ||
container = Container() | ||
for provider in providers: | ||
container.register(provider) | ||
|
||
validate_container(container, _VALIDATORS) | ||
|
||
|
||
def test_err() -> None: | ||
container = Container() | ||
container.register(Callable(_str_dependency)) | ||
|
||
with pytest.raises(ContainerValidationErrorGroup) as exc_info: | ||
validate_container(container, _VALIDATORS) | ||
|
||
assert len(exc_info.value.errors) == 1 | ||
err = exc_info.value.errors[0] | ||
assert isinstance(err, DependencyNotFoundError) | ||
assert err.dependency == int |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from collections.abc import Sequence | ||
from typing import Any | ||
|
||
import pytest | ||
|
||
from aioinject import Callable, Container, Provider, Singleton | ||
from aioinject.validation import ForbidDependency, validate_container | ||
from aioinject.validation.error import ( | ||
ContainerValidationErrorGroup, | ||
) | ||
|
||
|
||
_VALIDATORS = [ | ||
ForbidDependency( | ||
dependant=lambda p: isinstance(p, Singleton), | ||
dependency=lambda p: isinstance(p, Callable) | ||
and not isinstance(p, Singleton), | ||
), | ||
] | ||
|
||
|
||
def _str_dependency(number: int) -> str: | ||
return str(number) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"providers", | ||
[ | ||
[Callable(int)], | ||
[Callable(_str_dependency), Callable(int)], | ||
[Callable(_str_dependency), Singleton(int)], | ||
[Singleton(_str_dependency), Singleton(int)], | ||
], | ||
) | ||
def test_ok(providers: Sequence[Provider[Any]]) -> None: | ||
container = Container() | ||
for provider in providers: | ||
container.register(provider) | ||
|
||
validate_container(container, _VALIDATORS) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"providers", | ||
[ | ||
[Singleton(_str_dependency), Callable(int)], | ||
], | ||
) | ||
def test_err(providers: Sequence[Provider[Any]]) -> None: | ||
container = Container() | ||
for provider in providers: | ||
container.register(provider) | ||
|
||
with pytest.raises(ContainerValidationErrorGroup): | ||
validate_container(container, _VALIDATORS) |