fresh-bakery


Namefresh-bakery JSON
Version 0.3.3 PyPI version JSON
download
home_page
SummaryBake your dependencies stupidly simple!
upload_time2023-11-01 17:48:44
maintainer
docs_urlNone
authorDmitry Makarov
requires_python>=3.6.2,<3.13
licenseMIT
keywords dependency injection dependency injection pattern constructor injection inversion of control inversion of control container ioc
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            
<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"
}
        
Elapsed time: 0.14758s