<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.8+
## Installation
```shell
$ pip3 install fresh-bakery
```
## Examples
### Quickstart
This example is intended to show the nature of Dependency Injection and the ease of use the library. Many of us work 8 hours per day on average, 5 days a week, i.e. ~ 40 hours per week. Let's describe it using DI and bakery:
```python
from bakery import Bakery, Cake
def full_days_in(hours: int) -> float:
return hours / 24
def average(total: int, num: int) -> float:
return total / num
class WorkingBakery(Bakery):
average_hours: int = Cake(8)
week_hours: int = Cake(sum, [average_hours, average_hours, 7, 9, average_hours])
full_days: float = Cake(full_days_in, week_hours)
async def main() -> None:
async with WorkingBakery() as bakery:
assert bakery.week_hours == 40
assert bakery.full_days - 0.00001 < full_days_in(40)
assert int(bakery.average_hours) == 8
```
You can see it's as simple as it can be.
### One more example
Let's suppose we have a thin wrapper around file object.
```python
from typing import ClassVar, Final
from typing_extensions import Self
class FileWrapper:
file_opened: bool = False
write_lines: ClassVar[list[str]] = []
def __init__(self, filename: str) -> None:
self.filename: Final = filename
def write(self, line: str) -> int:
type(self).write_lines.append(line)
return len(line)
def __enter__(self) -> Self:
type(self).file_opened = True
return self
def __exit__(self, *_args: object) -> None:
type(self).file_opened = False
type(self).write_lines.clear()
```
This wrapper acts exactly like a file object: it can be opened, closed, and can write line to file.
Let's open file `hello.txt`, write 2 lines into it and close it. Let's do all this with the bakery syntax:
```python
from bakery import Bakery, Cake
class FileBakery(Bakery):
_file_obj: FileWrapper = Cake(FileWrapper, "hello.txt")
file_obj: FileWrapper = Cake(_file_obj)
write_1_bytes: int = Cake(file_obj.write, "hello, ")
write_2_bytes: int = Cake(file_obj.write, "world")
async def main() -> None:
assert FileWrapper.file_opened is False
assert FileWrapper.write_lines == []
async with FileBakery() as bakery:
assert bakery.file_obj.filename == "hello.txt"
assert FileWrapper.file_opened is True
assert FileWrapper.write_lines == ["hello, ", "world"]
assert FileWrapper.file_opened is False
assert FileWrapper.write_lines == []
```
Maybe you noticed some strange things concerning `FileBakery` bakery:
1. `_file_obj` and `file_obj` objects. Do we need them both?
2. Unused `write_1_bytes` and `write_2_bytes` objects. Do we need them?
Let's try to fix both cases. First, let's figure out why do we need `_file_obj` and `file_obj` objects?
- The first `Cake` for `_file_obj` initiates `FileWrapper` object, i.e. calls `__init__` method;
- the second `Cake` for `file_obj` calls context-manager, i.e. calls `__enter__` method on enter and `__exit__` method on exit.
Actually, we can merge these two statements into single one:
```python
# class FileBakery(Bakery):
file_obj: FileWrapper = Cake(Cake(FileWrapper, "hello.txt"))
```
So, what about unused arguments? OK, let's re-write this gist a little bit. First, let's declare the list of strings we want to write:
```python
# class FileBakery(Bakery):
strs_to_write: list[str] = Cake(["hello, ", "world"])
```
How to apply function to every string in this list? There are several ways to do it. One of them is built-in [`map`](https://docs.python.org/3/library/functions.html#map) function.
```python
map_cake = Cake(map, file_obj.write, strs_to_write)
```
But `map` function returns iterator and we need to get elements from it. Built-in [`list`](https://docs.python.org/3/library/functions.html#func-list) function will do the job.
```python
list_cake = Cake(list, map_cake)
```
In the same manner as we did for `file_obj` let's merge these two statements into one. The final `FileBakery` will look like this:
```python
class FileBakeryMap(Bakery):
file_obj: FileWrapper = Cake(Cake(FileWrapper, "hello.txt"))
strs_to_write: list[str] = Cake(["hello, ", "world"])
_: list[int] = Cake(list, Cake(map, file_obj.write, strs_to_write))
```
The last thing nobody likes is hard-coded strings! In this case such strings are:
- the name of the file `hello.txt`
- list of strings to write: `hello, ` and `world`
What if we've got another filename or other strings to write? Let's define filename and list of strings as `FileBakery` parameters:
```python
from bakery import Bakery, Cake, __Cake__
class FileBakery(Bakery):
filename: str = __Cake__()
strs_to_write: list[str] = __Cake__()
file_obj: FileWrapper = Cake(Cake(FileWrapper, filename))
_: list[int] = Cake(list, Cake(map, file_obj.write, strs_to_write))
```
To define parameters you can use dunder-cake construction: `__Cake__()`.
To pass arguments into `FileBakery` you can use native python syntax:
```python
# async def main() -> None:
async with FileBakeryMapWithParams(
filename="hello.txt", strs_to_write=["hello, ", "world"]
) as bakery:
...
```
And the whole example will look like this:
```python
from typing import ClassVar, Final
from typing_extensions import Self
from bakery import Bakery, Cake, __Cake__
# class FileWrapper: ...
class FileBakery(Bakery):
filename: str = __Cake__()
strs_to_write: list[str] = __Cake__()
file_obj: FileWrapper = Cake(Cake(FileWrapper, filename))
_: list[int] = Cake(list, Cake(map, file_obj.write, strs_to_write))
async def main() -> None:
assert FileWrapper.file_opened is False
assert FileWrapper.write_lines == []
async with FileBakeryMapWithParams(
filename="hello.txt", strs_to_write=["hello, ", "world"]
) as bakery:
assert bakery.file_obj.filename == "hello.txt"
assert FileWrapper.file_opened is True
assert FileWrapper.write_lines == ["hello, ", "world"]
assert FileWrapper.file_opened is False
assert FileWrapper.write_lines == []
```
More examples are presented in section [bakery examples](https://fresh-bakery.readthedocs.io/en/latest/bakery_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": null,
"name": "fresh-bakery",
"maintainer": null,
"docs_url": null,
"requires_python": "<3.13,>=3.8",
"maintainer_email": null,
"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/a2/cf/62a85fe182020040d67582afeb94e432e455d8aedc34d1bfe5b21070bfa8/fresh_bakery-0.4.2.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.8+\n\n## Installation\n\n```shell\n$ pip3 install fresh-bakery\n```\n\n## Examples\n\n### Quickstart\nThis example is intended to show the nature of Dependency Injection and the ease of use the library. Many of us work 8 hours per day on average, 5 days a week, i.e. ~ 40 hours per week. Let's describe it using DI and bakery:\n```python\nfrom bakery import Bakery, Cake\n\n\ndef full_days_in(hours: int) -> float:\n return hours / 24\n\n\ndef average(total: int, num: int) -> float:\n return total / num\n\n\nclass WorkingBakery(Bakery):\n average_hours: int = Cake(8)\n week_hours: int = Cake(sum, [average_hours, average_hours, 7, 9, average_hours])\n full_days: float = Cake(full_days_in, week_hours)\n\n\nasync def main() -> None:\n async with WorkingBakery() as bakery:\n assert bakery.week_hours == 40\n assert bakery.full_days - 0.00001 < full_days_in(40)\n assert int(bakery.average_hours) == 8\n```\nYou can see it's as simple as it can be.\n\n### One more example\nLet's suppose we have a thin wrapper around file object.\n```python\nfrom typing import ClassVar, Final\n\nfrom typing_extensions import Self\n\n\nclass FileWrapper:\n file_opened: bool = False\n write_lines: ClassVar[list[str]] = []\n\n def __init__(self, filename: str) -> None:\n self.filename: Final = filename\n\n def write(self, line: str) -> int:\n type(self).write_lines.append(line)\n return len(line)\n\n def __enter__(self) -> Self:\n type(self).file_opened = True\n return self\n\n def __exit__(self, *_args: object) -> None:\n type(self).file_opened = False\n type(self).write_lines.clear()\n```\nThis wrapper acts exactly like a file object: it can be opened, closed, and can write line to file.\nLet's open file `hello.txt`, write 2 lines into it and close it. Let's do all this with the bakery syntax:\n```python\nfrom bakery import Bakery, Cake\n\n\nclass FileBakery(Bakery):\n _file_obj: FileWrapper = Cake(FileWrapper, \"hello.txt\")\n file_obj: FileWrapper = Cake(_file_obj)\n write_1_bytes: int = Cake(file_obj.write, \"hello, \")\n write_2_bytes: int = Cake(file_obj.write, \"world\")\n\n\nasync def main() -> None:\n assert FileWrapper.file_opened is False\n assert FileWrapper.write_lines == []\n async with FileBakery() as bakery:\n assert bakery.file_obj.filename == \"hello.txt\"\n assert FileWrapper.file_opened is True\n assert FileWrapper.write_lines == [\"hello, \", \"world\"]\n\n assert FileWrapper.file_opened is False\n assert FileWrapper.write_lines == []\n```\nMaybe you noticed some strange things concerning `FileBakery` bakery:\n1. `_file_obj` and `file_obj` objects. Do we need them both?\n2. Unused `write_1_bytes` and `write_2_bytes` objects. Do we need them?\n\nLet's try to fix both cases. First, let's figure out why do we need `_file_obj` and `file_obj` objects?\n- The first `Cake` for `_file_obj` initiates `FileWrapper` object, i.e. calls `__init__` method;\n- the second `Cake` for `file_obj` calls context-manager, i.e. calls `__enter__` method on enter and `__exit__` method on exit.\n\nActually, we can merge these two statements into single one:\n```python\n# class FileBakery(Bakery):\n file_obj: FileWrapper = Cake(Cake(FileWrapper, \"hello.txt\"))\n```\nSo, what about unused arguments? OK, let's re-write this gist a little bit. First, let's declare the list of strings we want to write:\n```python\n# class FileBakery(Bakery):\n strs_to_write: list[str] = Cake([\"hello, \", \"world\"])\n```\nHow to apply function to every string in this list? There are several ways to do it. One of them is built-in [`map`](https://docs.python.org/3/library/functions.html#map) function.\n```python\nmap_cake = Cake(map, file_obj.write, strs_to_write)\n```\nBut `map` function returns iterator and we need to get elements from it. Built-in [`list`](https://docs.python.org/3/library/functions.html#func-list) function will do the job.\n```python\nlist_cake = Cake(list, map_cake)\n```\nIn the same manner as we did for `file_obj` let's merge these two statements into one. The final `FileBakery` will look like this:\n```python\nclass FileBakeryMap(Bakery):\n file_obj: FileWrapper = Cake(Cake(FileWrapper, \"hello.txt\"))\n strs_to_write: list[str] = Cake([\"hello, \", \"world\"])\n _: list[int] = Cake(list, Cake(map, file_obj.write, strs_to_write))\n```\nThe last thing nobody likes is hard-coded strings! In this case such strings are:\n- the name of the file `hello.txt`\n- list of strings to write: `hello, ` and `world`\n\nWhat if we've got another filename or other strings to write? Let's define filename and list of strings as `FileBakery` parameters:\n```python\nfrom bakery import Bakery, Cake, __Cake__\n\n\nclass FileBakery(Bakery):\n filename: str = __Cake__()\n strs_to_write: list[str] = __Cake__()\n file_obj: FileWrapper = Cake(Cake(FileWrapper, filename))\n _: list[int] = Cake(list, Cake(map, file_obj.write, strs_to_write))\n```\nTo define parameters you can use dunder-cake construction: `__Cake__()`. \nTo pass arguments into `FileBakery` you can use native python syntax:\n```python\n# async def main() -> None:\n async with FileBakeryMapWithParams(\n filename=\"hello.txt\", strs_to_write=[\"hello, \", \"world\"]\n ) as bakery:\n ...\n```\nAnd the whole example will look like this:\n```python\nfrom typing import ClassVar, Final\n\nfrom typing_extensions import Self\n\nfrom bakery import Bakery, Cake, __Cake__\n\n\n# class FileWrapper: ...\n\n\nclass FileBakery(Bakery):\n filename: str = __Cake__()\n strs_to_write: list[str] = __Cake__()\n file_obj: FileWrapper = Cake(Cake(FileWrapper, filename))\n _: list[int] = Cake(list, Cake(map, file_obj.write, strs_to_write))\n\n\nasync def main() -> None:\n assert FileWrapper.file_opened is False\n assert FileWrapper.write_lines == []\n async with FileBakeryMapWithParams(\n filename=\"hello.txt\", strs_to_write=[\"hello, \", \"world\"]\n ) as bakery:\n assert bakery.file_obj.filename == \"hello.txt\"\n assert FileWrapper.file_opened is True\n assert FileWrapper.write_lines == [\"hello, \", \"world\"]\n\n assert FileWrapper.file_opened is False\n assert FileWrapper.write_lines == []\n```\nMore examples are presented in section [bakery examples](https://fresh-bakery.readthedocs.io/en/latest/bakery_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.4.2",
"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": "910f181b1157793307784ab8a1bfa5285ec15321de575b498cc60113c28d91f0",
"md5": "2e8e5d9bd007090d3ad530af1cbeb97a",
"sha256": "24dcf08937dea3390cb5edf968938d83ce6951399350016c4f402435d6eb4d11"
},
"downloads": -1,
"filename": "fresh_bakery-0.4.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2e8e5d9bd007090d3ad530af1cbeb97a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<3.13,>=3.8",
"size": 19726,
"upload_time": "2024-10-14T09:03:57",
"upload_time_iso_8601": "2024-10-14T09:03:57.307448Z",
"url": "https://files.pythonhosted.org/packages/91/0f/181b1157793307784ab8a1bfa5285ec15321de575b498cc60113c28d91f0/fresh_bakery-0.4.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "a2cf62a85fe182020040d67582afeb94e432e455d8aedc34d1bfe5b21070bfa8",
"md5": "93c999a4f07f4a07d7dbb35a9c0f97ef",
"sha256": "4fbc015f4d036947b6312879cf4909816162d0cdb2b12d6e878afeab500ad184"
},
"downloads": -1,
"filename": "fresh_bakery-0.4.2.tar.gz",
"has_sig": false,
"md5_digest": "93c999a4f07f4a07d7dbb35a9c0f97ef",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<3.13,>=3.8",
"size": 18141,
"upload_time": "2024-10-14T09:04:00",
"upload_time_iso_8601": "2024-10-14T09:04:00.866291Z",
"url": "https://files.pythonhosted.org/packages/a2/cf/62a85fe182020040d67582afeb94e432e455d8aedc34d1bfe5b21070bfa8/fresh_bakery-0.4.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-14 09:04:00",
"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"
}