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.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
Release history Release notifications | RSS feed
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.2.1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a26479da5673c12eb76ca7513bff2f3b323a418f45ac9bf79eaa2545502e2b9c |
|
MD5 | 10c08ceee0d8cac2e7fcd9d36cc416f7 |
|
BLAKE2b-256 | ce0ccf59c4fd49a53ec2fa056b89e5e9ea910f71f0e8d4985562c1a724fdf02a |