<p align="center">
<a href="https://fresh-bakery.readthedocs.io/en/latest/"><img width="300px" src="https://github.com/Mityuha/fresh-bakery/assets/17745407/9ad83683-03dc-43af-b66f-f8a010bde264" alt='fresh-bakery'></a>
</p>
<p align="center">
<em>🍰 The little DI framework that tastes like a cake. 🍰</em>
</p>
---
**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 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:
```python
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:
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 console
For 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/
---
<p align="center"><i>Fresh Bakery is <a href="https://github.com/Mityuha/fresh-bakery/blob/main/LICENSE">MIT licensed</a> code.</p>
Raw data
{
"_id": null,
"home_page": "",
"name": "fresh-bakery",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.6.2,<3.13",
"maintainer_email": "",
"keywords": "Dependency Injection,Dependency Injection pattern,Constructor Injection,Inversion of Control,Inversion of Control Container,IoC",
"author": "Dmitry Makarov",
"author_email": "mit.makaroff@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/35/5a/34480584e526f45a0a59097d3a8c5e96db1530dd6c956048df6e56abcea7/fresh_bakery-0.3.3.tar.gz",
"platform": null,
"description": "\n<p align=\"center\">\n <a href=\"https://fresh-bakery.readthedocs.io/en/latest/\"><img width=\"300px\" src=\"https://github.com/Mityuha/fresh-bakery/assets/17745407/9ad83683-03dc-43af-b66f-f8a010bde264\" alt='fresh-bakery'></a>\n</p>\n<p align=\"center\">\n <em>\ud83c\udf70 The little DI framework that tastes like a cake. \ud83c\udf70</em>\n</p>\n\n---\n\n**Documentation**: [https://fresh-bakery.readthedocs.io/en/latest/](https://fresh-bakery.readthedocs.io/en/latest/)\n\n---\n\n# Fresh Bakery\n\nFresh bakery is a lightweight [Dependency Injection][DI] framework/toolkit,\nwhich is ideal for building object dependencies in Python.\n\nIt is [nearly] production-ready, and gives you the following:\n\n* A lightweight, stupidly simple DI framework.\n* Fully asynchronous, no synchronous mode.\n* Any async backends compatible (`asyncio`, `trio`).\n* Zero dependencies.\n* `Mypy` compatible (no probably need for `# type: ignore`).\n* `FastAPI` fully compatible.\n* `Litestar` compatible.\n* `Pytest` fully compatible (Fresh Bakery encourages the use of `pytest`).\n* Ease of testing.\n* Easily extended (contribution is welcome).\n\n## Requirements\n\nPython 3.6+\n\n## Installation\n\n```shell\n$ pip3 install fresh-bakery\n```\n\n## Examples\n\n### Raw example\nIn this example, you can see how to create a specific IoC container using the fresh bakery library in plain python code \n```python\nimport asyncio\n\nfrom dataclasses import dataclass\nfrom bakery import Bakery, Cake\n\n\n# your dependecies\n@dataclass\nclass Settings:\n database_dsn: str\n info_id_list: list[int]\n\n\nclass Database:\n def __init__(self, dsn: str):\n self.dsn: str = dsn\n\n async def fetch_info(self, info_id: int) -> dict:\n return {\"dsn\": self.dsn, \"info_id\": info_id}\n\n\nclass InfoManager:\n def __init__(self, database: Database):\n self.database: Database = database\n\n async def fetch_full_info(self, info_id: int) -> dict:\n info: dict = await self.database.fetch_info(info_id)\n info[\"full\"] = True\n return info\n\n\n# specific ioc container, all magic happens here\nclass MyBakeryIOC(Bakery):\n settings: Settings = Cake(Settings, database_dsn=\"my_dsn\", info_id_list=[1,2,3])\n database: Database = Cake(Database, dsn=settings.database_dsn)\n manager: InfoManager = Cake(InfoManager, database=database)\n\n\n# code in your application that needs those dependencies \u2191\nasync def main() -> None:\n async with MyBakery() as bakery:\n for info_id in bakery.settings.info_id_list:\n info: dict = await bakery.manager.fetch_full_info(info_id)\n assert info[\"dsn\"] == bakery.settings.database_dsn\n assert info[\"info_id\"] == info_id\n assert info[\"full\"]\n\n\n# just a piece of service code\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n### FastAPI example\nThis is a full-fledged complex example of how you can use IoC with your FastAPI application:\n```python\nimport asyncio\nimport random\nimport typing\n\nimport bakery\nimport fastapi\nimport pydantic\nfrom loguru import logger\n\n\n# The following is a long and boring list of dependencies\nclass PersonOut(pydantic.BaseModel):\n \"\"\"Person out.\"\"\"\n\n first_name: str\n second_name: str\n age: int\n person_id: int\n\n\nclass FakeDbConnection:\n \"\"\"Fake db connection.\"\"\"\n\n def __init__(self, *_: typing.Any, **__: typing.Any):\n ...\n\n\nclass DatabaseFakeService:\n \"\"\"Fake database layer.\"\"\"\n\n def __init__(self, connection: FakeDbConnection) -> None:\n # wannabe connection only for test purposes\n self._connection: FakeDbConnection = connection\n\n async def __aenter__(self) -> \"DatabaseFakeService\":\n \"\"\"On startup.\"\"\"\n return self\n\n async def __aexit__(self, *_args: typing.Any) -> None:\n \"\"\"Wannabe shutdown.\"\"\"\n await asyncio.sleep(0)\n\n async def fetch_person(\n self, person_id: int\n ) -> dict[typing.Literal['first_name', 'second_name', 'age', 'id'], str | int]:\n \"\"\"Fetch (fictitious) person.\"\"\"\n return {\n 'first_name': random.choice(('John', 'Danku', 'Ichigo', 'Sakura', 'Jugem', 'Itt\u014d')),\n 'second_name': random.choice(( 'Dow', 'Kurosaki', 'Amaterasu', 'Kas\u014d', 'HiryuGekizokuShintenRaiho')),\n 'age': random.randint(18, 120),\n 'id': person_id,\n }\n\n\nclass Settings(pydantic.BaseSettings):\n \"\"\"Service settings.\"\"\"\n\n postgres_dsn: pydantic.PostgresDsn = pydantic.Field(\n default=\"postgresql://bakery_tester:bakery_tester@0.0.0.0:5432/bakery_tester\"\n )\n postgres_pool_min_size: int = 5\n postgres_pool_max_size: int = 20\n controller_logger_name: str = \"[Controller]\"\n\n\nclass ServiceController:\n \"\"\"Service controller.\"\"\"\n\n def __init__(\n self,\n *,\n database: DatabaseFakeService,\n logger_name: str,\n ):\n self._database = database\n self._logger_name = logger_name\n\n def __repr__(self) -> str:\n return self._logger_name\n\n async def fetch_person(self, person_id: int, /) -> PersonOut | None:\n \"\"\"Fetch person by id.\"\"\"\n person: typing.Mapping | None = await self._database.fetch_person(person_id)\n if not person:\n return None\n res: PersonOut = PersonOut(\n first_name=person[\"first_name\"],\n second_name=person[\"second_name\"],\n age=person[\"age\"],\n person_id=person_id,\n )\n return res\n\n\ndef get_settings() -> Settings:\n \"\"\"Get settings.\"\"\"\n return Settings()\n\n\n# Here is your specific IoC container\nclass MainBakeryIOC(bakery.Bakery):\n \"\"\"Main bakery.\"\"\"\n\n config: Settings = bakery.Cake(get_settings)\n _connection: FakeDbConnection = bakery.Cake(\n FakeDbConnection,\n config.postgres_dsn,\n min_size=config.postgres_pool_min_size,\n max_size=config.postgres_pool_max_size,\n )\n database: DatabaseFakeService = bakery.Cake(\n bakery.Cake(\n DatabaseFakeService,\n connection=_connection,\n )\n )\n controller: ServiceController = bakery.Cake(\n ServiceController,\n database=database,\n logger_name=config.controller_logger_name,\n )\n\n\nasync def startup() -> None:\n logger.info(\"Init resources...\")\n bakery.logger = logger\n await MainBakeryIOC.aopen()\n\n\nasync def shutdown() -> None:\n logger.info(\"Shutdown resources...\")\n await MainBakeryIOC.aclose()\n\n\nMY_APP: fastapi.FastAPI = fastapi.FastAPI(\n on_startup=[startup],\n on_shutdown=[shutdown],\n)\n\n\n# Finally, an example of how you can use your dependencies\n@MY_APP.get('/person/random/')\nasync def create_person(\n inversed_controller: ServiceController = fastapi.Depends(MainBakeryIOC.controller),\n) -> PersonOut | None:\n \"\"\"Fetch random person from the \u00abdatabase\u00bb.\"\"\"\n person_id: typing.Final[int] = random.randint(10**1, 10**6)\n return await inversed_controller.fetch_person(person_id)\n```\nTo run this example, you will need to do the following:\n1. Install dependencies:\n ```\n pip install uvicorn fastapi loguru fresh-bakery\n ```\n1. Save the example text to the file test.py\n1. Run uvicorn\n ```\n uvicorn test:MY_APP\n ```\n1. Open this address in the browser: http://127.0.0.1:8000/docs#/default/create_person_person_random__get\n1. And don't forget to read the logs in the console\n\nFor a more complete examples, see [bakery examples](https://github.com/Mityuha/fresh-bakery/tree/main/examples).\n\n## Dependencies\n\nNo dependencies ;)\n\n## Changelog\nYou can see the release history here: https://github.com/Mityuha/fresh-bakery/releases/\n\n---\n\n<p align=\"center\"><i>Fresh Bakery is <a href=\"https://github.com/Mityuha/fresh-bakery/blob/main/LICENSE\">MIT licensed</a> code.</p>\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Bake your dependencies stupidly simple!",
"version": "0.3.3",
"project_urls": {
"Changelog": "https://github.com/Mityuha/fresh-bakery/releases",
"Documentation": "https://fresh-bakery.readthedocs.io/en/latest/",
"Source": "https://github.com/Mityuha/fresh-bakery",
"Tracker": "https://github.com/Mityuha/fresh-bakery/issues"
},
"split_keywords": [
"dependency injection",
"dependency injection pattern",
"constructor injection",
"inversion of control",
"inversion of control container",
"ioc"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "9fb469f7faa00f38f1084571d8bec50166def808230fbec3de3be3b4fb1c8583",
"md5": "1a705e71983269a6a2c3190d01ede54f",
"sha256": "c703a8b0a3da84e02febb5894346af4c5b768ec243b8ba747f4ba84c9603ab34"
},
"downloads": -1,
"filename": "fresh_bakery-0.3.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "1a705e71983269a6a2c3190d01ede54f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6.2,<3.13",
"size": 17947,
"upload_time": "2023-11-01T17:48:41",
"upload_time_iso_8601": "2023-11-01T17:48:41.258213Z",
"url": "https://files.pythonhosted.org/packages/9f/b4/69f7faa00f38f1084571d8bec50166def808230fbec3de3be3b4fb1c8583/fresh_bakery-0.3.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "355a34480584e526f45a0a59097d3a8c5e96db1530dd6c956048df6e56abcea7",
"md5": "31b68b698ebe7d80a7ba7139ab1d978f",
"sha256": "08991b54ccee2d50000d2fd4d6db385e0c700f410690b7365e3a78e9b3b10f20"
},
"downloads": -1,
"filename": "fresh_bakery-0.3.3.tar.gz",
"has_sig": false,
"md5_digest": "31b68b698ebe7d80a7ba7139ab1d978f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6.2,<3.13",
"size": 18010,
"upload_time": "2023-11-01T17:48:44",
"upload_time_iso_8601": "2023-11-01T17:48:44.462646Z",
"url": "https://files.pythonhosted.org/packages/35/5a/34480584e526f45a0a59097d3a8c5e96db1530dd6c956048df6e56abcea7/fresh_bakery-0.3.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-11-01 17:48:44",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Mityuha",
"github_project": "fresh-bakery",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "fresh-bakery"
}