Decorator type based dependency injection
Project description
quickd
Decorator type-based dependency injection for Python
📦 Installation
The package quickd
supports Python >= 3.5. You can install it by doing:
$ pip install quickd
📜 Example
Here is a quick example:
from quickd import inject, factory
class Database:
pass
class PostgreSQL(Database):
def __str__(self):
return 'PostgreSQL'
class MySQL(Database):
def __str__(self):
return 'MySQL'
@inject
def print_database(database: Database):
return print(database)
@factory
def choose_database() -> Database:
return PostgreSQL()
print_database() # Prints: PostgreSQL
print_database(MySQL()) # Prints: MySQL
🚀 Usage
There are only 3 decorators that compose the whole framework
@factory
- Registers an instance for a specific type for later use with
@inject
- Is mandatory to annotate the function with the return type of the class that you want to inject later
- It is not dynamic, so the implementation can only be chosen once
from quickd import factory
@factory
def choose_database() -> Database:
return PostgreSQL()
@inject
- Injects dependencies to a function by matching its arguments types with what has been registered
- As you can see below, it also works with constructors
from quickd import inject
@inject
def print_database(database: Database):
return print(database)
class UserService:
@inject
def __init__(self, database: Database): pass
@service
- Registers a class to be later injectable without using
@factory
- It also applies
@inject
to its constructor
from quickd import service, inject
@service
class UserService:
def __init__(self):
self.users = ['Bob', 'Tom']
def all(self):
return self.users
def add(self, user):
self.users.append(user)
@inject
def get_users(service: UserService):
return service.all()
@inject
def add_user(service: UserService):
return service.add("Pol")
get_users() # ['Bob', 'Tom']
add_user()
get_users() # ['Bob', 'Tom', 'Pol']
👨🍳 Recipes
Here are some common solutions to scenarios you will face.
Interfaces
from abc import abstractmethod
from quickd import inject, factory
class UserRepository:
@abstractmethod
def save(self, user):
pass
@abstractmethod
def search(self, id):
pass
class UserCreator:
@inject
def __int__(self, repository: UserRepository):
self.repository = repository
def create(self, user):
self.repository.save(user)
class MySQLUserRepository(UserRepository):
def __int__(self, host, password):
self.sql = MySQLConnection(host, password)
def save(self, user):
self.sql.execute('INSERT ...')
def search(self, id):
self.sql.execute('SELECT ...')
@factory
def choose_user_repository() -> UserRepository: # Notice super class is being used
return MySQLUserRepository('user', '123')
Testing
Following the above example we can create a unit test mocking the persistance, which will make our tests easier and faster.
fake_user = {'id': 1, 'name': 'Tom'}
class FakeUserRepository(UserRepository):
def save(self, user):
assert user == fake_user
repository = FakeUserRepository()
user_creator = UserCreator(repository)
user_creator.create(fake_user)
Configuration
There are multiple ways to configure your classes. A simple approach is to use environment variables on your factory annotated methods.
import os
from quickd import factory
@factory
def choose_database() -> Database:
username = os.environ.get("POSTGRES_USER")
password = os.environ.get("POSTGRES_PASS")
return PostgreSQL(username, password)
🧠 Motivation
Dependency injection provides a great way to decouple your classes in order to improve testability and maintainability.
Frameworks like Spring or Symfony are loved by the community.
I will just add a parameter to the constructor and Spring will fill with a global instance of the class
These frameworks rely heavy on the type system, to know which class should go where.
From Python 3.5 we have the typing package. This addition allows us to have the dependency injection framework that Python deserves.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file quickd-0.1.1.tar.gz
.
File metadata
- Download URL: quickd-0.1.1.tar.gz
- Upload date:
- Size: 5.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.9.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 48a19f55eca9c56c74ef1e0b199ecfc1498ba358df139bd282b45d4675308728 |
|
MD5 | 8568cd6653cba78c9275a3d73d167d98 |
|
BLAKE2b-256 | 1d140109de9a82d95bf18be7d65d9769aa677e166aa3598e6dade740478e69bc |
File details
Details for the file quickd-0.1.1-py3-none-any.whl
.
File metadata
- Download URL: quickd-0.1.1-py3-none-any.whl
- Upload date:
- Size: 5.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.9.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9e61a6d5b6ef38b34479835e434de8efc84ed5a799c27a0cba7029fc43091509 |
|
MD5 | f422a04759b49c3aa2949aa9bb496fd0 |
|
BLAKE2b-256 | d35cc2de61a26053bc8d77327b0ba6f936ebd8ef313fb347568f067642031f79 |