Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rename many fields so they have clearer meaning #8

Merged
merged 1 commit into from
Jan 23, 2024
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
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## DIshka - DI by Tishka17
## DIshka (from russian "small DI")

Minimal DI framework with scopes
Small DI framework with scopes and agreeable API.

### Purpose

Expand All @@ -14,6 +14,7 @@ Main ideas:
* **Modular providers**. Instead of creating lots of separate functions or contrariwise a big single class, you can split your factories into several classes, which makes them simpler reusable.
* **Clean dependencies**. You do not need to add custom markers to the code of dependencies so to allow library to see them. All customization is done within providers code and only borders of scopes have to deal with library API.
* **Simple API**. You need minimum of objects to start using library. You can easily integrate it with your task framework, examples provided.
* **Speed**. It is fast enough so you not to worry about. It is even faster than many of the analogs.

### Quickstart

Expand Down Expand Up @@ -55,27 +56,32 @@ with make_container(provider) as container:
**Provider** is a collection of functions which really provide some objects.
* Add method and mark it with `@provide` decorator. It can be sync or async method returning some value.
```python
@provide(scope=Scope.REQUEST)
def get_a(self) -> A:
return A()
class MyProvider(Provider):
@provide(scope=Scope.REQUEST)
def get_a(self) -> A:
return A()
```
* Want some finalization when exiting the scope? Make that method generator:
```python
@provide(scope=Scope.REQUEST)
def get_a(self) -> Iterable[A]:
a = A()
yield a
a.close()
class MyProvider(Provider):
@provide(scope=Scope.REQUEST)
def get_a(self) -> Iterable[A]:
a = A()
yield a
a.close()
```
* Do not have any specific logic and just want to create class using its `__init__`? then add a provider attribute using `provide` as function passing that class.
```python
a = provide(A, scope=Scope.REQUEST)
class MyProvider(Provider):
a = provide(A, scope=Scope.REQUEST)
```
* Want to create a child class instance when parent is requested? add a `dependency` attribute to `provide` function with a parent class while passing child as a first parameter
```python
a = provide(AChild, scope=Scope.REQUEST, dependency=A)
class MyProvider(Provider):
a = provide(source=AChild, scope=Scope.REQUEST, provides=A)
```
* Having multiple interfaces which can be created as a same class? Use alias:
* Having multiple interfaces which can be created as a same class with defined provider? Use alias:
```python
p = alias(AProtocol, A)
class MyProvider(Provider):
p = alias(source=A, provides=AProtocol)
```
2 changes: 1 addition & 1 deletion examples/di/with_dishka.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


class MyProvider(Provider):
a = provide(A1, scope=MyScope.REQUEST, dependency=A)
a = provide(A1, scope=MyScope.REQUEST, provides=A)
c1 = provide(CA, scope=MyScope.REQUEST)
c2 = provide(CAA, scope=MyScope.REQUEST)
c3 = provide(CAAA, scope=MyScope.REQUEST)
Expand Down
2 changes: 1 addition & 1 deletion examples/sync_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(self, a: int):
self.a = a

get_a = provide(A, scope=Scope.REQUEST)
get_basea = alias(A, dependency=BaseA)
get_basea = alias(source=A, provides=BaseA)

@provide(scope=Scope.APP)
def get_int(self) -> int:
Expand Down
10 changes: 5 additions & 5 deletions src/dishka/async_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,19 @@ async def _get_self(
for dependency in dep_provider.dependencies
]
if dep_provider.type is ProviderType.GENERATOR:
generator = dep_provider.callable(*sub_dependencies)
generator = dep_provider.source(*sub_dependencies)
self.exits.append(Exit(dep_provider.type, generator))
return next(generator)
elif dep_provider.type is ProviderType.ASYNC_GENERATOR:
generator = dep_provider.callable(*sub_dependencies)
generator = dep_provider.source(*sub_dependencies)
self.exits.append(Exit(dep_provider.type, generator))
return await anext(generator)
elif dep_provider.type is ProviderType.ASYNC_FACTORY:
return await dep_provider.callable(*sub_dependencies)
return await dep_provider.source(*sub_dependencies)
elif dep_provider.type is ProviderType.FACTORY:
return dep_provider.callable(*sub_dependencies)
return dep_provider.source(*sub_dependencies)
elif dep_provider.type is ProviderType.VALUE:
return dep_provider.callable
return dep_provider.source
else:
raise ValueError(f"Unsupported type {dep_provider.type}")

Expand Down
8 changes: 5 additions & 3 deletions src/dishka/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ def _get_self(
for dependency in dep_provider.dependencies
]
if dep_provider.type is ProviderType.GENERATOR:
generator = dep_provider.callable(*sub_dependencies)
generator = dep_provider.source(*sub_dependencies)
self.exits.append(generator)
return next(generator)
elif dep_provider.type is ProviderType.FACTORY:
return dep_provider.callable(*sub_dependencies)
return dep_provider.source(*sub_dependencies)
elif dep_provider.type is ProviderType.VALUE:
return dep_provider.callable
return dep_provider.source
else:
raise ValueError(f"Unsupported type {dep_provider.type}")

Expand Down Expand Up @@ -110,6 +110,8 @@ def close(self):


class ContextWrapper:
__slots__ = ("container", )

def __init__(self, container: Container):
self.container = container

Expand Down
85 changes: 46 additions & 39 deletions src/dishka/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,28 @@ class ProviderType(Enum):
VALUE = "value"


