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 Injected class 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:

Injected instances can be manipulated and combined in several ways to build complex dependency structures.

  1. map: Transform the result of an Injected instance.

    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]
    
  2. zip/mzip: Combine multiple Injected instances.

    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)
    
  3. dict/list: Create a dictionary or list from multiple Injected instances.

    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 Injected instances, they provide powerful ways to manage and utilize dependencies within your applications.

add_dynamic_dependencies(*deps: str | set[str])
and_then_injected(other: Injected[Callable[[T], U]]) Injected[Callable[[Any], U]]
apply_injected_function(other: Injected[Callable[[T], U]]) Injected[U]
static async_gather(*srcs: Injected[Awaitable])
static bind(_target_function_, **kwargs_mapping: str | type | Callable | Injected) InjectedFunction
static by_name(name: str)
property complete_dependencies: Set[str]
static conditional(condition: Injected[bool], true_case: Injected, false_case: Injected)
static conditional_preparation(condition: Injected[bool], preparation: Injected, utilization: Injected)
abstract dependencies() Set[str]
desync()
static dict(**kwargs: Injected) Injected[Dict]
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.

static ensure_injected(data: Injected | DelegatedVar)
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 list(*srcs: Injected | DelegatedVar)
map(f: Callable[[T], U]) Injected[U]
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 mzip(*srcs: Injected)
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)
static tuple(*srcs: Injected)
static wrap_injected_if_not(tgt: Injected | DelegatedVar | Any)
zip(other: Injected[U]) Injected[Tuple[T, U]]
pinjected.classes(**kwargs)

Binds classes to a new Design instance, 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 Design instance 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 injected decorator automates dependency injection, transforming a target (string, class, or callable) into an Injected instance. 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 Injected instance, proxy, or wrapped entity with dependencies injected, contingent on the nature of tgt.

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, dependency1 is a positional-only parameter and treated as a dependency to be automatically injected. On the other hand, normal_param is 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 as instance) is a decorator that creates an Injected instance from a function. This function essentially converts a regular function that returns an instance into an Injected type 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 Injected that wraps the provided function.

Return type:

Injected

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 Injected instance handling the complexities of resolving and managing dependencies.

pinjected.instances(**kwargs)

Registers multiple concrete instances or values to a new Design instance 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 DelegatedVar or Injected instances 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 DelegatedVar or Injected, raising an assertion error if this case is encountered to prevent misuse.

Returns:

Design

A Design instance with the concrete objects or values bound and their code locations registered.

Raises:

AssertionError

If any value in kwargs is an instance of DelegatedVar or Injected, 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: