Skip to content

Commit af3a276

Browse files
authored
feat: 📝 Update documentation
1 parent 7ea3c59 commit af3a276

7 files changed

Lines changed: 114 additions & 50 deletions

File tree

documentation/loaders.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,43 @@ def main(profile_name: str = None, /):
123123
if __name__ == "__main__":
124124
main("dev") # One could imagine the profile name being transmitted via an environment variable or CLI parameter
125125
```
126+
127+
## ProfileLoader
128+
129+
_This is a slightly more complete version of `load_profile`._
130+
131+
If you use modules as subsets of dependencies, this class will make your life easier.
132+
133+
It is recommended to use a single instance of `ProfileLoader` to avoid unexpected behavior. If it exists, this instance
134+
must be passed to `entrypointmaker` and `load_test_profile`.
135+
136+
Here's an example of its use:
137+
138+
```python
139+
from injection import mod
140+
from injection.loaders import ProfileLoader
141+
142+
profile_loader = ProfileLoader(
143+
{
144+
mod().name: ["global"],
145+
"dev": ["stub", "global"],
146+
"test": ["stub", "global"],
147+
"stub": ["global"]
148+
}
149+
)
150+
151+
# Ensures that dependent modules are used properly.
152+
# If `init` isn't called, it will be automatically called with `load`.
153+
profile_loader.init()
154+
155+
# Load `dev` profile.
156+
profile_loader.load("dev")
157+
```
158+
159+
> [!NOTE]
160+
> `load` can also be used as a context manager:
161+
>
162+
> ```python
163+
> with profile_loader.load("<profile-name>"):
164+
> ...
165+
> ```

injection/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ class Module:
320320
module: Module,
321321
*,
322322
priority: Priority | PriorityStr = ...,
323+
unlock: bool = ...,
323324
) -> Iterator[Self]:
324325
"""
325326
Context manager or decorator for temporary use of a module.

injection/_core/module.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,12 +788,16 @@ def use_temporarily(
788788
module: Module,
789789
*,
790790
priority: Priority | PriorityStr = Priority.get_default(),
791+
unlock: bool = False,
791792
) -> Iterator[Self]:
792793
self.use(module, priority=priority)
793794

794795
try:
795796
yield self
796797
finally:
798+
if unlock:
799+
self.unlock()
800+
797801
self.stop_using(module)
798802

799803
def change_priority(self, module: Module, priority: Priority | PriorityStr) -> Self:

injection/entrypoint.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,7 @@ def _make_decorator[*Ts, _T](
153153
setup_method = profile_loader.module.make_injected_function(setup_method)
154154

155155
def decorator(function: Callable[P, T]) -> Callable[P, _T]:
156-
if profile_loader.module_subsets:
157-
profile_loader.init()
158-
156+
profile_loader.init()
159157
self = cls(function, profile_loader)
160158
return MethodType(setup_method, self)().function
161159

injection/loaders.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ class ProfileLoader:
145145
module: Module = field(default_factory=mod, kw_only=True)
146146
__initialized_modules: set[str] = field(default_factory=set, init=False)
147147

148+
@property
149+
def __is_empty(self) -> bool:
150+
return not self.module_subsets
151+
148152
def init(self) -> Self:
149153
self.__init_subsets_for(self.module)
150154
return self
@@ -159,12 +163,12 @@ def _unload(self, name: str, /) -> None:
159163
self.module.unlock().stop_using(mod(name))
160164

161165
def __init_subsets_for(self, module: Module) -> Module:
162-
if not self.__is_initialized(module):
166+
if not self.__is_empty and not self.__is_initialized(module):
163167
target_modules = tuple(
164168
self.__init_subsets_for(mod(name))
165169
for name in self.module_subsets.get(module.name, ())
166170
)
167-
module.unlock().init_modules(*target_modules)
171+
module.init_modules(*target_modules)
168172
self.__mark_initialized(module)
169173

170174
return module

tests/core/test_module.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,23 @@ def some_function():
339339
some_function()
340340
event_history.assert_length(2)
341341

342+
def test_use_temporarily_with_unlock(self, module):
343+
second_module = Module()
344+
345+
@module.singleton
346+
class A: ...
347+
348+
with pytest.raises(ModuleLockError):
349+
with module.use_temporarily(second_module):
350+
module.find_instance(A)
351+
352+
# Cleaning
353+
module.unlock().stop_using(second_module)
354+
355+
# Ensure there are no errors
356+
with module.use_temporarily(second_module, unlock=True):
357+
module.find_instance(A)
358+
342359
"""
343360
change_priority
344361
"""

0 commit comments

Comments
 (0)