Skip to content

Commit

Permalink
feat: ✨ support async factories and (async)generator factories (#42)
Browse files Browse the repository at this point in the history
* chore: 🧹 replace rodi with fork (dij)

* feat: ✨ support async factories and generator factories

emualating fastapi, this commit adds support for generator
factories and async (and async-generators) for dependency
factories; moves from rodi to custom fork "dij" in order
to support this new feature

* chore: 🧹 update deps
  • Loading branch information
lucas-labs authored Oct 8, 2024
1 parent f0062c4 commit 072b39e
Show file tree
Hide file tree
Showing 29 changed files with 1,957 additions and 1,648 deletions.
13 changes: 2 additions & 11 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ line-length = 100
indent-width = 4
output-format = "full"

# [format]
# quote-style = "preserve"
[format]
quote-style = "preserve"

[lint]
select = ["E", "F", "Q", "I", "ANN", "ASYNC", "W", "S", "A"]
Expand All @@ -12,9 +12,6 @@ ignore = [
"ANN101", # missing-type-self https://beta.ruff.rs/docs/rules/missing-type-self/
"ANN102", # missing-type-cls https://beta.ruff.rs/docs/rules/missing-type-cls/
"ANN401", # any-type https://beta.ruff.rs/docs/rules/any-type/
# "ANN204", # missing-return-type https://beta.ruff.rs/docs/rules/missing-return-type/
# "E402", # module-import-not-at-top-of-file https://beta.ruff.rs/docs/rules/module-import-not-at-top-of-file/
# "F401" # unused-import https://beta.ruff.rs/docs/rules/unused-import/
]

[lint.flake8-quotes]
Expand All @@ -28,11 +25,5 @@ mypy-init-return = true


[lint.extend-per-file-ignores]
# ignore in tests:
# S101 (use of assert)
# ANN001 (missing function argument type annotation)
# ANN201 (missing return type annotation )
# E701 (multiple statements on one line)
# F401 (unused import)
"**/**/test/**" = ["S101", "ANN001", "ANN201", "E701"]
"**/**/test_*.py" = ["S101", "ANN001", "ANN201", "E701"]
132 changes: 66 additions & 66 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,66 +1,66 @@
{
"files.exclude": {
// 📙 sub-libs
"tools": true,

// ⚙️ config
// "pyproject.toml": true,
"**/**/*.code-workspace": true,
"poetry.lock": true,
"poetry.toml": true,

// 🧼 linters & styles
".ruff.toml": true,
"coco.yml": true,
".env": true,

// 🧪 tests
// "tests": true,
// "noxfile.py": true,
// "conftest.py": true,
// ".nox": true,
// "htmlcov": true,
// "coverage": true,
// ".coverage": true,

// 🗑️
".task": true, // task-go
".venv": true,
".vscode": true,
".ruff_cache": true,
"**/**/__pycache__": true,
".git": true,
".gitignore": true,
".pytest_cache": true,
".github": true,

// 📝 docs
// "**/**/README.md": true
},
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
"editor.defaultFormatter": "ms-python.black-formatter"
},

"black-formatter.importStrategy": "fromEnvironment",

"evenBetterToml.schema.associations": {
"\\.?coco(\\.yml|\\.yaml|rc)$": "https://gist.githubusercontent.com/lucas-labs/0cb798e4b6c7fc720c7111c40d215c21/raw/cb068efd7d73794e738a5240a80f0cb729883553/coco-schema.json"
},

"python.testing.pytestArgs": [
"tests",
"--verbose",
"--no-header",
"--import-mode=importlib",
],

"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true
}
{
"files.exclude": {
// 📙 sub-libs
"tools": true,
"dist": true,

// ⚙️ config
"**/**/*.code-workspace": true,
"poetry.lock": true,
"poetry.toml": true,

// 🧼 linters & styles
".ruff.toml": true,
"coco.yml": true,
".env": true,

// 🧪 tests
// "tests": true,
"noxfile.py": true,
"conftest.py": true,
".nox": true,
// "htmlcov": true,
// "coverage": true,
// ".coverage": true,

// 🗑️
".task": true, // task-go
".venv": true,
".vscode": true,
".ruff_cache": true,
"**/**/__pycache__": true,
".git": true,
".gitignore": true,
".pytest_cache": true,
".github": true,

// 📝 docs
// "**/**/README.md": true
},
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
"editor.defaultFormatter": "ms-python.black-formatter"
},

"black-formatter.importStrategy": "fromEnvironment",

"evenBetterToml.schema.associations": {
"\\.?coco(\\.yml|\\.yaml|rc)$": "https://gist.githubusercontent.com/lucas-labs/0cb798e4b6c7fc720c7111c40d215c21/raw/cb068efd7d73794e738a5240a80f0cb729883553/coco-schema.json"
},

"python.testing.pytestArgs": [
"tests",
"--verbose",
"--no-header",
"--import-mode=importlib",
],

"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true
}
3 changes: 2 additions & 1 deletion example/app/app_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
from pest.metadata.types.injectable_meta import ValueProvider

