Skip to main content

Python Dependency Injection made simple so you can focus on creating amazing code.

Project description

Bevy

Bevy makes using Dependency Injection in Python a breeze so that you can focus on creating amazing code.

Installation

pip install bevy>2.0.0

Dependency Injection

Put simply, Dependency Injection is a design pattern where the objects that your code depends on are instantiated by the caller. Those dependencies are then injected into your code when it is run. This promotes loosely coupled code where your code doesn't require direct knowledge of what objects it depends on or how to create them. Instead, your code declares what interface it expects and an outside framework handles the work of creating objects with the correct interface.

Interfaces

Python doesn't have an actual interface implementation like many other languages. Class inheritance, however, can be used in a very similar way since subclasses will likely have the same fundamental interface as their base class.

Why Do I Care?

Dependency Injection and its reliance on abstract interfaces makes your code easier to maintain:

  • Changes can be made without needing to alter implementation details in unrelated code, so long as the interface isn’t modified in a substantial way.
  • Tests can provide mock implementations of dependencies without needing to rely on patching or duck typing. They can provide the mock to Bevy which can then ensure it is used when necessary.

How It Works

Bevy makes use of Python's contextvars to store a repository object global to the context. You can then use bevy.inject and bevy.dependency to inject dependencies into functions and classes.

The dependency function returns a descriptor that can detect the type hints that have been set on a class attribute. It can then create and return a dependency that matches the type hints.

The inject decorator allows you to use the dependecy function to inject dependencies into functions. Each parameter that needs to be injected should have its default value set to dependency() and then the inject decorator will intelligently handle injecting those dependencies into the function arguments based on the parameter type hints. This injection is compatible with positional only, positional, keyword, and keyword only arguments.

Example

from bevy import inject, dependency

class Thing:
    ...

class Example:
    foo: Thing = dependency()

@inject
def example(foo: Thing = dependency()):
    ...

Each dependency once created is stored in the context global repository's cache to be reused by other functions and classes that depend on them. This sharing is very useful for database connections, config files, authenticated API sessions, etc.

Setting Values In the Repository

It is possible to provide a value to the context's global repository that will be used as a dependency.You can get the current repository using the bevy.get_repository function, you can then use that repository's set method to assign an instance to a given key.

from bevy import get_repository

get_repository().set(Thing, Thing("foobar"))

This would cause anything that has a dependency for Thing to have the instance Thing("foobar") injected.

The set method will return an empty bevy.options.Value object if it succeeds in setting the value into the cache or will return a bevy.options.Null object if it fails.

Getting Values From the Repository

It is possible to get values directly from the repository using its get method.

from bevy import get_repository

thing = get_repository().get(Thing)

If an instance of Thing is not found in the cache it will create an instance, save it in the cache for future use, and then return it.

It is possible to get a value from the cache without creating an instance if it's not found using the find method. This method returns an Option type that can either be your value wrapped in a Value type or a Null type if the value was not found. You can use the value_or method to get your value or a default if it doesn't exist.

thing = get_repository().find(Thing).value_or(None)

It is also possible to use match/case to unwrap the value with more flexibility.

from bevy.options import Value, Null

match get_repository().find(Thing):
    case Value(value):
        thing = value
    case Null():
        raise Exception("Could not find an instance of Thing")

Dependency Providers

To make Bevy as flexible as possible, it has dependency providers. These are classes that can be registered with the repository to cache value instances, look up cached instances using a key type, and create new instances for a key type.

The default repository type has two dependency providers: an annotated provider and a type provider.

  • The Type Provider handles key types that are class types. So when a dependency annotation is Thing, for example, the type provider will handle looking through its cache for an instance of Thing and creating an instance if it is not found.
  • The Annotated Provider handles key types that are instances of typing.Annotated. It works very similarly to the type provider except it allows you to provide an annotation. This is helpful if you have multiple instances of the same type that need to exist together. You could have Annotated[Thing, "testA"] and Annotated[Thing, "testB"], both of them would be able to point to distinct instances of Thing in the same repository cache.

It is possible to add new providers to the repository using it's add_providers method which takes any number of provider types.

It is also possible to subclass bevy.Repository, provide a custom factory class method that returns an instance of Repository with whatever default providers you want set. Just call Repository.set_repository with an instance of the new repository type.

Project details


Release history Release notifications | RSS feed

This version

2.0.2

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bevy-2.0.2.tar.gz (12.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bevy-2.0.2-py3-none-any.whl (14.3 kB view details)

Uploaded Python 3

File details

Details for the file bevy-2.0.2.tar.gz.

File metadata

  • Download URL: bevy-2.0.2.tar.gz
  • Upload date:
  • Size: 12.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.6.1 CPython/3.10.13 Linux/5.15.0-1050-azure

File hashes

Hashes for bevy-2.0.2.tar.gz
Algorithm Hash digest
SHA256 e80ba57ec85acf393836b73c21800cbc4c8af827b61ed7d3d7ac99d5d3d53646
MD5 693566cf65fd3d597d4e09243bdee010
BLAKE2b-256 aeb06a1176559c96d7d57c7a9b97b9c7f5e1cbe3ed039b60d0c0e696836b4fb0

See more details on using hashes here.

File details

Details for the file bevy-2.0.2-py3-none-any.whl.

File metadata

  • Download URL: bevy-2.0.2-py3-none-any.whl
  • Upload date:
  • Size: 14.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.6.1 CPython/3.10.13 Linux/5.15.0-1050-azure

File hashes

Hashes for bevy-2.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 bbe5c87027a43fef958698b7bf3bd7d6fbc45b08cd572debf625f2f61651873d
MD5 8a00a227531e7c76bb8e0ebaf1993064
BLAKE2b-256 2bc5aeea84ef41db8d9b930ae78b90cab39730a064246768704c0a1258564fb7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page