Skip to content

Commit

Permalink
Merge pull request #2 from kirillsulim/development
Browse files Browse the repository at this point in the history
Add profiles
  • Loading branch information
kirillsulim authored Mar 24, 2023
2 parents a9bca4d + 560392a commit bf851c6
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 7 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tagil"
version = "0.1.0.post3"
version = "0.2.0"
description = "Dependency injection library for python"
authors = ["Kirill Sulim <[email protected]>"]
license = "mit"
Expand Down
10 changes: 6 additions & 4 deletions src/tagil/decorators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Union, Type, Optional
from typing import Dict, Union, Type, Optional, List

from tagil.manager import InjectionManager

Expand All @@ -8,11 +8,12 @@ def component(
*,
name: str = None,
inject: Optional[Dict[str, Union[str, Type]]] = None,
profile: Optional[Union[str, List[str]]] = None,
):
if cls is None:
return lambda c: component(c, name=name, inject=inject)
return lambda c: component(c, name=name, inject=inject, profile=profile)

InjectionManager().register_component(cls, name, inject)
InjectionManager().register_component(cls, name, inject, profile=profile)

return cls

Expand All @@ -22,8 +23,9 @@ def constructor(
*,
name: str = None,
inject: Optional[Dict[str, Union[str, Type]]] = None,
profile: Optional[Union[str, List[str]]] = None,
):
if method is None:
return lambda m: constructor(m, name=name, inject=inject)
return lambda m: constructor(m, name=name, inject=inject, profile=profile)

InjectionManager().register_constructor(method, name, inject)
38 changes: 36 additions & 2 deletions src/tagil/manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import inspect
import os
from inspect import signature
from collections import defaultdict
from typing import Optional, Dict, Union, Type, Callable
from typing import Optional, Dict, Union, Type, Callable, List
from threading import Lock

from tagil.exceptions import (
Expand Down Expand Up @@ -29,6 +30,7 @@ def __init__(
constructor: Optional[Callable] = None,
name: Optional[str] = None,
inject: Optional[Dict[str, Union[str, Type]]] = None,
profile: Optional[Union[str, List[str]]] = None,
):
if cls is None and constructor is None:
raise ValueError("Expect cls or constructor but both are None")
Expand All @@ -50,6 +52,7 @@ def __init__(
self.constructor = constructor
self.name = name
self.inject = {} if inject is None else inject
self.profile = None if profile is None else set(profile)

self.instance = None
self.mutex = Lock()
Expand Down Expand Up @@ -81,18 +84,28 @@ def get_arguments_data(self) -> Dict[str, ArgumentData]:


class InjectionManager(metaclass=Singleton):
PROFILES_ENV = "TAGIL_PROFILES"

def __init__(self):
self.by_class = defaultdict(list)
self.by_name = defaultdict(list)
self.init_stack = []

self.profiles = self._parse_profiles()

def register_component(
self,
cls: Type,
name: Optional[str] = None,
inject: Optional[Dict[str, Union[str, Type]]] = None,
profile: Optional[Union[str, List[str]]] = None,
):
ic = InstanceContainer(cls=cls, name=name, inject=inject)
ic = InstanceContainer(
cls=cls,
name=name,
inject=inject,
profile=profile,
)

self._add_container(ic)

Expand All @@ -101,11 +114,13 @@ def register_constructor(
method: Callable,
name: Optional[str] = None,
inject: Optional[Dict[str, Union[str, Type]]] = None,
profile: Optional[Union[str, List[str]]] = None,
):
ic = InstanceContainer(
constructor=method,
name=name,
inject=inject,
profile=profile,
)

self._add_container(ic)
Expand All @@ -114,10 +129,24 @@ def get_component(self, cls=None, name=None):
candidates = []
if cls is not None:
candidates.extend(self.by_class[cls])
candidates = list(
filter(
lambda c: c.profile is None
or len(c.profile.intersection(self.profiles)) != 0,
candidates,
)
)
if len(candidates) > 1 and name is not None:
candidates = list(filter(lambda ic: ic.name == name, candidates))
elif name is not None:
candidates.extend(self.by_name[name])
candidates = list(
filter(
lambda c: c.profile is None
or len(c.profile.intersection(self.profiles)) != 0,
candidates,
)
)
else:
raise ValueError("Must be called with cls or name or both")

Expand Down Expand Up @@ -180,3 +209,8 @@ def _has_post_init(instance):
@staticmethod
def _has_pre_destroy(instance):
return hasattr(instance, "pre_destroy") and callable(instance.pre_destroy)

@staticmethod
def _parse_profiles():
profiles_str = os.environ.get(InjectionManager.PROFILES_ENV, "")
return {s.strip().lower() for s in profiles_str.split(",")}
24 changes: 24 additions & 0 deletions tests/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,20 @@ def constructor_inject_by_name(arg):
return arg


class Profiled:
pass


@component(profile="a")
class ProfiledA(Profiled):
pass


@component(profile="b")
class ProfiledB(Profiled):
pass


class TestManager(TestCase):
def test_stub_class_load(self):
instance = InjectionManager().get_component(StubClass)
Expand Down Expand Up @@ -191,3 +205,13 @@ def test_constructor_inject_by_name(self):
instance = InjectionManager().get_component(name="constructor_inject_by_name")

self.assertIsInstance(instance, NamedStub)

def test_profile(self):
manager = InjectionManager()
saved = manager.profiles
manager.profiles = set("a")

instance = InjectionManager().get_component(Profiled)

self.assertIsInstance(instance, ProfiledA)
manager.profile = saved

0 comments on commit bf851c6

Please sign in to comment.