Your first API

Build a small API with a module, a controller and an injected provider.

Vous consultez la version 0.0.1. La dernière est 0.0.2.

Voir 0.0.2

You will build a two-endpoint API and see dependency injection resolve a provider without registering it anywhere.

You need Python 3.12+ and Bustan 0.0.1.

Install#

pip install bustan==0.0.1

A provider#

Providers hold behaviour. Mark one injectable:

# app/tasks/service.py
from bustan import Injectable

@Injectable()
class TaskService:
    def __init__(self) -> None:
        self._tasks: dict[int, str] = {}
        self._next_id = 1

    def add(self, title: str) -> int:
        task_id = self._next_id
        self._tasks[task_id] = title
        self._next_id += 1
        return task_id

    def all(self) -> dict[int, str]:
        return dict(self._tasks)

A controller#

Controllers translate HTTP to calls on providers, and should contain almost no logic:

# app/tasks/controller.py
from bustan import Controller, Get, Post, Body
from .service import TaskService

@Controller("/tasks")
class TaskController:
    def __init__(self, tasks: TaskService) -> None:
        self.tasks = tasks

    @Get("/")
    async def list_tasks(self) -> dict[int, str]:
        return self.tasks.all()

    @Post("/")
    async def create(self, title: str = Body()) -> dict[str, int]:
        return {"id": self.tasks.add(title)}

TaskService arrives because the constructor annotation asks for it.

A module#

A module declares what it owns:

# app/tasks/module.py
from bustan import Module
from .controller import TaskController
from .service import TaskService

@Module(controllers=[TaskController], providers=[TaskService])
class TaskModule:
    pass

And the root module composes them:

# app/main.py
from bustan import Module, Bustan
from .tasks.module import TaskModule

@Module(imports=[TaskModule])
class AppModule:
    pass

app = Bustan(AppModule)

Run it#

uvicorn app.main:app --reload
$ curl -s localhost:8000/tasks/
{}
$ curl -s -X POST localhost:8000/tasks/ -d '{"title":"write docs"}' -H 'content-type: application/json'
{"id":1}

What you have#

  • A provider the container constructs once and shares
  • A controller that never constructs its own dependencies
  • A module boundary you can move without touching either

Next#

  • Share a provider between modules — see the how-to guides
  • Understand how the container resolves things — see Explanation