Skip to content

Build your first Tenchi app

Create a working application, call its API, follow one request through the code, and change its behavior in a plain async function.

Requirements

Tenchi requires Python 3.12 or newer. The commands in this guide use uv to create the environment and run the application.

Create the application

uvx tenchi new my_app
cd my_app
uv sync

The generated application already contains a todos feature and SQLite persistence. Start it:

uv run tenchi dev

Call the API

From another terminal, create and list todos:

curl -i \
  -H 'content-type: application/json' \
  -d '{"title":"Buy milk"}' \
  http://127.0.0.1:8000/todos

curl http://127.0.0.1:8000/todos

The create operation returns status 201, a validated Todo body, and a Location header. The list operation returns the saved todo. Restart the server and list again to confirm that SQLite persisted it.

Open http://127.0.0.1:8000/docs to inspect and call the same API through Swagger UI.

Follow the request through four files

The create operation has four application-facing parts:

PathResponsibility
app/features/todos/schemas.pyDefines the validated request and response data
app/features/todos/contracts.pyDeclares POST /todos and its HTTP behavior
app/features/todos/use_cases/create_todo.pyImplements the application behavior
app/features/todos/routes.pyBinds the contract to the use case

The contract and route handle the boundary. The use case stays ordinary Python:

async def create_todo(request: CreateTodo, context: AppContext) -> Todo:
    return await context.todos.create(title=request.title)

context.todos is a TodoRepository port owned by the application. The running server supplies a SQLite adapter; the direct use-case test supplies a memory adapter. The use case does not need to know which one it received.

The rest of the scaffold can wait

The generated project also includes server wiring, the SQLite and memory adapters, and an HTTP test. You do not need to understand or edit them to work on this operation.

Change the behavior

Trim surrounding whitespace before saving a title. In app/features/todos/use_cases/create_todo.py, change the repository call:

 async def create_todo(request: CreateTodo, context: AppContext) -> Todo:
-    return await context.todos.create(title=request.title)
+    return await context.todos.create(title=request.title.strip())

Update the direct test in app/features/todos/tests/test_create_todo.py so it proves the new behavior:

-    todo = await create_todo(CreateTodo(title="Buy milk"), context)
+    todo = await create_todo(CreateTodo(title="  Buy milk  "), context)

     assert todo.title == "Buy milk"

Run that focused test:

uv run pytest app/features/todos/tests/test_create_todo.py

The test calls the use case directly with a memory repository. It does not start an HTTP server or open SQLite.

Check the application

uv run tenchi check

tenchi check runs the generated project's formatting, linting, type, behavior, architecture, and boundary checks. The command reports every failed step so one run gives you the complete repair list.

Your first change touched one plain function and one focused test. The contract, route, persistence wiring, OpenAPI, and HTTP behavior remained aligned without requiring changes.

Continue from the core