Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 0 additions & 2 deletions .flake8

This file was deleted.

23 changes: 3 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,13 @@ on:
branches: [main]

jobs:
lint:
name: Lint
ruff:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup python
uses: actions/setup-python@v5
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
python-version: 3.11

- name: Check black
uses: psf/black@stable
with:
options: "--check --verbose --line-length 140"
src: "./fastapi_mctools"
version: "~= 22.0"

- name: Check flake8
uses: py-actions/flake8@v2
with:
path: "./fastapi_mctools"

test:
name: Test after Lint
Expand Down
9 changes: 4 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11
python-version: 3.12
- name: Install dependencies
run: |
pip install poetry
poetry config pypi-token.pypi ${{ secrets.PYPI_API }}
pip install uv

- name: Build and publish
run: |
poetry build
poetry publish
uv build
uv publish --token ${{ secrets.PYPI_API }}
17 changes: 6 additions & 11 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 22.3.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.11
hooks:
- id: black
args: [--line-length=140]
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
args: [--max-line-length=140]
- id: ruff-check
args: [--fix]
- id: ruff-format
Empty file removed fastapi_mctools/aws/__init__.py
Empty file.
77 changes: 0 additions & 77 deletions fastapi_mctools/aws/s3.py

This file was deleted.

17 changes: 6 additions & 11 deletions fastapi_mctools/commands/_templates/pre-commit-tpl
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 22.3.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.11
hooks:
- id: black
args: [--line-length=140]
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
args: [--max-line-length=140]
- id: ruff-check
args: [--fix]
- id: ruff-format
9 changes: 0 additions & 9 deletions fastapi_mctools/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ def main():
pass


@main.command(help="uvicorn 실행 command 출력")
def show():
"""
일일히 터미널에 입력하기 귀찮아서 만든 명령어
출력되면 복사해서 터미널에 입력하면 됨
"""
click.echo("uvicorn app.main:app --reload --host 127.0.0.1")


@main.command("dev", help="uvicorn 서버 실행")
@click.option("--host", default="127.0.0.1", help="서버 호스트 주소")
@click.option("--port", default=8000, help="서버 포트 번호")
Expand Down
46 changes: 46 additions & 0 deletions fastapi_mctools/lifespan.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@


class EventExecutor:
"""
Collects and executes startup/shutdown events for the application.
"""

def __init__(self) -> None:
self.events = []

def add_event(self, event: Event, *args, **kwargs) -> None:
"""
Add an event (function or coroutine) to the executor.
"""
self.events.append((event, args, kwargs))

async def run(self) -> None:
"""
Run all registered events in order. Await if coroutine, call if sync.
"""
for event, args, kwargs in self.events:
if asyncio.iscoroutinefunction(event):
await event(*args, **kwargs)
Expand All @@ -27,32 +37,62 @@ async def run(self) -> None:


class Lifespan:
"""
Context manager and event system for managing application startup and shutdown events.

Example:
lifespan = Lifespan()
lifespan.add_startup(startup_func)
lifespan.add_shutdown(shutdown_func)
async with lifespan:
...
"""

def __init__(self, timeout: int | None = None) -> None:
"""
Initialize the Lifespan manager.
:param timeout: Optional timeout (seconds) for startup/shutdown events.
"""
self.startup_events = EventExecutor()
self.shutdown_runner = EventExecutor()
self.timeout = timeout
self.__states = None

def add_startup(self, event: Event, *args, **kwargs) -> None:
"""
Register a function/coroutine to run on application startup.
"""
self.startup_events.add_event(event, *args, **kwargs)

def add_shutdown(self, event: Event, *args, **kwargs) -> None:
"""
Register a function/coroutine to run on application shutdown.
"""
self.shutdown_runner.add_event(event, *args, **kwargs)

@property
def states(self) -> State:
"""
Get the current states dictionary (if set).
"""
if self.__states is not None and not isinstance(self.__states, dict):
raise ValueError("States must be a dictionary or None")
return self.__states

@states.setter
def states(self, value: State) -> None:
"""
Set the states dictionary or a callable returning a dictionary.
"""
if isinstance(value, Callable):
self.__states = value()
else:
self.__states = value

async def __aenter__(self) -> "Lifespan":
"""
Enter the lifespan context, running all startup events.
"""
if self.timeout:
async with asyncio.timeout(self.timeout):
await self.startup_events.run()
Expand All @@ -61,6 +101,9 @@ async def __aenter__(self) -> "Lifespan":
return self

async def __aexit__(self, exc_type: _ExceptionType, exc: _Exception, tb: _TracebackType) -> None:
"""
Exit the lifespan context, running all shutdown events.
"""
if self.timeout:
async with asyncio.timeout(self.timeout):
await self.shutdown_runner.run()
Expand All @@ -69,6 +112,9 @@ async def __aexit__(self, exc_type: _ExceptionType, exc: _Exception, tb: _Traceb
return None

async def lifespan(self, app: ASGIApp) -> AsyncGenerator[Any, Any]:
"""
ASGI lifespan protocol integration. Use as FastAPI lifespan context.
"""
await self.__aenter__()
try:
yield self.states
Expand Down
7 changes: 6 additions & 1 deletion fastapi_mctools/orms/sqlalchemy/sync_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ def get_all(
return db.query(*columns).all()

def get_all_by_filters(
self, db: Session, page: int | None = None, page_size: int | None = None, columns: list[str] = None, **kwargs
self,
db: Session,
page: int | None = None,
page_size: int | None = None,
columns: list[str] = None,
**kwargs,
) -> list[T]:
"""
SELECT * or ... FROM {table_name(self.model)} WHERE {key} = {value} AND ...
Expand Down
4 changes: 3 additions & 1 deletion fastapi_mctools/utils/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
P = Union[ParamSpecArgs, ParamSpecKwargs]


def ensure_session(func: Callable[..., Coroutine[Any, Any, httpx.Response]]) -> Callable[..., Coroutine[Any, Any, httpx.Response]]:
def ensure_session(
func: Callable[..., Coroutine[Any, Any, httpx.Response]],
) -> Callable[..., Coroutine[Any, Any, httpx.Response]]:
@wraps(func)
async def wrapper(self: T, *args: P, **kwargs: P) -> httpx.Response:
if self.session is None:
Expand Down
Loading