Lesson 4 — your first application¶
What you will build¶
The smallest application that is still an application: a service, one registration, a lifetime, and one resolution. Lesson 1 printed what the runtime is made of; this is the shape you will repeat in every application you write from here.
What you need¶
The setup from lesson 1: Python 3.10 or newer, and Xyberos. This lesson needs no optional extra.
The code¶
"""Lesson 4 — your first application.
Run it with:
python -m learn_fundamentals.fundamentals04_your_first_application
"""
from __future__ import annotations
import asyncio
from xyberos import App
class Greeter:
"""A service: an ordinary class that knows nothing about Xyberos."""
def greet(self, name: str) -> str:
"""Return a greeting."""
return f"Hello, {name}!"
async def main() -> None:
app = App()
app.provide(Greeter)
async with app.lifespan():
greeter = await app.resolve(Greeter)
print(greeter.greet("world"))
print("state:", app.state.value)
if __name__ == "__main__":
asyncio.run(main())
How to run it¶
PYTHONPATH is only there because the example lives in this repository's examples/ directory instead
of in an installed package. In your own project the file is yours, so the second line becomes
python app.py.
What it prints¶
The lines that matter¶
| Line | What it does |
|---|---|
app = App() |
creates the application: a composition root, not a container you look things up in |
app.provide(Greeter) |
registers Greeter as a service, keyed by its own class |
async with app.lifespan(): |
starts the application, and stops it when the block ends |
await app.resolve(Greeter) |
asks the application for the service |
app.state.value |
ready, because that line runs inside the lifespan |
Two of those are worth pausing on.
The service knows nothing about the framework. Greeter is an ordinary class: no base class, no
decorator, no import. Everything the runtime needs is in the composition root — App(),
app.provide, app.resolve.
resolve is asynchronous. The await is not decoration: the runtime may have to construct the
service, and construction is allowed to be asynchronous.
Common mistakes¶
- Forgetting
await.app.resolve(Greeter)without it returns a coroutine, and the next line fails with'coroutine' object has no attribute 'greet'. - Resolving before the lifespan starts. Outside
async with app.lifespan():you getLifecycleStateError, and the state isnewrather thanready. - Looking for a container. There is no
get("Greeter"), and no lookup by name anywhere. A service that needs another service declares it in its constructor — that is lesson 8.
Exercise¶
Make the greeting say your name instead of world.
Show the answer
Change one line:"""Lesson 4, exercise — the greeting says your name.
The answer to the lesson's exercise, kept as a file rather than as a block on the page: the page
includes it by path and the suite runs it, so the answer cannot rot while the lesson stays correct.
Nothing is new here except one argument.
"""
from __future__ import annotations
import asyncio
from xyberos import App
class Greeter:
"""A service: an ordinary class that knows nothing about the runtime."""
def greet(self, name: str) -> str:
return f"Hello, {name}!"
async def main() -> None:
"""Run the exercise's version of lesson 4."""
app = App()
app.provide(Greeter)
async with app.lifespan():
greeter = await app.resolve(Greeter)
print(greeter.greet("Ada"))
print("state:", app.state.value)
if __name__ == "__main__":
asyncio.run(main())
Next¶
Lesson 5 runs an application the way a deployment does — with a settings file, a configuration environment, and a shutdown path. Until it is published, the roadmap shows where each lesson stands.