def _identity(x: Any) -> Any:
return x


class DependencyProvider:
__slots__ = (
"dependencies", "callable", "result_type", "scope", "type",
"dependencies", "source", "provides", "scope", "type",
"is_to_bound",
)

def __init__(
self,
dependencies: Sequence,
callable: Callable,
result_type: Type,
dependencies: Sequence[Any],
source: Any,
provides: Type,
scope: Optional[BaseScope],
type: ProviderType,
is_to_bound: bool,
):
self.dependencies = dependencies
self.callable = callable
self.result_type = result_type
self.source = source
self.provides = provides
self.scope = scope
self.type = type
self.is_to_bound = is_to_bound
Expand All @@ -55,110 +59,113 @@ def __get__(self, instance, owner):
if instance is None:
return self
if self.is_to_bound:
callable = self.callable.__get__(instance, owner)
source = self.source.__get__(instance, owner)
else:
callable = self.callable
source = self.source
return DependencyProvider(
dependencies=self.dependencies,
callable=callable,
result_type=self.result_type,
source=source,
provides=self.provides,
scope=self.scope,
type=self.type,
is_to_bound=False,
)

def aliased(self, target: Type):
return DependencyProvider(
dependencies=self.dependencies,
callable=self.callable,
result_type=target,
dependencies=[self.provides],
source=_identity,
provides=target,
scope=self.scope,
type=self.type,
is_to_bound=self.is_to_bound,
)


def make_dependency_provider(
dependency: Any,
provides: Any,
scope: Optional[BaseScope],
func: Callable,
source: Callable,
):
if isclass(func):
hints = get_type_hints(func.__init__, include_extras=True)
if isclass(source):
hints = get_type_hints(source.__init__, include_extras=True)
hints.pop("return", None)
possible_dependency = func
possible_dependency = source
is_to_bind = False
else:
hints = get_type_hints(func, include_extras=True)
hints = get_type_hints(source, include_extras=True)
possible_dependency = hints.pop("return", None)
is_to_bind = True

if isclass(func):
if isclass(source):
provider_type = ProviderType.FACTORY
elif isasyncgenfunction(func):
elif isasyncgenfunction(source):
provider_type = ProviderType.ASYNC_GENERATOR
if get_origin(possible_dependency) is AsyncIterable:
possible_dependency = get_args(possible_dependency)[0]
else: # async generator
possible_dependency = get_args(possible_dependency)[0]
elif isgeneratorfunction(func):
elif isgeneratorfunction(source):
provider_type = ProviderType.GENERATOR
if get_origin(possible_dependency) is Iterable:
possible_dependency = get_args(possible_dependency)[0]
else: # generator
possible_dependency = get_args(possible_dependency)[1]
elif iscoroutinefunction(func):
elif iscoroutinefunction(source):
provider_type = ProviderType.ASYNC_FACTORY
else:
provider_type = ProviderType.FACTORY

return DependencyProvider(
dependencies=list(hints.values()),
type=provider_type,
callable=func,
source=source,
scope=scope,
result_type=dependency or possible_dependency,
provides=provides or possible_dependency,
is_to_bound=is_to_bind,
)


class Alias:
def __init__(self, target, result_type):
self.target = target
self.result_type = result_type
__slots__ = ("source", "provides")

def __init__(self, source, provides):
self.source = source
self.provides = provides


def alias(
target: Type,
dependency: Any = None,
*,
source: Type,
provides: Type,
):
return Alias(
target=target,
result_type=dependency,
source=source,
provides=provides,
)


def provide(
func: Union[None, Callable] = None,
source: Union[None, Callable, Type] = None,
*,
scope: BaseScope = None,
dependency: Any = None,
provides: Any = None,
):
if func is not None:
return make_dependency_provider(dependency, scope, func)
if source is not None:
return make_dependency_provider(provides, scope, source)

def scoped(func):
return make_dependency_provider(dependency, scope, func)
return make_dependency_provider(provides, scope, func)

return scoped


class Provider:
def __init__(self):
self.dependencies = {}
self.dependency_providers = {}
self.aliases = []
for name, attr in vars(type(self)).items():
if isinstance(attr, DependencyProvider):
self.dependencies[attr.result_type] = getattr(self, name)
self.dependency_providers[attr.provides] = getattr(self, name)
elif isinstance(attr, Alias):
self.aliases.append(attr)
8 changes: 4 additions & 4 deletions src/dishka/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, scope: BaseScope):
self.scope = scope

def add_provider(self, provider: DependencyProvider):
self._providers[provider.result_type] = provider
self._providers[provider.provides] = provider

def get_provider(self, dependency: Any):
return self._providers.get(dependency)
Expand All @@ -21,15 +21,15 @@ def get_provider(self, dependency: Any):
def make_registry(*providers: Provider, scope: BaseScope) -> Registry:
registry = Registry(scope)
for provider in providers:
for dependency_provider in provider.dependencies.values():
for dependency_provider in provider.dependency_providers.values():
if dependency_provider.scope is scope:
registry.add_provider(dependency_provider)

for provider in providers:
for alias in provider.aliases:
dependency_provider = registry.get_provider(alias.target)
dependency_provider = registry.get_provider(alias.source)
if dependency_provider:
registry.add_provider(
dependency_provider.aliased(alias.result_type),
dependency_provider.aliased(alias.provides),
)
return registry
Loading