Skip to main content

Bake your dependencies stupidly simple!

Project description

fresh-bakery

🍰 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 [fully] 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

$ 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:

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.

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:

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:

# 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:

# 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 function.

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 function will do the job.

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:

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:

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:

# 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:

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.

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

fresh_bakery-0.4.3.tar.gz (17.6 kB view details)

Uploaded Source

Built Distribution

fresh_bakery-0.4.3-py3-none-any.whl (19.7 kB view details)

Uploaded Python 3

File details

Details for the file fresh_bakery-0.4.3.tar.gz.

File metadata

  • Download URL: fresh_bakery-0.4.3.tar.gz
  • Upload date:
  • Size: 17.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.0.1 CPython/3.12.8 Linux/6.8.0-1020-azure

File hashes

Hashes for fresh_bakery-0.4.3.tar.gz
Algorithm Hash digest
SHA256 13d8d2f50d1fc40f94b58c2bacf0f6be00ac1c8210cf4eb6e687affdcd25dac7
MD5 30cdd124f752946d64faa6b39c1b7b07
BLAKE2b-256 f1bd8dc0e080ca9fc50d68272516159bf30247f93fbe819d5b6b57645368baf1

See more details on using hashes here.

File details

Details for the file fresh_bakery-0.4.3-py3-none-any.whl.

File metadata

  • Download URL: fresh_bakery-0.4.3-py3-none-any.whl
  • Upload date:
  • Size: 19.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.0.1 CPython/3.12.8 Linux/6.8.0-1020-azure

File hashes

Hashes for fresh_bakery-0.4.3-py3-none-any.whl
Algorithm Hash digest
SHA256 6d234a2ba629d6f7211244795df5a0c4b333ea061e986d6b3bc88592beda3184
MD5 46f1a788ba37ef039d0b14ddda5b6512
BLAKE2b-256 1b41e65cf6a2a52788bcf5388430bc88dcc3ec6f6c3169bfae476df629950917

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page