Welcome to pinjected’s documentation!¶
Indices and tables¶
- class pinjected.Design¶
- abstract property bindings: Dict[IBindKey, IBind]¶
- abstract property children¶
- dfs_design()¶
- static empty()¶
- static from_bindings(bindings: Dict[IBindKey, IBind])¶
- keys()¶
- provide(tgt: str | IBindKey)¶
- purify(target: Providable)¶
- to_graph()¶
- abstract property validations: Dict[IBindKey, Callable[[IBindKey, Any], Awaitable[ValResult]]]¶
- pinjected.IProxy¶
alias of
DelegatedVar
- class pinjected.Injected¶
The
Injectedclass represents a sophisticated dependency injection mechanism in Python. It encapsulates an object that requires certain dependencies to be resolved. The class maintains a set of dependencies necessary for the object’s creation and utilizes a provider function that generates the desired variable with its dependencies satisfied.Basic Usage:¶
from pinjected.di.util import Injected from pinjected import Design def provide_ab(a:int, b:int) -> int: return a + b # The bind method creates an Injected object from a provider function, using the function's # arguments as dependencies. injected: Injected[int] = Injected.bind(provide_ab) design = EmptyDesign.bind_instance(a=1, b=2) assert design.to_graph()[injected] == 3
Advanced Features:¶
Composition:
Injectedinstances can be manipulated and combined in several ways to build complex dependency structures.map: Transform the result of an
Injectedinstance.from pinjected.di.util import Injected, instances from pinjected import Design design: Design = instances(a=1) # Shortcut for binding instances a: Injected[int] = Injected.by_name('a') b: Injected[int] = a.map(lambda x: x + 1) # b is now a + 1 g = design.to_graph() assert g[a] + 1 == g[b]
zip/mzip: Combine multiple
Injectedinstances.from pinjected.di.util import Injected from pinjected import Design design = EmptyDesign.bind_instance(a=1, b=2) g = design.to_graph() a = Injected.by_name('a') b = Injected.by_name('b') c = a.map(lambda x: x + 2) abc = Injected.mzip(a, b, c) # Combine more than two Injected instances ab_zip = Injected.zip(a, b) # Standard zip for two Injected instances assert g[abc] == (1, 2, 3) assert g[ab_zip] == (1, 2)
dict/list: Create a dictionary or list from multiple
Injectedinstances.from pinjected.di.util import Injected, instances design = instances(a=1, b=2) a = Injected.by_name('a') b = Injected.by_name('b') c = a.map(lambda x: x + 2) injected_dict: Injected[dict] = Injected.dict(a=a, b=b, c=c) # Creates {'a':1, 'b':2, 'c':3} injected_list: Injected[list] = Injected.list(a, b, c) # Creates [1, 2, 3]
These composition tools enhance the flexibility of dependency injection, enabling more complex and varied structures to suit diverse programming needs. By allowing transformations and combinations of
Injectedinstances, they provide powerful ways to manage and utilize dependencies within your applications.- add_dynamic_dependencies(*deps: str | set[str])¶
- static bind(_target_function_, **kwargs_mapping: str | type | Callable | Injected) InjectedFunction¶
- static by_name(name: str)¶
- property complete_dependencies: Set[str]¶
- static conditional_preparation(condition: Injected[bool], preparation: Injected, utilization: Injected)¶
- abstract dependencies() Set[str]¶
- desync()¶
- static direct(_target_function, **kwargs) Injected¶
uses Injected.pure by default unless the parameter is an instance of Injected. :param _target_function: :param kwargs: :return:
- abstract dynamic_dependencies() Set[str]¶
- Returns:
a set of dependencies which are not statically known. mainly used for analysis.
use this to express an injected that conditionally depends on something, such as caches.
- from_impl(dependencies: Set[str])¶
- abstract get_provider()¶
- Returns:
a provider
- get_signature()¶
- static inject_except(target_function, *whitelist: str) Injected[Callable]¶
- Parameters:
target_function
whitelist – name of arguments which should not be injected by DI.
- Returns:
Injected[Callable[(whitelisted args)=>Any]]
- static inject_partially(original_function: Callable, **injection_targets: Injected) Injected[Callable]¶
Partially injects dependencies into the parameters of the target function.
This utility allows for specific arguments of a callable to be automatically populated with injected dependencies, leaving the remainder to be supplied at the point of invocation. It facilitates a cleaner separation of concerns by decoupling the resolution of dependencies from the function’s implementation.
Parameters: original_function (Callable): The original function into which dependencies are to be injected. This function maintains its original signature, with certain parameters now automatically resolved via injection. injection_targets (dict): A mapping of parameter names to their injected dependencies. Each entry corresponds to a specific argument of original_function and the dependency to be injected into it. These parameters will be automatically populated when the returned function is invoked.
Returns: Injected[Callable]: A wrapped version of original_function that, when called, will receive the injected dependencies specified in injection_targets. The callable requires the remaining parameters that were not part of injection_targets, maintaining the order and names as in the original function’s signature.
Note: The implementation focuses on flexibility and separation of concerns at the cost of some complexity in the function’s wrapping mechanics. This design choice prioritizes decoupling over simplicity to ensure that functions can be composed and dependencies managed outside of the business logic.
- Example:
def f(a, b, c): return a + b + c # Creating a partially injected function injected_func = Injected.partial(f, a=1, b=2) # 'a' and 'b' are injected, 'c' is left to be provided later. # Using the partially injected function result:Injected[int] = injected_func(c=3) # Now we provide 'c', and the function executes with all parameters. g[result] == 6 # The result is 6. g[] is a syntax sugar for g.provide(result) # Alternatively, using different syntax or within different contexts, the following are equivalent: assert g[Injected.pure(6)] == g[result] # Comparing with a pure value assert g[Injected.partial(f, a=1, b=2)](c=3) == 6 # Using the function within a context 'g' assert g[Injected.partial(f, a=1, b=2)(c=3)] == 6 # Immediate invocation within a context 'g'
These examples illustrate how partial is used to pre-fill certain parameters of a function, allowing the remaining ones to be specified at a later point in the code or within different execution contexts.
- static map_elements(f: Injected[Callable], elements: Injected[Iterable]) Injected[Iterable]¶
# for ( # f <- f, # elements <- elements # ) yield map(f,elements)
- Parameters:
f
elements
- Returns:
- static partial(f: Injected[Callable], *args: Injected, **kwargs: Injected)¶
applies partial application to the given function.
- static procedure(*targets: Injected)¶
Runs the targets in order, and returns the last one. This is useful for running injecteds which performs side effects. :param targets: :return:
- property proxy: DelegatedVar¶
use this to modify injected variables freely without map. call eval() at the end to finish modification # it seems this thing is preventing from pickling?
- static pure(value)¶
- pinjected.classes(**kwargs)¶
Binds classes to a new
Designinstance, adding code locations for reference.This function is a utility for binding classes to a new design, making them available for dependency injection. It also registers their code locations, aiding in debugging by preserving the origin information of each class.
Parameters:¶
- **kwargsdict
Arbitrary keyword arguments. Each key is a string that represents the dependency name, and the value is the class to be bound.
Returns:¶
- Design
Returns a new
Designinstance with the specified classes bound.
Example:¶
class MyClass: pass # Binding the class within a new design design_with_class = classes(MyClass=MyClass) # 'design_with_class' now has 'MyClass' available for dependency injection.
Note:¶
The function automatically adds code locations for the classes, making it easier to track the source of each dependency within the system.
- pinjected.destructors(**kwargs)¶
registers destructors. using DestructorKey. The values must be an async function that takes one argument.
- pinjected.injected(tgt: str | type | Callable)¶
The
injecteddecorator automates dependency injection, transforming a target (string, class, or callable) into anInjectedinstance. It specifically treats positional-only parameters as dependencies, automatically injecting them. In contrast, non-positional-only parameters are left as-is for later specification during function or method invocation.- Parameters:
tgt – The target indicating what is to be injected. This can be: 1.
str: the name of a dependency. 2.type: a class that needs automated dependency injection for instantiation. 3.Callable: a function or method requiring dependencies.- Returns:
An appropriate
Injectedinstance, proxy, or wrapped entity with dependencies injected, contingent on the nature oftgt.
Usage Example:
@injected def function_requiring_dependencies(dependency1: Type1, /, normal_param: Type2): # Function body here. 'dependency1' is injected, 'normal_param' must be provided during call. @injected class ClassRequiringDependencies: def __init__(self, dependency1: Type1, /, normal_param: Type2): # Constructor body. 'dependency1' is injected, 'normal_param' is left for object creation time. # For direct dependency retrieval via a string identifier. dependency_instance = injected("dependency_key")
In these examples,
dependency1is a positional-only parameter and treated as a dependency to be automatically injected. On the other hand,normal_paramis a non-positional-only parameter. It’s not considered a dependency within the automatic injection process, and thus, must be specified during the routine call or object instantiation.Note
This approach enforces clear demarcation between automatically resolved dependencies (positional-only) and those parameters that developers need to provide explicitly during function/method invocation or class instantiation. This strategy enhances code readability and ensures that the dependency injection framework adheres to explicit programming practices.
- pinjected.instance(f) Injected¶
The
injected_instance(also accessible asinstance) is a decorator that creates anInjectedinstance from a function. This function essentially converts a regular function that returns an instance into anInjectedtype that can be used for dependency injection, particularly within the context of a design setup.- Parameters:
f (Function) – The function that generates an instance, typically utilizing dependencies.
- Returns:
An instance of
Injectedthat wraps the provided function.- Return type:
- Example:
@instance def logger(dep1, dep2): from logging import getLogger # maybe use dep1, dep2... return getLogger(__name__) # Now, 'logger' is an instance of 'Injected' specialized with 'Logger'. assert type(logger) == Injected # True for Injected[Logger] # This 'Injected' instance can be contributed to a design like so: design = providers( logger=logger ) # Usage of the designed provider _logger = design.provide('logger') # returns a Logger instance _logger.info('hello world')
This approach allows the function to be integrated into a dependency injection system easily, with the
Injectedinstance handling the complexities of resolving and managing dependencies.
- pinjected.instances(**kwargs)¶
Registers multiple concrete instances or values to a new
Designinstance and adds their respective code locations.This function is utilized for binding actual instances, as opposed to providers or factories, directly into a
Design. This is particularly useful for constants or already-constructed objects that you wish to make available for dependency injection. Each instance’s code location is automatically registered, aiding in debugging and system introspection by maintaining information about where each instance was defined.The function performs assertions to ensure that no
DelegatedVarorInjectedinstances are passed, preventing common errors.Parameters:¶
kwargs : dict Arbitrary keyword arguments representing the instances to be bound. Each key represents the name of the dependency, and the value is the corresponding concrete instance or value. The function asserts that none of the values are instances of
DelegatedVarorInjected, raising an assertion error if this case is encountered to prevent misuse.Returns:¶
- Design
A
Designinstance with the concrete objects or values bound and their code locations registered.
Raises:¶
- AssertionError
If any value in
kwargsis an instance ofDelegatedVarorInjected, an assertion error is raised, indicating that such usage is forbidden to prevent errors in binding.
Example:¶
# Pre-constructed objects or constants database_connection = create_database_connection() constant_value = 42 # Create a design with instances registered design_with_instances = instances( database_connection=database_connection, constant=constant_value ) # The 'design_with_instances' now has 'database_connection' and 'constant_value' # registered as concrete instances, along with their code locations.
By enforcing constraints and adding code locations, this function helps maintain a clear and error-resistant system design, ensuring that the dependency injection mechanism is used appropriately and effectively.
- pinjected.providers(**kwargs)¶
Register multiple providers or Injected instances to a new Design instance, automatically adding code locations.
This utility function simplifies the process of creating a Design instance and binding providers to it. It accepts both regular functions and instances of Injected as providers. These functions can be ones that require dependencies for their computations or Injected objects that encapsulate the dependency requirements.
Each provider is automatically associated with its code location, aiding in system introspection and debugging by keeping track of where each provider was defined.
Parameters:¶
- **kwargsdict
Arbitrary keyword arguments. Each key represents the name of the dependency, and the value can be either: 1. A provider function, which is a callable that returns the necessary object for the dependency. This function can itself require other dependencies. 2. An Injected instance which encapsulates both the creation of the dependency and its own dependencies.
Returns:¶
- Design
A Design instance with the providers or Injected instances bound and their code locations registered.
Example:¶
from pinjected.di.util import Injected def provide_database_connection(): # logic to initiate a database connection pass injected_query_executor = Injected.bind(lambda connection: connection.execute_query()) # Create a design with various types of providers design_with_providers = providers( database_connection=provide_database_connection, query_executor=injected_query_executor ) # The 'design_with_providers' has the 'provide_database_connection' function and # 'injected_query_executor' Injected instance registered as providers for 'database_connection' # and 'query_executor', respectively, along with their code locations.
This approach consolidates the registration of dependencies, streamlining the setup process and enhancing the traceability and debuggability of the system components.
- pinjected.reload(*targets: str)¶
A stub marker decorator for pinjected to reload the target function on console run. :param targets: :return: