Skip to content

Modules

Simple IOC container.

Classes:

Container
MissingDependencyError
InvalidRegistrationError
InvalidForwardReferenceError
MissingDependencyException
InvalidRegistrationException
InvalidForwardReferenceException
Scope

Misc Variables:

empty

Container

Provides dependency registration and resolution.

This is the main entrypoint of the Punq library. In normal scenarios users will only need to interact with this class.

Source code in punq/__init__.py
class Container:
    """Provides dependency registration and resolution.

    This is the main entrypoint of the Punq library. In normal scenarios users
    will only need to interact with this class.
    """

    def __init__(self):
        self.registrations = _Registry()
        self.register(Container, instance=self)
        self._singletons = {}

    def register(self, service, factory=empty, instance=empty, scope=Scope.transient, **kwargs):
        """Register a dependency into the container.

        Each registration in Punq has a "service", which is the key used for
        resolving dependencies, and either an "instance" that implements the
        service or a "factory" that understands how to create an instance on
        demand.

        Examples:
            If we have an object that is expensive to construct, or that
            wraps a resouce that must not be shared, we might choose to
            use a singleton instance.

            >>> import sqlalchemy
            >>> from punq import Container
            >>> container = Container()

            >>> class DataAccessLayer:
            ...     pass
            ...
            >>> class SqlAlchemyDataAccessLayer(DataAccessLayer):
            ...     def __init__(self, engine: sqlalchemy.engine.Engine):
            ...         pass
            ...
            >>> dal = SqlAlchemyDataAccessLayer(sqlalchemy.create_engine("sqlite:///"))
            >>> container.register(
            ...     DataAccessLayer,
            ...     instance=dal
            ... )
            <punq.Container object at 0x...>
            >>> assert container.resolve(DataAccessLayer) is dal

            If we need to register a dependency, but we don't need to
                abstract it, we can register it as concrete.

            >>> class FileReader:
            ...     def read (self):
            ...         # Assorted legerdemain and rigmarole
            ...         pass
            ...
            >>> container.register(FileReader)
            <punq.Container object at 0x...>
            >>> assert type(container.resolve(FileReader)) == FileReader

            In this example, the EmailSender type is an abstract class
            and SmtpEmailSender is our concrete implementation.

            >>> class EmailSender:
            ...     def send(self, msg):
            ...         pass
            ...
            >>> class SmtpEmailSender (EmailSender):
            ...     def send(self, msg):
            ...         print("Sending message via smtp")
            ...
            >>> container.register(EmailSender, SmtpEmailSender)
            <punq.Container object at 0x...>
            >>> instance = container.resolve(EmailSender)
            >>> instance.send("beep")
            Sending message via smtp
        """
        self.registrations.register(service, factory, instance, scope, **kwargs)
        return self

    def resolve_all(self, service, **kwargs):
        """Return all registrations for a given service.

        Some patterns require us to use multiple implementations of an
        interface at the same time.

        Examples:
            In this example, we want to use multiple Authenticator instances to
            check a request.

            >>> class Authenticator:
            ...     def matches(self, req):
            ...         return False
            ...
            ...     def authenticate(self, req):
            ...         return False
            ...
            >>> class BasicAuthenticator(Authenticator):
            ...     def matches(self, req):
            ...         head = req.headers.get("Authorization", "")
            ...         return head.startswith("Basic ")
            ...
            >>> class TokenAuthenticator(Authenticator):
            ...     def matches(self, req):
            ...         head = req.headers.get("Authorization", "")
            ...         return head.startswith("Bearer ")
            ...
            >>> def authenticate_request(container, req):
            ...     for authn in req.resolve_all(Authenticator):
            ...         if authn.matches(req):
            ...             return authn.authenticate(req)
        """
        context = self.registrations.build_context(service)

        return [self._build_impl(x, kwargs, context) for x in context.all_registrations(service)]

    def _build_impl(self, registration, resolution_args, context):
        """Instantiate the registered service."""
        spec = inspect.getfullargspec(registration.builder)
        target_args = spec.args

        args = _match_defaults(spec.args, spec.defaults)
        args.update({
            k: self._resolve_impl(v, resolution_args, context, args.get(k))
            for k, v in registration.needs.items()
            if k != "return" and k not in registration.args and k not in resolution_args
        })
        args.update(registration.args)

        if "self" in target_args:
            target_args.remove("self")
        condensed_resolution_args = {key: resolution_args[key] for key in resolution_args if key in target_args}
        args.update(condensed_resolution_args or {})

        result = registration.builder(**args)

        if registration.scope == Scope.singleton:
            self._singletons[registration.service] = result

        context[registration.service] = result

        return result

    def _resolve_impl(self, service_key, kwargs, context, default=None):
        context = self.registrations.build_context(service_key, context)

        if service_key in self._singletons:
            return self._singletons[service_key]

        if context.has_cached(service_key):
            return context[service_key]

        target = context.target(service_key)

        if target.is_generic_list():
            return self.resolve_all(target.generic_parameter)

        registration = target.next_impl()

        if registration is None and default is not None:
            return default

        if registration is None:
            raise MissingDependencyError("Failed to resolve implementation for " + str(service_key))

        if service_key in registration.needs.values():
            self._resolve_impl(service_key, kwargs, context)

        return self._build_impl(registration, kwargs, context)

    def resolve(self, service_key, **kwargs):
        """Build and return an instance of a registered service."""
        context = self.registrations.build_context(service_key)

        return self._resolve_impl(service_key, kwargs, context)

    def instantiate(self, service_key, **kwargs):
        """Instantiate an unregistered service."""
        registration = _Registration(
            service_key,
            Scope.transient,
            service_key,
            self.registrations._get_needs_for_ctor(service_key),
            {},
        )

        context = _ResolutionContext(service_key, [registration])

        return self._build_impl(registration, kwargs, context)

instantiate(service_key, **kwargs)

Instantiate an unregistered service.

Source code in punq/__init__.py
def instantiate(self, service_key, **kwargs):
    """Instantiate an unregistered service."""
    registration = _Registration(
        service_key,
        Scope.transient,
        service_key,
        self.registrations._get_needs_for_ctor(service_key),
        {},
    )

    context = _ResolutionContext(service_key, [registration])

    return self._build_impl(registration, kwargs, context)

register(service, factory=empty, instance=empty, scope=Scope.transient, **kwargs)

Register a dependency into the container.

Each registration in Punq has a "service", which is the key used for resolving dependencies, and either an "instance" that implements the service or a "factory" that understands how to create an instance on demand.

Examples:

If we have an object that is expensive to construct, or that wraps a resouce that must not be shared, we might choose to use a singleton instance.

>>> import sqlalchemy
>>> from punq import Container
>>> container = Container()
>>> class DataAccessLayer:
...     pass
...
>>> class SqlAlchemyDataAccessLayer(DataAccessLayer):
...     def __init__(self, engine: sqlalchemy.engine.Engine):
...         pass
...
>>> dal = SqlAlchemyDataAccessLayer(sqlalchemy.create_engine("sqlite:///"))
>>> container.register(
...     DataAccessLayer,
...     instance=dal
... )
<punq.Container object at 0x...>
>>> assert container.resolve(DataAccessLayer) is dal

If we need to register a dependency, but we don't need to abstract it, we can register it as concrete.

>>> class FileReader:
...     def read (self):
...         # Assorted legerdemain and rigmarole
...         pass
...
>>> container.register(FileReader)
<punq.Container object at 0x...>
>>> assert type(container.resolve(FileReader)) == FileReader

In this example, the EmailSender type is an abstract class and SmtpEmailSender is our concrete implementation.

>>> class EmailSender:
...     def send(self, msg):
...         pass
...
>>> class SmtpEmailSender (EmailSender):
...     def send(self, msg):
...         print("Sending message via smtp")
...
>>> container.register(EmailSender, SmtpEmailSender)
<punq.Container object at 0x...>
>>> instance = container.resolve(EmailSender)
>>> instance.send("beep")
Sending message via smtp
Source code in punq/__init__.py
def register(self, service, factory=empty, instance=empty, scope=Scope.transient, **kwargs):
    """Register a dependency into the container.

    Each registration in Punq has a "service", which is the key used for
    resolving dependencies, and either an "instance" that implements the
    service or a "factory" that understands how to create an instance on
    demand.

    Examples:
        If we have an object that is expensive to construct, or that
        wraps a resouce that must not be shared, we might choose to
        use a singleton instance.

        >>> import sqlalchemy
        >>> from punq import Container
        >>> container = Container()

        >>> class DataAccessLayer:
        ...     pass
        ...
        >>> class SqlAlchemyDataAccessLayer(DataAccessLayer):
        ...     def __init__(self, engine: sqlalchemy.engine.Engine):
        ...         pass
        ...
        >>> dal = SqlAlchemyDataAccessLayer(sqlalchemy.create_engine("sqlite:///"))
        >>> container.register(
        ...     DataAccessLayer,
        ...     instance=dal
        ... )
        <punq.Container object at 0x...>
        >>> assert container.resolve(DataAccessLayer) is dal

        If we need to register a dependency, but we don't need to
            abstract it, we can register it as concrete.

        >>> class FileReader:
        ...     def read (self):
        ...         # Assorted legerdemain and rigmarole
        ...         pass
        ...
        >>> container.register(FileReader)
        <punq.Container object at 0x...>
        >>> assert type(container.resolve(FileReader)) == FileReader

        In this example, the EmailSender type is an abstract class
        and SmtpEmailSender is our concrete implementation.

        >>> class EmailSender:
        ...     def send(self, msg):
        ...         pass
        ...
        >>> class SmtpEmailSender (EmailSender):
        ...     def send(self, msg):
        ...         print("Sending message via smtp")
        ...
        >>> container.register(EmailSender, SmtpEmailSender)
        <punq.Container object at 0x...>
        >>> instance = container.resolve(EmailSender)
        >>> instance.send("beep")
        Sending message via smtp
    """
    self.registrations.register(service, factory, instance, scope, **kwargs)
    return self

resolve(service_key, **kwargs)

Build and return an instance of a registered service.

Source code in punq/__init__.py
def resolve(self, service_key, **kwargs):
    """Build and return an instance of a registered service."""
    context = self.registrations.build_context(service_key)

    return self._resolve_impl(service_key, kwargs, context)

resolve_all(service, **kwargs)

Return all registrations for a given service.

Some patterns require us to use multiple implementations of an interface at the same time.

Examples:

In this example, we want to use multiple Authenticator instances to check a request.

>>> class Authenticator:
...     def matches(self, req):
...         return False
...
...     def authenticate(self, req):
...         return False
...
>>> class BasicAuthenticator(Authenticator):
...     def matches(self, req):
...         head = req.headers.get("Authorization", "")
...         return head.startswith("Basic ")
...
>>> class TokenAuthenticator(Authenticator):
...     def matches(self, req):
...         head = req.headers.get("Authorization", "")
...         return head.startswith("Bearer ")
...
>>> def authenticate_request(container, req):
...     for authn in req.resolve_all(Authenticator):
...         if authn.matches(req):
...             return authn.authenticate(req)
Source code in punq/__init__.py
def resolve_all(self, service, **kwargs):
    """Return all registrations for a given service.

    Some patterns require us to use multiple implementations of an
    interface at the same time.

    Examples:
        In this example, we want to use multiple Authenticator instances to
        check a request.

        >>> class Authenticator:
        ...     def matches(self, req):
        ...         return False
        ...
        ...     def authenticate(self, req):
        ...         return False
        ...
        >>> class BasicAuthenticator(Authenticator):
        ...     def matches(self, req):
        ...         head = req.headers.get("Authorization", "")
        ...         return head.startswith("Basic ")
        ...
        >>> class TokenAuthenticator(Authenticator):
        ...     def matches(self, req):
        ...         head = req.headers.get("Authorization", "")
        ...         return head.startswith("Bearer ")
        ...
        >>> def authenticate_request(container, req):
        ...     for authn in req.resolve_all(Authenticator):
        ...         if authn.matches(req):
        ...             return authn.authenticate(req)
    """
    context = self.registrations.build_context(service)

    return [self._build_impl(x, kwargs, context) for x in context.all_registrations(service)]

InvalidForwardReferenceError

Bases: InvalidForwardReferenceException

Raised when a registered service has a forward reference that can't be resolved.

Examples:

In this example, we register a service with a string as a type annotation. When we try to inspect the constructor for the service we fail with an InvalidForwardReferenceError

>>> from dataclasses import dataclass
>>> from punq import Container
>>> @dataclass
... class Client:
...     dep: 'Dependency'
>>> container = Container()
>>> container.register(Client)
Traceback (most recent call last):
...
punq.InvalidForwardReferenceError: name 'Dependency' is not defined

This error can be resolved by first registering a type with the name 'Dependency' in the container.

>>> class Dependency:
...     pass
...
>>> container.register(Dependency)
<punq.Container object at 0x...>
>>> container.register(Client)
<punq.Container object at 0x...>
>>> container.resolve(Client)
Client(dep=<punq.Dependency object at 0x...>)

Alternatively, we can register a type using the literal key 'Dependency'.

>>> class AlternativeDependency:
...     pass
...
>>> container = Container()
>>> container.register('Dependency', AlternativeDependency)
<punq.Container object at 0x...>
>>> container.register(Client)
<punq.Container object at 0x...>
>>> container.resolve(Client)
Client(dep=<punq.AlternativeDependency object at 0x...>)
Source code in punq/__init__.py
class InvalidForwardReferenceError(InvalidForwardReferenceException):
    """Raised when a registered service has a forward reference that can't be resolved.

    Examples:
        In this example, we register a service with a string as a type annotation.
        When we try to inspect the constructor for the service we fail with an
        InvalidForwardReferenceError

        >>> from dataclasses import dataclass
        >>> from punq import Container
        >>> @dataclass
        ... class Client:
        ...     dep: 'Dependency'
        >>> container = Container()
        >>> container.register(Client)
        Traceback (most recent call last):
        ...
        punq.InvalidForwardReferenceError: name 'Dependency' is not defined


        This error can be resolved by first registering a type with the name
        'Dependency' in the container.

        >>> class Dependency:
        ...     pass
        ...
        >>> container.register(Dependency)
        <punq.Container object at 0x...>
        >>> container.register(Client)
        <punq.Container object at 0x...>
        >>> container.resolve(Client)
        Client(dep=<punq.Dependency object at 0x...>)


        Alternatively, we can register a type using the literal key 'Dependency'.

        >>> class AlternativeDependency:
        ...     pass
        ...
        >>> container = Container()
        >>> container.register('Dependency', AlternativeDependency)
        <punq.Container object at 0x...>
        >>> container.register(Client)
        <punq.Container object at 0x...>
        >>> container.resolve(Client)
        Client(dep=<punq.AlternativeDependency object at 0x...>)

    """

    pass

InvalidForwardReferenceException

Bases: Exception

Deprecated alias for InvalidForwardReferenceError.

Source code in punq/__init__.py
class InvalidForwardReferenceException(Exception):
    """Deprecated alias for InvalidForwardReferenceError."""

    pass

InvalidRegistrationError

Bases: InvalidRegistrationException

Raised when a registration would result in an unresolvable service.

Source code in punq/__init__.py
class InvalidRegistrationError(InvalidRegistrationException):
    """Raised when a registration would result in an unresolvable service."""

    pass

InvalidRegistrationException

Bases: Exception

Deprecated alias for InvalidRegistrationError.

Source code in punq/__init__.py
class InvalidRegistrationException(Exception):
    """Deprecated alias for InvalidRegistrationError."""

    pass

MissingDependencyError

Bases: MissingDependencyException

Raised when a service, or one of its dependencies, is not registered.

Examples:

>>> import punq
>>> container = punq.Container()
>>> container.resolve("foo")
Traceback (most recent call last):
punq.MissingDependencyError: Failed to resolve implementation for foo
Source code in punq/__init__.py
class MissingDependencyError(MissingDependencyException):
    """Raised when a service, or one of its dependencies, is not registered.

    Examples:
        >>> import punq
        >>> container = punq.Container()
        >>> container.resolve("foo")
        Traceback (most recent call last):
        punq.MissingDependencyError: Failed to resolve implementation for foo
    """

    pass

MissingDependencyException

Bases: Exception

Deprecated alias for MissingDependencyError.

Source code in punq/__init__.py
class MissingDependencyException(Exception):
    """Deprecated alias for MissingDependencyError."""

    pass

Scope

Bases: Enum

Controls the lifetime of resolved objects.

Attributes:

Name Type Description
transient

create a fresh instance for each resolve call

singleton

re-use a single instance for every resolve call

Source code in punq/__init__.py
class Scope(Enum):
    """Controls the lifetime of resolved objects.

    Attributes:
        transient: create a fresh instance for each `resolve` call
        singleton: re-use a single instance for every `resolve` call
    """

    transient = 0
    singleton = 1