No project description provided
Project description
ditto: dependency injection tool
ditto is a simple and lightweight dependency injection tool for Python, with support for nullable arguments and parent class registration.
Features
- Easy-to-use decorators for dependency injection
- Supports class and instance-based services
- Sync and Async support
- Nullable argument handling
- Parent class registration
Installation
pip install pyditto
Usage
Basic Usage
Declare your dependencies to ditto on application start:
import ditto as di
class Pokemon:
def attack(self) -> str:
pass
class FirePokemon(Pokemon):
def attack(self) -> str:
return 'Ember ! 🔥'
di.register(FirePokemon) # register as a class
di.register(FirePokemon()) # or as an object
Then you can inject your dependency into your class:
@di.inject
class Team:
charmander: FirePokemon
def battle(self):
self.charmander.attack()
team = Team()
team.battle() # 'Ember ! 🔥'
Or into an isolated function within your class:
class Team:
@di.inject
def battle(self, charmander: FirePokemon):
charmander.attack()
team = Team()
team.battle() # 'Ember ! 🔥'
Or just within a bare function:
@di.inject
def battle(charmander: FirePokemon):
charmander.attack()
battle() # 'Ember ! 🔥'
Advanced Usage
Type Lookup in Registry
ditto uses the lowercase name of the class as the key in its registry:
di.register(Charmander)
# These will all retrieve the Charmander instance:
fire_pokemon = di.ServiceRegistry.get_instance().get('firepokemon')
charmander = di.ServiceRegistry.get_instance().get('charmander')
This is particularly useful to understand when working with optional dependencies or when overriding parent class implementations.
Asynchronous Support
ditto supports dependency injection for asynchronous functions:
import asyncio
class AsyncPokemon:
async def attack(self) -> str:
await asyncio.sleep(1)
return "Async attack!"
di.register(AsyncPokemon)
@di.inject
async def async_battle(pokemon: AsyncPokemon):
result = await pokemon.attack()
print(result)
asyncio.run(async_battle()) # Output: Async attack!
Nullable Argument Handling
ditto supports nullable arguments, allowing you to specify optional dependencies:
from typing import Optional
@di.inject
def train_pokemon(charmander: FirePokemon, optional_pokemon: Optional[Pokemon] = None):
charmander.attack()
if optional_pokemon:
optional_pokemon.attack()
else:
print("No optional Pokemon available")
# This will work even if Pokemon is not registered
train_pokemon()
# You can also explicitly pass None for optional arguments
train_pokemon(optional_pokemon=None)
In this example:
charmanderis a required dependency and must be registered.optional_pokemonis an optional dependency. If it's not registered,Nonewill be injected.
Note on Type Registration for Optional Parameters:
For both Pokemon and Optional[Pokemon], ditto uses the same key ('pokemon') in its registry. The Optional wrapper doesn't affect the registration key; it only changes how ditto handles the case when the service isn't found.
Parent Class Registration
When you register a service class, ditto automatically registers it under its immediate parent class as well:
class Pokemon:
pass
class FirePokemon(Pokemon):
pass
class Charmander(FirePokemon):
pass
di.register(Charmander)
@di.inject
def train(fire_pokemon: FirePokemon, charmander: Charmander):
print(f"Training: {fire_pokemon.__class__.__name__}, {charmander.__class__.__name__}")
train() # Output: Training: Charmander, Charmander
@di.inject
def catch(pokemon: Pokemon):
print(f"Trying to catch: {pokemon.__class__.__name__}")
catch() # This will raise a ValueError: Service 'pokemon' not found.
In this example:
- Registering
Charmanderalso makes it available whenFirePokemon(its immediate parent class) is requested. - However,
Pokemon(the grandparent class) is not automatically registered. - This allows for one level of inheritance in dependency injection.
Error Handling and Common Pitfalls
-
Missing dependencies: If a required dependency is not registered, ditto will raise a
ValueError:@di.inject def use_unregistered(service: NotInPokedexPokemon): pass use_unregistered() # Raises ValueError: Service 'notinpokedexpokemon' not found.
-
Circular dependencies: ditto doesn't automatically resolve circular dependencies. Be cautious when designing your dependency graph.
Multiple Registrations
When multiple classes are registered for the same type, ditto will use the most recently registered one:
class Pikachu(Pokemon):
pass
class Charmander(Pokemon):
pass
di.register(Pikachu)
di.register(Charmander)
@di.inject
def catch(pokemon: Pokemon):
print(f"Caught a {pokemon.__class__.__name__}")
catch() # Output: Caught a Charmander
Best Practices
- Register dependencies at the application's entry point.
- Use interfaces (abstract base classes) for better decoupling.
- Avoid registering too many concrete implementations to keep your dependency graph simple.
Managing Registered Services
To unregister a service:
# Unregister a service
ServiceRegistry.get_instance()._services.pop('pokemon', None)
Limitations
- ditto doesn't support automatic constructor injection. Parameters must be explicitly annotated.
- Circular dependencies are not automatically resolved and may cause issues if not carefully managed.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyditto-0.1.3.tar.gz.
File metadata
- Download URL: pyditto-0.1.3.tar.gz
- Upload date:
- Size: 4.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.12.0 Linux/5.15.146.1-microsoft-standard-WSL2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58a75a0cca5ef4efd84e7c7e7d04a628685253d8cf844b97d6e376ce963e5aab
|
|
| MD5 |
3c32fa61f79ee09e06d80869b249e279
|
|
| BLAKE2b-256 |
59a807374045eda7d4fc019b520381c6fc323b8adfda2263bfe36ef433151b08
|
File details
Details for the file pyditto-0.1.3-py3-none-any.whl.
File metadata
- Download URL: pyditto-0.1.3-py3-none-any.whl
- Upload date:
- Size: 5.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.12.0 Linux/5.15.146.1-microsoft-standard-WSL2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb3a7fc633e737ca61f15e5df86a88cd97113d9c846d3eb695358a7841b3257a
|
|
| MD5 |
afe2e5a92614191867b5fdc41d39c896
|
|
| BLAKE2b-256 |
17a144c4b13c126ce67fdeb4915711554e04c08353db803be89a59a0ea859fee
|