Getting Started: 5-Minute Tutorial¶
Welcome to pico-ioc! 🚀
This guide will get you installed and running your first dependency-injected application in less than 5 minutes. We'll go from an empty file to a fully-wired application.
By the end of this tutorial, you will understand the three most fundamental APIs in pico-ioc:
@component: The decorator that registers your classes with the container.init(): The function that scans your project and builds the container.container.get(): The method you use to ask the container for your final, fully-wired object.
1. Installation¶
pico-ioc requires Python 3.11 or newer.
You can install it directly from PyPI using pip:
This core package includes all the necessary logic. For advanced, tree-based configuration from YAML files (covered in the User Guide), you can install the optional dependency:
2. The 5-Minute Tutorial¶
Let's build a simple "Hello, World!" application.
Step 1: Define Your Components¶
First, create a file named app.py. We'll define two classes:
GreeterService: A simple service with one job: providing a greeting.App: Our main application class, which depends on theGreeterService.
The @component decorator is the core of pico-ioc. It's a marker that tells the container: "Find this class, scan its dependencies, and make it available for injection."
# app.py
from pico_ioc import component
@component
class GreeterService:
"""A service that provides a greeting."""
def greet(self) -> str:
return "Hello, pico-ioc!"
@component
class App:
"""Our main application class."""
# pico-ioc will see this and inject GreeterService
def __init__(self, greeter: GreeterService):
self.greeter = greeter
def run(self):
# Use the injected service
print(self.greeter.greet())
What's happening here?¶
@componentonGreeterServicetellspico-iocto register this class. It has no dependencies.@componentonAppalso registers it.def __init__(self, greeter: GreeterService):This is the magic.pico-iocreads your constructor's type hints. It sees thatApprequires an instance ofGreeterServiceand understands that it must provide one when building anApp.
Step 2: Initialize the Container¶
Now, at the bottom of your app.py, we need to tell pico-ioc to find these components. We do this by calling init().
init() is the function that builds the container. Its most important argument is modules, which tells it where to look for @component decorators.
# app.py
from pico_ioc import component, init
# ... (GreeterService and App classes from above) ...
if __name__ == "__main__":
# Scan the current module (this file)
# and find all @component decorators
container = init(modules=[__name__])
What's happening here?¶
if __name__ == "__main__":ensures this code only runs when the script is executed directly.container = init(modules=[__name__]): This line does all the work:- It scans the module specified (in this case,
__name__refers to the current file,app.py). - It finds
GreeterServiceandApp. - It validates their dependencies (sees that
AppneedsGreeterServiceand thatGreeterServiceis available). - It returns a
PicoContainerinstance, which is now a fully-configured factory ready to build your components.
Step 3: Get Your Component and Run¶
The container object now knows how to build App and its entire dependency tree. We can ask for the final App instance using container.get() and then call its run() method.
# app.py
from pico_ioc import component, init
# ... (GreeterService and App classes from above) ...
if __name__ == "__main__":
container = init(modules=[__name__])
# Ask the container for the 'App' instance
app = container.get(App)
# Run the application
app.run()
What's happening here?¶
When you call container.get(App), pico-ioc performs a "depth-first" resolution:
- It sees you want an
App. - It checks
App's dependencies and sees it needs aGreeterService. - It checks
GreeterService's dependencies (it has none). - It creates the
GreeterServiceinstance (and caches it for future use). - It creates the
Appinstance, injecting theGreeterServiceinstance into its constructor. - It returns the fully-wired
Appinstance to you.
3. Putting It All Together¶
Here is the complete, runnable app.py file.
# app.py
from pico_ioc import component, init
# 1. Define components
@component
class GreeterService:
"""A service that provides a greeting."""
def greet(self) -> str:
return "Hello, pico-ioc!"
@component
class App:
"""Our main application class."""
# 2. Define dependencies via type hints
def __init__(self, greeter: GreeterService):
self.greeter = greeter
def run(self):
# Use the injected service
print(self.greeter.greet())
# 3. Initialize and run
if __name__ == "__main__":
# Scans this module and finds App/GreeterService
container = init(modules=[__name__])
# Resolves App and its GreeterService dependency
app = container.get(App)
app.run()
Run it from your terminal:
4. Next Steps¶
Congratulations! You've successfully built your first pico-ioc application.
You've learned the three core APIs: @component to register classes, init() to build the container, and container.get() to resolve components.
Now you're ready to explore the core features in the User Guide: - User Guide: ./user-guide/README.md
5. Tips and Troubleshooting¶
- Make sure every class you want the container to build is annotated with
@component. - Use type hints on
__init__parameters sopico-ioccan understand dependencies. - Ensure you pass the correct modules to
init(modules=[...])where your components are defined. - If you see resolution errors, check for:
- Missing
@componenton a dependency class. - Typos in type hints.
- Circular dependencies between components.