Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pycharm's "Reformat code" && "Optimize imports" of code examples. #4

Merged
merged 4 commits into from
Mar 28, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 75 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,36 +54,36 @@ pip install 'magic-di[celery]'

```python
from fastapi import FastAPI

from magic_di import Connectable
from magic_di.fastapi import inject_app, Provide

app = inject_app(FastAPI())


class Database:
connected: bool = False
connected: bool = False

def __connect__(self):
self.connected = True
def __connect__(self):
self.connected = True

def __disconnect__(self):
self.connected = False
def __disconnect__(self):
self.connected = False


class Service(Connectable):
def __init__(self, db: Database):
self.db = db
def __init__(self, db: Database):
self.db = db

def is_connected(self):
return self.db.connected
def is_connected(self):
return self.db.connected


@app.get(path="/hello-world")
def hello_world(service: Provide[Service]) -> dict:
return {
"is_connected": service.is_connected()
}

return {
"is_connected": service.is_connected()
}
```

That's all!
Expand All @@ -107,32 +107,32 @@ Simply fetch everything needed from the environment. There is no need for an add

```python
from dataclasses import dataclass, field

from pydantic import Field
from pydantic_settings import BaseSettings

from redis.asyncio import Redis as RedisClient, from_url


class RedisConfig(BaseSettings):
url: str = Field(validation_alias='REDIS_URL')
decode_responses: bool = Field(validation_alias='REDIS_DECODE_RESPONSES')
url: str = Field(validation_alias='REDIS_URL')
decode_responses: bool = Field(validation_alias='REDIS_DECODE_RESPONSES')


@dataclass
class Redis:
config: RedisConfig = field(default_factory=RedisConfig)
client: RedisClient = field(init=False)
config: RedisConfig = field(default_factory=RedisConfig)
client: RedisClient = field(init=False)

async def __connect__(self):
self.client = await from_url(self.config.url, decode_responses=self.config.decode_responses)
await self.client.ping()
async def __connect__(self):
self.client = await from_url(self.config.url, decode_responses=self.config.decode_responses)
await self.client.ping()

async def __disconnect__(self):
await self.client.close()
async def __disconnect__(self):
await self.client.close()

@property
def db(self) -> RedisClient:
return self.client
@property
def db(self) -> RedisClient:
return self.client


Redis() # works even without passing arguments in the constructor.
Expand All @@ -142,40 +142,41 @@ As an alternative, you can inject configs instead of using default factories.

```python
from dataclasses import dataclass, field

from pydantic import Field
from pydantic_settings import BaseSettings
from magic_di import Connectable, DependencyInjector

from redis.asyncio import Redis as RedisClient, from_url

from magic_di import Connectable, DependencyInjector


class RedisConfig(Connectable, BaseSettings):
url: str = Field(validation_alias='REDIS_URL')
decode_responses: bool = Field(validation_alias='REDIS_DECODE_RESPONSES')
url: str = Field(validation_alias='REDIS_URL')
decode_responses: bool = Field(validation_alias='REDIS_DECODE_RESPONSES')


@dataclass
class Redis:
config: RedisConfig
client: RedisClient = field(init=False)
config: RedisConfig
client: RedisClient = field(init=False)

async def __connect__(self):
self.client = await from_url(self.config.url, decode_responses=self.config.decode_responses)
await self.client.ping()
async def __connect__(self):
self.client = await from_url(self.config.url, decode_responses=self.config.decode_responses)
await self.client.ping()

async def __disconnect__(self):
await self.client.close()
async def __disconnect__(self):
await self.client.close()

@property
def db(self) -> RedisClient:
return self.client
@property
def db(self) -> RedisClient:
return self.client


injector = DependencyInjector()
redis = injector.inject(Redis)() # works even without passing arguments in the constructor.

async with injector:
await redis.db.ping()
await redis.db.ping()
```

## Using interfaces instead of implementations
Expand All @@ -185,18 +186,19 @@ Sometimes, you may not want to stick to a certain interface implementation every
from typing import Protocol

from fastapi import FastAPI

from magic_di import Connectable, DependencyInjector
from magic_di.fastapi import inject_app, Provide


class MyInterface(Protocol):
def do_something(self) -> bool:
...
def do_something(self) -> bool:
...


class MyInterfaceImplementation(Connectable):
def do_something(self) -> bool:
return True
def do_something(self) -> bool:
return True


app = inject_app(FastAPI())
Expand All @@ -207,18 +209,20 @@ injector.bind({MyInterface: MyInterfaceImplementation})

@app.get(path="/hello-world")
def hello_world(service: Provide[MyInterface]) -> dict:
return {
"result": service.do_something(),
}
return {
"result": service.do_something(),
}
```

Using `injector.bind`, you can bind implementations that will be injected everywhere the bound interface is used.

## Integration with Celery

### Function based celery tasks

```python
from celery import Celery

from magic_di.celery import get_celery_loader, InjectableCeleryTask, PROVIDE

app = Celery(
Expand All @@ -234,28 +238,32 @@ async def calculate(x: int, y: int, calculator: Calculator = PROVIDE):


### Class based celery tasks

```python
from dataclasses import dataclass

from celery import Celery

from magic_di.celery import get_celery_loader, InjectableCeleryTask, BaseCeleryConnectableDeps, PROVIDE

app = Celery(
loader=get_celery_loader(),
task_cls=InjectableCeleryTask,
)


@dataclass
class CalculatorTaskDeps(BaseCeleryConnectableDeps):
calculator: Calculator
calculator: Calculator


class CalculatorTask(InjectableCeleryTask):
deps: CalculatorTaskDeps
deps: CalculatorTaskDeps

async def run(self, x: int, y: int, smart_processor: SmartProcessor = PROVIDE):
return smart_processor.process(
await self.deps.calculator.calculate(x, y)
)
async def run(self, x: int, y: int, smart_processor: SmartProcessor = PROVIDE):
return smart_processor.process(
await self.deps.calculator.calculate(x, y)
)


app.register_task(CalculatorTask)
Expand All @@ -274,35 +282,37 @@ If you need to mock a dependency in tests, you can easily do so by using the `in
To mock clients, you can use `InjectableMock` from the `testing` module.

### Default simple mock

```python
import pytest
from fastapi.testclient import TestClient
from my_app import app

from magic_di import DependencyInjector
from magic_di.testing import InjectableMock


@pytest.fixture()
def injector():
return DependencyInjector()
return DependencyInjector()


@pytest.fixture()
def service_mock() -> Service:
return InjectableMock()
return InjectableMock()


@pytest.fixture()
def client(injector: DependencyInjector, service_mock: InjectableMock):
with injector.override({Service: service_mock.mock_cls}):
with TestClient(app) as client:
yield client
with injector.override({Service: service_mock.mock_cls}):
with TestClient(app) as client:
yield client


def test_http_handler(client):
resp = client.post('/hello-world')
resp = client.post('/hello-world')

assert resp.status_code == 200
assert resp.status_code == 200
```

### Custom mocks
Expand All @@ -314,14 +324,14 @@ from magic_di.testing import get_injectable_mock_cls

@pytest.fixture()
def service_mock() -> Service:
return SomeSmartServiceMock()
return SomeSmartServiceMock()


@pytest.fixture()
def client(injector: DependencyInjector, service_mock: Service):
with injector.override({Service: get_injectable_mock_cls(service_mock)}):
with TestClient(app) as client:
yield client
with injector.override({Service: get_injectable_mock_cls(service_mock)}):
with TestClient(app) as client:
yield client
```

## Alternatives
Expand Down
Loading