picodi


Namepicodi JSON
Version 0.31.0 PyPI version JSON
download
home_pagehttps://github.com/yakimka/picodi
SummarySimple Dependency Injection for Python
upload_time2025-01-05 21:02:44
maintainerNone
docs_urlNone
authoryakimka
requires_python<4.0,>=3.10
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Picodi - Python DI (Dependency Injection) Library

[![Build Status](https://github.com/yakimka/picodi/actions/workflows/workflow-ci.yml/badge.svg?branch=main&event=push)](https://github.com/yakimka/picodi/actions/workflows/workflow-ci.yml)
[![Codecov](https://codecov.io/gh/yakimka/picodi/branch/main/graph/badge.svg)](https://codecov.io/gh/yakimka/picodi)
[![PyPI - Version](https://img.shields.io/pypi/v/picodi.svg)](https://pypi.org/project/picodi/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/picodi)](https://pypi.org/project/picodi/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/picodi)](https://pypi.org/project/picodi/)
[![Documentation Status](https://readthedocs.org/projects/picodi/badge/?version=stable)](https://picodi.readthedocs.io/en/stable/?badge=stable)

[Documentation](https://picodi.readthedocs.io/)

Picodi simplifies Dependency Injection (DI) for Python applications.
[DI](https://en.wikipedia.org/wiki/Dependency_injection) is a design pattern
that allows objects to receive their dependencies from
an external source rather than creating them internally.
This library supports both synchronous and asynchronous contexts,
and offers features like lifecycle management.

## Table of Contents

- [Status](#status)
- [Installation](#installation)
- [Features](#features)
- [Quick Start](#quick-start)
- [FastAPI Integration](#fastapi-integration)
- [FastAPI Example Project](#fastapi-example-project)
- [Known Issues](#known-issues)
- [License](#license)
- [Contributing](#contributing)
- [Credits](#credits)

## Status

Picodi is currently in the experimental stage.
Public APIs may change without notice until the library reaches a 1.x.x version.

## Installation

```bash
pip install picodi
```

## Features

- ๐ŸŒŸ Simple and lightweight
- ๐Ÿ“ฆ Zero dependencies
- โฑ๏ธ Supports both sync and async contexts
- ๐Ÿ”„ Lifecycle management
- ๐Ÿ” Type hints support
- ๐Ÿ Python & PyPy 3.10+ support
- ๐Ÿš€ Works well with [FastAPI](https://fastapi.tiangolo.com/)
- ๐Ÿงช Integration with [pytest](https://docs.pytest.org/)

## Quick Start

```python
import asyncio
from collections.abc import Callable
from datetime import date
from typing import Any

import httpx

from picodi import (
    Provide,
    inject,
    dependency,
    SingletonScope,
)
from picodi.helpers import get_value, lifespan


# Regular functions without required arguments can be used as a dependency
def get_settings() -> dict:
    return {
        "nasa_api": {
            "api_key": "DEMO_KEY",
            "base_url": "https://api.nasa.gov",
            "timeout": 10,
        }
    }


# Helper function to get a setting from the settings dictionary.
# We can use this function to inject specific settings, not the whole settings object.
@inject
def get_setting(path: str, settings: dict = Provide(get_settings)) -> Callable[[], Any]:
    value = get_value(path, settings)
    return lambda: value


# We want to reuse the same client for all requests, so we declare a dependency
#   with `SingletonScope` that provides an httpx.AsyncClient instance
#   with the correct settings.
@dependency(scope_class=SingletonScope)
@inject
async def get_nasa_client(
    api_key: str = Provide(get_setting("nasa_api.api_key")),
    base_url: str = Provide(get_setting("nasa_api.base_url")),
    timeout: int = Provide(get_setting("nasa_api.timeout")),
) -> httpx.AsyncClient:
    async with httpx.AsyncClient(
        base_url=base_url, params={"api_key": api_key}, timeout=timeout
    ) as client:
        yield client


@inject
async def get_apod(
    date: date, client: httpx.AsyncClient = Provide(get_nasa_client)
) -> dict[str, Any]:
    # Printing the client ID to show that the same client is reused for all requests.
    print("Client ID:", id(client))
    response = await client.get("/planetary/apod", params={"date": date.isoformat()})
    response.raise_for_status()
    return response.json()


@inject
# Note that asynchronous `get_nasa_client` is injected
#  in synchronous `print_client_info` function.
def print_client_info(client: httpx.AsyncClient = Provide(get_nasa_client)):
    print("Client ID:", id(client))
    print("Client Base URL:", client.base_url)
    print("Client Params:", client.params)
    print("Client Timeout:", client.timeout)


# `lifespan` will initialize dependencies on the application startup.
#   This will create the
#   httpx.AsyncClient instance and cache it for later use. Thereby, the same
#   client will be reused for all requests. This is important for connection
#   pooling and performance. Because it's created on app startup,
#   it will allow to pass asynchronous `get_nasa_client` into synchronous functions.
# And closing all inited dependencies after the function execution.
@lifespan(dependencies_for_init=[get_nasa_client])
async def main():
    print_client_info()

    apod_data = await get_apod(date(2011, 7, 19))
    print("Title:", apod_data["title"])

    apod_data = await get_apod(date(2011, 7, 26))
    print("Title:", apod_data["title"])


asyncio.run(main())
# Client ID: 4334576784
# Client Base URL: https://api.nasa.gov
# Client Params: api_key=DEMO_KEY
# Client Timeout: Timeout(timeout=10)
#
# Client ID: 4334576784
# Title: Vesta Vista
#
# Client ID: 4334576784
# Title: Galaxy NGC 474: Cosmic Blender
```

## Integration with other libraries

[Read on the documentation site](https://picodi.readthedocs.io/en/stable/integrations.html)

## FastAPI Example Project

Here is an example of a FastAPI application
that uses Picodi for dependency injection:

[Picodi FastAPI Example](https://github.com/yakimka/picodi-fastapi-example)

## Known Issues

[Read on the documentation site](https://picodi.readthedocs.io/en/stable/knownissues.html)

## License

[MIT](https://github.com/yakimka/picodi/blob/main/LICENSE)

## Contributing

Contributions are welcome!
Please read the [CONTRIBUTING.md](https://github.com/yakimka/picodi/blob/main/CONTRIBUTING.md) file for more information.

## Credits

This project was generated with [`yakimka/cookiecutter-pyproject`](https://github.com/yakimka/cookiecutter-pyproject).

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/yakimka/picodi",
    "name": "picodi",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.10",
    "maintainer_email": null,
    "keywords": null,
    "author": "yakimka",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/f9/68/c596e552bffc429cf9cc41e9b76acd6fa7952e71db61a14c634f8075c27b/picodi-0.31.0.tar.gz",
    "platform": null,
    "description": "# Picodi - Python DI (Dependency Injection) Library\n\n[![Build Status](https://github.com/yakimka/picodi/actions/workflows/workflow-ci.yml/badge.svg?branch=main&event=push)](https://github.com/yakimka/picodi/actions/workflows/workflow-ci.yml)\n[![Codecov](https://codecov.io/gh/yakimka/picodi/branch/main/graph/badge.svg)](https://codecov.io/gh/yakimka/picodi)\n[![PyPI - Version](https://img.shields.io/pypi/v/picodi.svg)](https://pypi.org/project/picodi/)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/picodi)](https://pypi.org/project/picodi/)\n[![PyPI - Downloads](https://img.shields.io/pypi/dm/picodi)](https://pypi.org/project/picodi/)\n[![Documentation Status](https://readthedocs.org/projects/picodi/badge/?version=stable)](https://picodi.readthedocs.io/en/stable/?badge=stable)\n\n[Documentation](https://picodi.readthedocs.io/)\n\nPicodi simplifies Dependency Injection (DI) for Python applications.\n[DI](https://en.wikipedia.org/wiki/Dependency_injection) is a design pattern\nthat allows objects to receive their dependencies from\nan external source rather than creating them internally.\nThis library supports both synchronous and asynchronous contexts,\nand offers features like lifecycle management.\n\n## Table of Contents\n\n- [Status](#status)\n- [Installation](#installation)\n- [Features](#features)\n- [Quick Start](#quick-start)\n- [FastAPI Integration](#fastapi-integration)\n- [FastAPI Example Project](#fastapi-example-project)\n- [Known Issues](#known-issues)\n- [License](#license)\n- [Contributing](#contributing)\n- [Credits](#credits)\n\n## Status\n\nPicodi is currently in the experimental stage.\nPublic APIs may change without notice until the library reaches a 1.x.x version.\n\n## Installation\n\n```bash\npip install picodi\n```\n\n## Features\n\n- \ud83c\udf1f Simple and lightweight\n- \ud83d\udce6 Zero dependencies\n- \u23f1\ufe0f Supports both sync and async contexts\n- \ud83d\udd04 Lifecycle management\n- \ud83d\udd0d Type hints support\n- \ud83d\udc0d Python & PyPy 3.10+ support\n- \ud83d\ude80 Works well with [FastAPI](https://fastapi.tiangolo.com/)\n- \ud83e\uddea Integration with [pytest](https://docs.pytest.org/)\n\n## Quick Start\n\n```python\nimport asyncio\nfrom collections.abc import Callable\nfrom datetime import date\nfrom typing import Any\n\nimport httpx\n\nfrom picodi import (\n    Provide,\n    inject,\n    dependency,\n    SingletonScope,\n)\nfrom picodi.helpers import get_value, lifespan\n\n\n# Regular functions without required arguments can be used as a dependency\ndef get_settings() -> dict:\n    return {\n        \"nasa_api\": {\n            \"api_key\": \"DEMO_KEY\",\n            \"base_url\": \"https://api.nasa.gov\",\n            \"timeout\": 10,\n        }\n    }\n\n\n# Helper function to get a setting from the settings dictionary.\n# We can use this function to inject specific settings, not the whole settings object.\n@inject\ndef get_setting(path: str, settings: dict = Provide(get_settings)) -> Callable[[], Any]:\n    value = get_value(path, settings)\n    return lambda: value\n\n\n# We want to reuse the same client for all requests, so we declare a dependency\n#   with `SingletonScope` that provides an httpx.AsyncClient instance\n#   with the correct settings.\n@dependency(scope_class=SingletonScope)\n@inject\nasync def get_nasa_client(\n    api_key: str = Provide(get_setting(\"nasa_api.api_key\")),\n    base_url: str = Provide(get_setting(\"nasa_api.base_url\")),\n    timeout: int = Provide(get_setting(\"nasa_api.timeout\")),\n) -> httpx.AsyncClient:\n    async with httpx.AsyncClient(\n        base_url=base_url, params={\"api_key\": api_key}, timeout=timeout\n    ) as client:\n        yield client\n\n\n@inject\nasync def get_apod(\n    date: date, client: httpx.AsyncClient = Provide(get_nasa_client)\n) -> dict[str, Any]:\n    # Printing the client ID to show that the same client is reused for all requests.\n    print(\"Client ID:\", id(client))\n    response = await client.get(\"/planetary/apod\", params={\"date\": date.isoformat()})\n    response.raise_for_status()\n    return response.json()\n\n\n@inject\n# Note that asynchronous `get_nasa_client` is injected\n#  in synchronous `print_client_info` function.\ndef print_client_info(client: httpx.AsyncClient = Provide(get_nasa_client)):\n    print(\"Client ID:\", id(client))\n    print(\"Client Base URL:\", client.base_url)\n    print(\"Client Params:\", client.params)\n    print(\"Client Timeout:\", client.timeout)\n\n\n# `lifespan` will initialize dependencies on the application startup.\n#   This will create the\n#   httpx.AsyncClient instance and cache it for later use. Thereby, the same\n#   client will be reused for all requests. This is important for connection\n#   pooling and performance. Because it's created on app startup,\n#   it will allow to pass asynchronous `get_nasa_client` into synchronous functions.\n# And closing all inited dependencies after the function execution.\n@lifespan(dependencies_for_init=[get_nasa_client])\nasync def main():\n    print_client_info()\n\n    apod_data = await get_apod(date(2011, 7, 19))\n    print(\"Title:\", apod_data[\"title\"])\n\n    apod_data = await get_apod(date(2011, 7, 26))\n    print(\"Title:\", apod_data[\"title\"])\n\n\nasyncio.run(main())\n# Client ID: 4334576784\n# Client Base URL: https://api.nasa.gov\n# Client Params: api_key=DEMO_KEY\n# Client Timeout: Timeout(timeout=10)\n#\n# Client ID: 4334576784\n# Title: Vesta Vista\n#\n# Client ID: 4334576784\n# Title: Galaxy NGC 474: Cosmic Blender\n```\n\n## Integration with other libraries\n\n[Read on the documentation site](https://picodi.readthedocs.io/en/stable/integrations.html)\n\n## FastAPI Example Project\n\nHere is an example of a FastAPI application\nthat uses Picodi for dependency injection:\n\n[Picodi FastAPI Example](https://github.com/yakimka/picodi-fastapi-example)\n\n## Known Issues\n\n[Read on the documentation site](https://picodi.readthedocs.io/en/stable/knownissues.html)\n\n## License\n\n[MIT](https://github.com/yakimka/picodi/blob/main/LICENSE)\n\n## Contributing\n\nContributions are welcome!\nPlease read the [CONTRIBUTING.md](https://github.com/yakimka/picodi/blob/main/CONTRIBUTING.md) file for more information.\n\n## Credits\n\nThis project was generated with [`yakimka/cookiecutter-pyproject`](https://github.com/yakimka/cookiecutter-pyproject).\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Simple Dependency Injection for Python",
    "version": "0.31.0",
    "project_urls": {
        "Homepage": "https://github.com/yakimka/picodi",
        "Repository": "https://github.com/yakimka/picodi"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "59d2e35709fa77258a7123692b58924f28d6beb1a9b700ea960fa50ebf870fc4",
                "md5": "22fa652b4d2ad5661c5e6e6914391ffc",
                "sha256": "d1827c669d30975a041556d0469282c99dff707dcf5a28bd73545e306d8bb27a"
            },
            "downloads": -1,
            "filename": "picodi-0.31.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "22fa652b4d2ad5661c5e6e6914391ffc",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.10",
            "size": 19219,
            "upload_time": "2025-01-05T21:02:42",
            "upload_time_iso_8601": "2025-01-05T21:02:42.757565Z",
            "url": "https://files.pythonhosted.org/packages/59/d2/e35709fa77258a7123692b58924f28d6beb1a9b700ea960fa50ebf870fc4/picodi-0.31.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f968c596e552bffc429cf9cc41e9b76acd6fa7952e71db61a14c634f8075c27b",
                "md5": "7cb109b2b9cab83cea958b7c0ec78d07",
                "sha256": "8db6e53ba94d592fd7ec059a9849aa257275fb569667c06ab8a195463dd6b47c"
            },
            "downloads": -1,
            "filename": "picodi-0.31.0.tar.gz",
            "has_sig": false,
            "md5_digest": "7cb109b2b9cab83cea958b7c0ec78d07",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.10",
            "size": 18308,
            "upload_time": "2025-01-05T21:02:44",
            "upload_time_iso_8601": "2025-01-05T21:02:44.710985Z",
            "url": "https://files.pythonhosted.org/packages/f9/68/c596e552bffc429cf9cc41e9b76acd6fa7952e71db61a14c634f8075c27b/picodi-0.31.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-05 21:02:44",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "yakimka",
    "github_project": "picodi",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "picodi"
}
        
Elapsed time: 1.40858s