from .data.data import TodoRepo
from .modules.auth._module import AuthModule
from .modules.todo.module import TodoModule


@module(
imports=[TodoModule],
imports=[TodoModule, AuthModule],
providers=[
# singleton
ValueProvider(provide=TodoRepo, use_value=TodoRepo())
Expand Down
2 changes: 1 addition & 1 deletion example/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def format_record(record: Any) -> str:
logging={
'intercept': [('uvicorn*', LogLevel.DEBUG), 'pest*', 'fastapi'],
'level': LogLevel.DEBUG,
'format': format_record,
# 'format': format_record,
'access_log': True,
'sinks': [{'sink': 'example/logs/app.log', 'rotation': '1 week', 'format': format_record}],
},
Expand Down
17 changes: 17 additions & 0 deletions example/app/modules/auth/_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from pest.decorators.module import module
from pest.metadata.types.injectable_meta import FactoryProvider, Scope

from .controller import AuthController
from .db.session import Session, get_session
from .service import AuthService


@module(
controllers=[AuthController],
providers=[
AuthService,
FactoryProvider(provide=Session, use_factory=get_session, scope=Scope.SCOPED),
],
)
class AuthModule:
pass
37 changes: 37 additions & 0 deletions example/app/modules/auth/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from hashlib import sha256
from time import time

from pydantic import BaseModel

from pest import controller, post
from pest.exceptions import UnauthorizedException

from .db.session import User
from .service import AuthService


class LoginBodyReq(BaseModel):
username: str
password: str


class LoginResponse(BaseModel):
token: str

@classmethod
def from_user(cls, user: User) -> 'LoginResponse':
token = sha256(f'{user.username}{time()}'.encode()).hexdigest()
return cls(token=token)


@controller('/auth', tags=['Auth'])
class AuthController:
auth: AuthService # 💉 automatically injected

@post('/login')
async def login(self, dto: LoginBodyReq) -> LoginResponse:
user = await self.auth.login(dto.username, dto.password)
if not user:
raise UnauthorizedException('Invalid credentials')

return LoginResponse.from_user(user)
54 changes: 54 additions & 0 deletions example/app/modules/auth/db/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import uuid
from typing import Any, AsyncGenerator

from pydantic import BaseModel, Field


class User(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
password: str = Field(..., min_length=3, max_length=50)


USERS = [
User(username='mr.spock', password='f4sc1n4t1ng'), # noqa: S106
User(username='captain.kirk', password='b34m-m3-up-sc077y'), # noqa: S106
User(username='captain.picard', password='m4k3-1t-s0'), # noqa: S106
]


class Session:
id: uuid.UUID
connected: bool = False

def __init__(self) -> None:
self.id = uuid.uuid4()

def disconnect(self) -> None:
print(f'Disconnecting session {self.id}')
self.connected = False

def connect(self) -> None:
print(f'Connecting session {self.id}')
self.connected = True

async def __aenter__(self) -> 'Session':
self.connect()
return self

async def __aexit__(self, _exc_type: Any, _exc_value: Any, _traceback: Any) -> None:
self.disconnect()

async def select_user_where_username_eq(self, username: str) -> User | None:
if not self.connected:
raise Exception('Session is disconnected')

for user in USERS:
if user.username == username:
return user
return None


# session generator
async def get_session() -> AsyncGenerator[Session, None]:
async with Session() as session:
yield session
13 changes: 13 additions & 0 deletions example/app/modules/auth/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from example.app.modules.auth.db.session import Session, User


class AuthService:
db: Session # 💉 automatically injected (scoped to request, using async generator)

async def login(self, username: str, password: str) -> User | None:
user = await self.db.select_user_where_username_eq(username)
if not user:
return None
if user.password == password:
return user
return None
18 changes: 18 additions & 0 deletions example/bruno/auth/login.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
meta {
name: login
type: http
seq: 1
}

post {
url: {{url}}/auth/login
body: json
auth: none
}

body:json {
{
"username": "captain.kirk",
"password": "b34m-m3-up-sc077y"
}
}
6 changes: 6 additions & 0 deletions example/bruno/bruno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"version": "1",
"name": "pest",
"type": "collection",
"ignore": ["node_modules", ".git"]
}
3 changes: 3 additions & 0 deletions example/bruno/environments/local.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vars {
url: http://localhost:8000
}
17 changes: 17 additions & 0 deletions example/bruno/todo/create-new.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
meta {
name: create-new
type: http
seq: 3
}

post {
url: {{url}}/todo
body: json
auth: none
}

body:json {
{
"title": "Don't forget to sleep"
}
}
21 changes: 21 additions & 0 deletions example/bruno/todo/delete.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
meta {
name: delete
type: http
seq: 4
}

delete {
url: {{url}}/todo/:id
body: none
auth: none
}

params:path {
id: 5
}

body:json {
{
"title": "Don't forget to sleep"
}
}
11 changes: 11 additions & 0 deletions example/bruno/todo/get-all.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: get-all
type: http
seq: 1
}

get {
url: {{url}}/todo
body: none
auth: none
}
Loading

0 comments on commit 072b39e

Please sign in to comment.