wireup


Namewireup JSON
Version 0.15.1 PyPI version JSON
download
home_pagehttps://github.com/maldoinc/wireup
SummaryPython Dependency Injection Library
upload_time2025-01-04 19:20:43
maintainerNone
docs_urlNone
authorAldo Mateli
requires_python<4.0,>=3.8
licenseMIT
keywords flask django injector dependency injection dependency injection container dependency injector
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <div align="center">
<h1>Wireup</h1>
<p>Modern Dependency Injection for Python.</p>

[![GitHub](https://img.shields.io/github/license/maldoinc/wireup)](https://github.com/maldoinc/wireup)
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/maldoinc/wireup/run_all.yml)](https://github.com/maldoinc/wireup)
[![Coverage](https://img.shields.io/codeclimate/coverage/maldoinc/wireup?label=Coverage)](https://codeclimate.com/github/maldoinc/wireup)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/wireup)](https://pypi.org/project/wireup/)
[![PyPI - Version](https://img.shields.io/pypi/v/wireup)](https://pypi.org/project/wireup/)

<p>Wireup is a Performant, concise, and easy-to-use Dependency Injection container for Python 3.8+.</p>
<p><a target="_blank" href="https://maldoinc.github.io/wireup">📚 Documentation</a> | <a target="_blank" href="https://github.com/maldoinc/wireup-demo">🎮 Demo Application</a></p>
</div>

---

Dependency Injection (DI) is a design pattern where objects receive their dependencies externally instead of creating them.
Wireup manages the creation, injection, and lifecycle management of dependencies. It uses typing to automatically
resolve dependencies where required, reducing boilerplate and supports modern Python features such as async and generators.

It can function standalone as a DI container or service locator and also integrates with popular frameworks such as Django, FastAPI and Flask.

## ⚡ Key Features
* Inject services and configuration.
* Interfaces and abstract classes.
* Factory pattern.
* Singleton and transient dependencies.
* Framework-agnostic.
* Apply the container as a decorator.
* Service Locator.

## 📋 Quickstart

To showcase the basics of Wireup, we will create a container able to inject the following:

* A `WeatherService` that queries a fictional weather api, needs an api key and, a `KeyValueStore` to cache request data and an async http client
* `KeyValueStore` itself needs a `redis_url` denoting the server it will connect to to query/store data.

These services will then be retrieved in a `/weather/forecast` endpoint that requires `WeatherService` to provide weather information.

``` mermaid
graph LR
    A --> C
    B --> D
    C --> D
    D --> E
    F --> D

    A[⚙️ redis_url]
    B[⚙️ weather_api_key]
    C[🐍 KeyValueStore]
    D[🐍 WeatherService]
    E[🌎 /weather/forecast]
    F[🏭 HttpClient]
```

**1. Set up**

### 1. Setup

Install wireup using pip or your favorite package manager.

```shell
$ pip install wireup
```

The first step is to create a container.

```python
import wireup

container = wireup.create_container(
    # Parameters serve as application/service configuration.
    parameters={
        "redis_url": os.environ["APP_REDIS_URL"],
        "weather_api_key": os.environ["APP_WEATHER_API_KEY"],
    },
    # Let the container know where service registrations are located.
    service_modules=[services]
)
```

Parameters are configuration your application needs. Such as an api key, database url, or other settings.
Service modules is a list of top-level python modules containing service definitions this container needs to know about.


### 2. Define services

The container uses configuration metadata from annotations and types to define services and the dependencies between them.
This means that the service declaration is self-contained and does not require additional setup for most use cases.


#### 🐍 `KeyValueStore`
To create `KeyValueStore`, all we need is the `redis_url` parameter.
The `@service` decorator tells Wireup this is a service, and we simply need to tell the container via annotated types
to fetch the value of the `redis_url` parameter for `dsn`. 


```python
from wireup import service, Inject
from typing_extensions import Annotated

@service
class KeyValueStore:
    def __init__(self, dsn: Annotated[str, Inject(param="redis_url")]) -> None:
        self.client = redis.from_url(dsn)
```

#### 🐍 `WeatherService`
Creating `WeatherService` is also straightforward. The `@service` decorator is used to let Wireup know this is a service
and we use the same syntax as above for the `api_key`. Class dependencies do not need additional annotations in this case.

```python
@service
class WeatherService:
    def __init(
        self,
        api_key: Annotated[str, Inject(param="weather_api_key")],
        kv_store: KeyValueStore,
        client: aiohttp.ClientSession,
    ) -> None:
        self.api_key = api_key
        self.kv_store = kv_store
```

#### 🏭 `aiohttp.ClientSession`

The http client making requests cannot be instantiated directly as we need to enter an async context manager.
To accomodate such cases, Wireup allows you to use functions to create dependencies. 
These can be sync/async as well as regular or generator functions if cleanup needs to take place.

Factories can define their dependencies in the function's signature.

**Note:** When using generator factories make sure to call `container.close` (or `container.aclose()` for async generators)
when the application is terminating for the necessary cleanup to take place.

```python title="services/factories.py"
@service
async def make_http_client() -> AsyncIterator[aiohttp.ClientSession]:
    async with aiohttp.ClientSession() as client:
        yield client
```

> [!TIP] 
> If using annotations is not suitable for your project, you can use factories as shown above to create all dependencies.
> This lets you keep service definitions devoid of Wireup references.

### 3. Use

Use the container as a service locator or apply it as a decorator.

The container instance provides an `autowire` method that when applied
to a function will cause the container to pass the dependencies
when the function is called.

```python title="views/posts.py"  hl_lines="2 3"
@app.get("/weather/forecast")
@container.autowire
async def get_forecast_view(weather_service: WeatherService):
    return await weather_service.get_forecast(...)
```

Alternatively you can use the container's ability to function as a service locator.
Simply call `.get` on the container instance with the type you wish to retrieve.

```python title="views/posts.py"  hl_lines="3"
@app.get("/weather/forecast")
async def get_forecast_view():
    weather_service = container.get(WeatherService)
    return await weather_service.get_forecast(...)
```

#### 3.5 Integrate

While Wireup is framework-agnostic, usage can be simplified when using it alongside one of the integrations.
A key benefit of the integrations, is removing the need to have a global container variable
and the need to decorate injection targets in the frameworks.

Each integration also comes with additional goodies specific to that framework.

- [Django](integrations/django.md)
- [FastAPI](integrations/fastapi.md)
- [Flask](integrations/flask.md)

### 4. Test

Wireup does not patch your services, which means they can be instantiated and tested independently of the container.

To substitute dependencies on autowired targets such as views in a web application you can override dependencies with new ones on the fly.


```python
with container.override.service(WeatherService, new=test_weather_service):
    response = client.get("/weather/forecast")
```

Requests to inject `WeatherService` during the lifetime of the context manager 
will result in `test_weather_service` being injected instead.

## 📚 Documentation

For more information [check out the documentation](https://maldoinc.github.io/wireup)

## 🎮 Demo application

A demo flask application is available at [maldoinc/wireup-demo](https://github.com/maldoinc/wireup-demo)

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/maldoinc/wireup",
    "name": "wireup",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.8",
    "maintainer_email": null,
    "keywords": "flask, django, injector, dependency injection, dependency injection container, dependency injector",
    "author": "Aldo Mateli",
    "author_email": "aldo.mateli@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/3f/7f/2f9fd18d76b9326f107e53d938c92b4955c5fbf05a7a9f36fa79b893a58b/wireup-0.15.1.tar.gz",
    "platform": null,
    "description": "<div align=\"center\">\n<h1>Wireup</h1>\n<p>Modern Dependency Injection for Python.</p>\n\n[![GitHub](https://img.shields.io/github/license/maldoinc/wireup)](https://github.com/maldoinc/wireup)\n[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/maldoinc/wireup/run_all.yml)](https://github.com/maldoinc/wireup)\n[![Coverage](https://img.shields.io/codeclimate/coverage/maldoinc/wireup?label=Coverage)](https://codeclimate.com/github/maldoinc/wireup)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/wireup)](https://pypi.org/project/wireup/)\n[![PyPI - Version](https://img.shields.io/pypi/v/wireup)](https://pypi.org/project/wireup/)\n\n<p>Wireup is a Performant, concise, and easy-to-use Dependency Injection container for Python 3.8+.</p>\n<p><a target=\"_blank\" href=\"https://maldoinc.github.io/wireup\">\ud83d\udcda Documentation</a> | <a target=\"_blank\" href=\"https://github.com/maldoinc/wireup-demo\">\ud83c\udfae Demo Application</a></p>\n</div>\n\n---\n\nDependency Injection (DI) is a design pattern where objects receive their dependencies externally instead of creating them.\nWireup manages the creation, injection, and lifecycle management of dependencies. It uses typing to automatically\nresolve dependencies where required, reducing boilerplate and supports modern Python features such as async and generators.\n\nIt can function standalone as a DI container or service locator and also integrates with popular frameworks such as Django, FastAPI and Flask.\n\n## \u26a1 Key Features\n* Inject services and configuration.\n* Interfaces and abstract classes.\n* Factory pattern.\n* Singleton and transient dependencies.\n* Framework-agnostic.\n* Apply the container as a decorator.\n* Service Locator.\n\n## \ud83d\udccb Quickstart\n\nTo showcase the basics of Wireup, we will create a container able to inject the following:\n\n* A `WeatherService` that queries a fictional weather api, needs an api key and, a `KeyValueStore` to cache request data and an async http client\n* `KeyValueStore` itself needs a `redis_url` denoting the server it will connect to to query/store data.\n\nThese services will then be retrieved in a `/weather/forecast` endpoint that requires `WeatherService` to provide weather information.\n\n``` mermaid\ngraph LR\n    A --> C\n    B --> D\n    C --> D\n    D --> E\n    F --> D\n\n    A[\u2699\ufe0f redis_url]\n    B[\u2699\ufe0f weather_api_key]\n    C[\ud83d\udc0d KeyValueStore]\n    D[\ud83d\udc0d WeatherService]\n    E[\ud83c\udf0e /weather/forecast]\n    F[\ud83c\udfed HttpClient]\n```\n\n**1. Set up**\n\n### 1. Setup\n\nInstall wireup using pip or your favorite package manager.\n\n```shell\n$ pip install wireup\n```\n\nThe first step is to create a container.\n\n```python\nimport wireup\n\ncontainer = wireup.create_container(\n    # Parameters serve as application/service configuration.\n    parameters={\n        \"redis_url\": os.environ[\"APP_REDIS_URL\"],\n        \"weather_api_key\": os.environ[\"APP_WEATHER_API_KEY\"],\n    },\n    # Let the container know where service registrations are located.\n    service_modules=[services]\n)\n```\n\nParameters are configuration your application needs. Such as an api key, database url, or other settings.\nService modules is a list of top-level python modules containing service definitions this container needs to know about.\n\n\n### 2. Define services\n\nThe container uses configuration metadata from annotations and types to define services and the dependencies between them.\nThis means that the service declaration is self-contained and does not require additional setup for most use cases.\n\n\n#### \ud83d\udc0d `KeyValueStore`\nTo create `KeyValueStore`, all we need is the `redis_url` parameter.\nThe `@service` decorator tells Wireup this is a service, and we simply need to tell the container via annotated types\nto fetch the value of the `redis_url` parameter for `dsn`. \n\n\n```python\nfrom wireup import service, Inject\nfrom typing_extensions import Annotated\n\n@service\nclass KeyValueStore:\n    def __init__(self, dsn: Annotated[str, Inject(param=\"redis_url\")]) -> None:\n        self.client = redis.from_url(dsn)\n```\n\n#### \ud83d\udc0d `WeatherService`\nCreating `WeatherService` is also straightforward. The `@service` decorator is used to let Wireup know this is a service\nand we use the same syntax as above for the `api_key`. Class dependencies do not need additional annotations in this case.\n\n```python\n@service\nclass WeatherService:\n    def __init(\n        self,\n        api_key: Annotated[str, Inject(param=\"weather_api_key\")],\n        kv_store: KeyValueStore,\n        client: aiohttp.ClientSession,\n    ) -> None:\n        self.api_key = api_key\n        self.kv_store = kv_store\n```\n\n#### \ud83c\udfed `aiohttp.ClientSession`\n\nThe http client making requests cannot be instantiated directly as we need to enter an async context manager.\nTo accomodate such cases, Wireup allows you to use functions to create dependencies. \nThese can be sync/async as well as regular or generator functions if cleanup needs to take place.\n\nFactories can define their dependencies in the function's signature.\n\n**Note:** When using generator factories make sure to call `container.close` (or `container.aclose()` for async generators)\nwhen the application is terminating for the necessary cleanup to take place.\n\n```python title=\"services/factories.py\"\n@service\nasync def make_http_client() -> AsyncIterator[aiohttp.ClientSession]:\n    async with aiohttp.ClientSession() as client:\n        yield client\n```\n\n> [!TIP] \n> If using annotations is not suitable for your project, you can use factories as shown above to create all dependencies.\n> This lets you keep service definitions devoid of Wireup references.\n\n### 3. Use\n\nUse the container as a service locator or apply it as a decorator.\n\nThe container instance provides an `autowire` method that when applied\nto a function will cause the container to pass the dependencies\nwhen the function is called.\n\n```python title=\"views/posts.py\"  hl_lines=\"2 3\"\n@app.get(\"/weather/forecast\")\n@container.autowire\nasync def get_forecast_view(weather_service: WeatherService):\n    return await weather_service.get_forecast(...)\n```\n\nAlternatively you can use the container's ability to function as a service locator.\nSimply call `.get` on the container instance with the type you wish to retrieve.\n\n```python title=\"views/posts.py\"  hl_lines=\"3\"\n@app.get(\"/weather/forecast\")\nasync def get_forecast_view():\n    weather_service = container.get(WeatherService)\n    return await weather_service.get_forecast(...)\n```\n\n#### 3.5 Integrate\n\nWhile Wireup is framework-agnostic, usage can be simplified when using it alongside one of the integrations.\nA key benefit of the integrations, is removing the need to have a global container variable\nand the need to decorate injection targets in the frameworks.\n\nEach integration also comes with additional goodies specific to that framework.\n\n- [Django](integrations/django.md)\n- [FastAPI](integrations/fastapi.md)\n- [Flask](integrations/flask.md)\n\n### 4. Test\n\nWireup does not patch your services, which means they can be instantiated and tested independently of the container.\n\nTo substitute dependencies on autowired targets such as views in a web application you can override dependencies with new ones on the fly.\n\n\n```python\nwith container.override.service(WeatherService, new=test_weather_service):\n    response = client.get(\"/weather/forecast\")\n```\n\nRequests to inject `WeatherService` during the lifetime of the context manager \nwill result in `test_weather_service` being injected instead.\n\n## \ud83d\udcda Documentation\n\nFor more information [check out the documentation](https://maldoinc.github.io/wireup)\n\n## \ud83c\udfae Demo application\n\nA demo flask application is available at [maldoinc/wireup-demo](https://github.com/maldoinc/wireup-demo)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Python Dependency Injection Library",
    "version": "0.15.1",
    "project_urls": {
        "Changelog": "https://github.com/maldoinc/wireup/releases",
        "Documentation": "https://maldoinc.github.io/wireup/",
        "Homepage": "https://github.com/maldoinc/wireup",
        "Repository": "https://github.com/maldoinc/wireup"
    },
    "split_keywords": [
        "flask",
        " django",
        " injector",
        " dependency injection",
        " dependency injection container",
        " dependency injector"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2b1df9437e5f9f65a492fe03ac01f401cf2f4502e18eb968436bbb43c0b8b9a6",
                "md5": "b4926ea26147fd42211ce08ba22c5955",
                "sha256": "b55d3eb161205cec9c26cdbe56ca21554693633d664561fa5c41f9394f8a2290"
            },
            "downloads": -1,
            "filename": "wireup-0.15.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "b4926ea26147fd42211ce08ba22c5955",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.8",
            "size": 32487,
            "upload_time": "2025-01-04T19:20:41",
            "upload_time_iso_8601": "2025-01-04T19:20:41.395841Z",
            "url": "https://files.pythonhosted.org/packages/2b/1d/f9437e5f9f65a492fe03ac01f401cf2f4502e18eb968436bbb43c0b8b9a6/wireup-0.15.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3f7f2f9fd18d76b9326f107e53d938c92b4955c5fbf05a7a9f36fa79b893a58b",
                "md5": "38050df3c81fc41c760b80c857e77ecd",
                "sha256": "7f7e85422597618519385322b0acefaa0db3263679b33e455ca6f0eb231dcbd2"
            },
            "downloads": -1,
            "filename": "wireup-0.15.1.tar.gz",
            "has_sig": false,
            "md5_digest": "38050df3c81fc41c760b80c857e77ecd",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.8",
            "size": 27378,
            "upload_time": "2025-01-04T19:20:43",
            "upload_time_iso_8601": "2025-01-04T19:20:43.042137Z",
            "url": "https://files.pythonhosted.org/packages/3f/7f/2f9fd18d76b9326f107e53d938c92b4955c5fbf05a7a9f36fa79b893a58b/wireup-0.15.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-04 19:20:43",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "maldoinc",
    "github_project": "wireup",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "wireup"
}
        
Elapsed time: 4.54430s