Bake your dependencies stupidly simple!
Project description
🍰 The little DI framework that tastes like a cake. 🍰
Documentation: https://fresh-bakery.readthedocs.io/en/latest/
Fresh Bakery
Fresh bakery is a lightweight [Dependency Injection][DI] framework/toolkit, which is ideal for building object dependencies in Python.
It is [nearly] production-ready, and gives you the following:
- A lightweight, stupidly simple DI framework.
- Fully asynchronous, no synchronous mode.
- Any async backends compatible (
asyncio
,trio
). - Zero dependencies.
Mypy
compatible (no probably need for# type: ignore
).FastAPI
fully compatible.Litestar
compatible.Pytest
fully compatible (Fresh Bakery encourages the use ofpytest
).- Ease of testing.
- Easily extended (contribution is welcome).
Requirements
Python 3.6+
Installation
$ pip3 install fresh-bakery
Examples
Raw example
In this example, you can see how to create a specific IoC container using the fresh bakery library in plain python code
import asyncio
from dataclasses import dataclass
from bakery import Bakery, Cake
# your dependecies
@dataclass
class Settings:
database_dsn: str
info_id_list: list[int]
class Database:
def __init__(self, dsn: str):
self.dsn: str = dsn
async def fetch_info(self, info_id: int) -> dict:
return {"dsn": self.dsn, "info_id": info_id}
class InfoManager:
def __init__(self, database: Database):
self.database: Database = database
async def fetch_full_info(self, info_id: int) -> dict:
info: dict = await self.database.fetch_info(info_id)
info["full"] = True
return info
# specific ioc container, all magic happens here
class MyBakeryIOC(Bakery):
settings: Settings = Cake(Settings, database_dsn="my_dsn", info_id_list=[1,2,3])
database: Database = Cake(Database, dsn=settings.database_dsn)
manager: InfoManager = Cake(InfoManager, database=database)
# code in your application that needs those dependencies ↑
async def main() -> None:
async with MyBakery() as bakery:
for info_id in bakery.settings.info_id_list:
info: dict = await bakery.manager.fetch_full_info(info_id)
assert info["dsn"] == bakery.settings.database_dsn
assert info["info_id"] == info_id
assert info["full"]
# just a piece of service code
if __name__ == "__main__":
asyncio.run(main())
FastAPI example
This is a full-fledged complex example of how you can use IoC with your FastAPI application:
import asyncio
import random
import typing
import bakery
import fastapi
import pydantic
from loguru import logger
# The following is a long and boring list of dependencies
class PersonOut(pydantic.BaseModel):
"""Person out."""
first_name: str
second_name: str
age: int
person_id: int
class FakeDbConnection:
"""Fake db connection."""
def __init__(self, *_: typing.Any, **__: typing.Any):
...
class DatabaseFakeService:
"""Fake database layer."""
def __init__(self, connection: FakeDbConnection) -> None:
# wannabe connection only for test purposes
self._connection: FakeDbConnection = connection
async def __aenter__(self) -> "DatabaseFakeService":
"""On startup."""
return self
async def __aexit__(self, *_args: typing.Any) -> None:
"""Wannabe shutdown."""
await asyncio.sleep(0)
async def fetch_person(
self, person_id: int
) -> dict[typing.Literal['first_name', 'second_name', 'age', 'id'], str | int]:
"""Fetch (fictitious) person."""
return {
'first_name': random.choice(('John', 'Danku', 'Ichigo', 'Sakura', 'Jugem', 'Ittō')),
'second_name': random.choice(( 'Dow', 'Kurosaki', 'Amaterasu', 'Kasō', 'HiryuGekizokuShintenRaiho')),
'age': random.randint(18, 120),
'id': person_id,
}
class Settings(pydantic.BaseSettings):
"""Service settings."""
postgres_dsn: pydantic.PostgresDsn = pydantic.Field(
default="postgresql://bakery_tester:bakery_tester@0.0.0.0:5432/bakery_tester"
)
postgres_pool_min_size: int = 5
postgres_pool_max_size: int = 20
controller_logger_name: str = "[Controller]"
class ServiceController:
"""Service controller."""
def __init__(
self,
*,
database: DatabaseFakeService,
logger_name: str,
):
self._database = database
self._logger_name = logger_name
def __repr__(self) -> str:
return self._logger_name
async def fetch_person(self, person_id: int, /) -> PersonOut | None:
"""Fetch person by id."""
person: typing.Mapping | None = await self._database.fetch_person(person_id)
if not person:
return None
res: PersonOut = PersonOut(
first_name=person["first_name"],
second_name=person["second_name"],
age=person["age"],
person_id=person_id,
)
return res
def get_settings() -> Settings:
"""Get settings."""
return Settings()
# Here is your specific IoC container
class MainBakeryIOC(bakery.Bakery):
"""Main bakery."""
config: Settings = bakery.Cake(get_settings)
_connection: FakeDbConnection = bakery.Cake(
FakeDbConnection,
config.postgres_dsn,
min_size=config.postgres_pool_min_size,
max_size=config.postgres_pool_max_size,
)
database: DatabaseFakeService = bakery.Cake(
bakery.Cake(
DatabaseFakeService,
connection=_connection,
)
)
controller: ServiceController = bakery.Cake(
ServiceController,
database=database,
logger_name=config.controller_logger_name,
)
async def startup() -> None:
logger.info("Init resources...")
bakery.logger = logger
await MainBakeryIOC.aopen()
async def shutdown() -> None:
logger.info("Shutdown resources...")
await MainBakeryIOC.aclose()
MY_APP: fastapi.FastAPI = fastapi.FastAPI(
on_startup=[startup],
on_shutdown=[shutdown],
)
# Finally, an example of how you can use your dependencies
@MY_APP.get('/person/random/')
async def create_person(
inversed_controller: ServiceController = fastapi.Depends(MainBakeryIOC.controller),
) -> PersonOut | None:
"""Fetch random person from the «database»."""
person_id: typing.Final[int] = random.randint(10**1, 10**6)
return await inversed_controller.fetch_person(person_id)
To run this example, you will need to do the following:
- Install dependencies:
pip install uvicorn fastapi loguru fresh-bakery
- Save the example text to the file test.py
- Run uvicorn
uvicorn test:MY_APP
- Open this address in the browser: http://127.0.0.1:8000/docs#/default/create_person_person_random__get
- And don't forget to read the logs in the console
For a more complete examples, see bakery examples.
Dependencies
No dependencies ;)
Changelog
You can see the release history here: https://github.com/Mityuha/fresh-bakery/releases/
Fresh Bakery is MIT licensed code.
Project details
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
Hashes for fresh_bakery-0.3.3-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c703a8b0a3da84e02febb5894346af4c5b768ec243b8ba747f4ba84c9603ab34 |
|
MD5 | 1a705e71983269a6a2c3190d01ede54f |
|
BLAKE2b-256 | 9fb469f7faa00f38f1084571d8bec50166def808230fbec3de3be3b4fb1c8583 |