Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Mityuha/fresh-bakery
Bake dependency injections asynchronously and stupidly simple
https://github.com/Mityuha/fresh-bakery
Last synced: 3 months ago
JSON representation
Bake dependency injections asynchronously and stupidly simple
- Host: GitHub
- URL: https://github.com/Mityuha/fresh-bakery
- Owner: Mityuha
- License: mit
- Created: 2022-08-28T11:20:41.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-05-14T06:25:25.000Z (5 months ago)
- Last Synced: 2024-07-22T00:46:09.046Z (3 months ago)
- Language: Python
- Homepage: https://fresh-bakery.readthedocs.io/en/latest/
- Size: 177 KB
- Stars: 20
- Watchers: 6
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-dependency-injection-in-python - Fresh Bakery - Bake dependency injections asynchronously and stupidly simple. [🐍, MIT License]. (Software / DI Frameworks / Containers)
README
🍰 The little DI framework that tastes like a cake. 🍰---
**Documentation**: [https://fresh-bakery.readthedocs.io/en/latest/](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 of `pytest`).
* Ease of testing.
* Easily extended (contribution is welcome).## Requirements
Python 3.6+
## Installation
```shell
$ 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
```python
import asynciofrom 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 = dsnasync 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 = databaseasync 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:
```python
import asyncio
import random
import typingimport 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: intclass 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 = connectionasync def __aenter__(self) -> "DatabaseFakeService":
"""On startup."""
return selfasync 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:[email protected]: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_namedef __repr__(self) -> str:
return self._logger_nameasync 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 resdef 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:
1. Install dependencies:
```
pip install uvicorn fastapi loguru fresh-bakery
```
1. Save the example text to the file test.py
1. Run uvicorn
```
uvicorn test:MY_APP
```
1. Open this address in the browser: http://127.0.0.1:8000/docs#/default/create_person_person_random__get
1. And don't forget to read the logs in the consoleFor a more complete examples, see [bakery examples](https://github.com/Mityuha/fresh-bakery/tree/main/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.