Skip to content

Commit b59f69d

Browse files
committed
Update: [Server] fastapi-utils への依存を排除して psutil を 6.x にアップグレードし、ruff と pyright を main の依存関係から除外
ref: fastapiutils/fastapi-utils#368
1 parent 13db7ed commit b59f69d

File tree

7 files changed

+161
-54
lines changed

7 files changed

+161
-54
lines changed

Readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ nano config.yaml
951951
# 一時的な Poetry 仮想環境の構築 (poetry run task update-thirdparty の実行に必要)
952952
cd server/
953953
poetry env use 3.11
954-
poetry install --no-root
954+
poetry install --no-root --with dev
955955

956956
# 最新のサードパーティーライブラリを GitHub Actions からダウンロード
957957
## 本番環境用のスタンドアローン版 Python もサードパーティーライブラリに含まれている
@@ -968,7 +968,7 @@ rm -rf .venv/
968968
poetry env use /Develop/KonomiTV/server/thirdparty/Python/bin/python
969969

970970
# 依存パッケージのインストール
971-
poetry install --no-root
971+
poetry install --no-root --with dev
972972
```
973973

974974
### サーバーの起動

server/app/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from fastapi.middleware.cors import CORSMiddleware
1111
from fastapi.responses import FileResponse, JSONResponse
1212
from fastapi.staticfiles import StaticFiles
13-
from fastapi_utils.tasks import repeat_every
1413

1514
from app import logging
1615
from app.config import Config, LoadConfig
@@ -43,6 +42,7 @@
4342
)
4443
from app.streams.LiveStream import LiveStream
4544
from app.utils.edcb.EDCBTuner import EDCBTuner
45+
from app.utils.FastAPITaskUtil import repeat_every
4646

4747

4848
# もし Config() の実行時に AssertionError が発生した場合は、LoadConfig() を実行してサーバー設定データをロードする

server/app/metadata/ThumbnailGenerator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ async def __generateThumbnailTile(self) -> bool:
406406
try:
407407
# 動画の長さと tile_interval_sec から候補フレーム数を算出
408408
# ※ ceil() を使うことで、端数でも切り捨てずに確実にすべての区間をカバー
409-
num_candidates = int(math.ceil(self.duration_sec / self.tile_interval_sec))
409+
num_candidates = math.ceil(self.duration_sec / self.tile_interval_sec)
410410
if num_candidates < 1:
411411
num_candidates = 1
412412

server/app/streams/VideoEncodingTask.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ def WriteAllToPipe(pipe_fd: int, data: bytes) -> None:
863863
# Future がまだ未完了の場合にのみ実行
864864
if current_segment is not None:
865865
# 判定に用いる次セグメント開始時刻
866-
next_segment_start_timestamp = current_segment.start_dts + int(round(current_segment.duration_seconds * ts.HZ))
866+
next_segment_start_timestamp = current_segment.start_dts + round(current_segment.duration_seconds * ts.HZ)
867867
# logging.debug(
868868
# f'{self.video_stream.log_prefix} Current Timestamp: {current_timestamp_unwrapped} / '
869869
# f'Next Segment Start Timestamp: {next_segment_start_timestamp}'
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# fastapi-utils が psutil 5.x に依存している問題が1年以上解決されないため、以下のソースコードをそのまま移植した
2+
# ref: https://github.com/fastapiutils/fastapi-utils/issues/368
3+
# ref: https://github.com/fastapiutils/fastapi-utils/blob/master/fastapi_utils/tasks.py
4+
5+
from __future__ import annotations
6+
7+
import asyncio
8+
import logging
9+
import warnings
10+
from collections.abc import Callable, Coroutine
11+
from functools import wraps
12+
from traceback import format_exception
13+
from typing import Any
14+
15+
from starlette.concurrency import run_in_threadpool
16+
17+
18+
NoArgsNoReturnFuncT = Callable[[], None]
19+
NoArgsNoReturnAsyncFuncT = Callable[[], Coroutine[Any, Any, None]]
20+
ExcArgNoReturnFuncT = Callable[[Exception], None]
21+
ExcArgNoReturnAsyncFuncT = Callable[[Exception], Coroutine[Any, Any, None]]
22+
NoArgsNoReturnAnyFuncT = NoArgsNoReturnFuncT | NoArgsNoReturnAsyncFuncT
23+
ExcArgNoReturnAnyFuncT = ExcArgNoReturnFuncT | ExcArgNoReturnAsyncFuncT
24+
NoArgsNoReturnDecorator = Callable[[NoArgsNoReturnAnyFuncT], NoArgsNoReturnAsyncFuncT]
25+
26+
27+
async def _handle_func(func: NoArgsNoReturnAnyFuncT) -> None:
28+
if asyncio.iscoroutinefunction(func):
29+
await func()
30+
else:
31+
await run_in_threadpool(func)
32+
33+
34+
async def _handle_exc(exc: Exception, on_exception: ExcArgNoReturnAnyFuncT | None) -> None:
35+
if on_exception:
36+
if asyncio.iscoroutinefunction(on_exception):
37+
await on_exception(exc)
38+
else:
39+
await run_in_threadpool(on_exception, exc)
40+
41+
42+
def repeat_every(
43+
*,
44+
seconds: float,
45+
wait_first: float | None = None,
46+
logger: logging.Logger | None = None,
47+
raise_exceptions: bool = False,
48+
max_repetitions: int | None = None,
49+
on_complete: NoArgsNoReturnAnyFuncT | None = None,
50+
on_exception: ExcArgNoReturnAnyFuncT | None = None,
51+
) -> NoArgsNoReturnDecorator:
52+
"""
53+
This function returns a decorator that modifies a function so it is periodically re-executed after its first call.
54+
55+
The function it decorates should accept no arguments and return nothing. If necessary, this can be accomplished
56+
by using `functools.partial` or otherwise wrapping the target function prior to decoration.
57+
58+
Parameters
59+
----------
60+
seconds: float
61+
The number of seconds to wait between repeated calls
62+
wait_first: float (default None)
63+
If not None, the function will wait for the given duration before the first call
64+
logger: Optional[logging.Logger] (default None)
65+
Warning: This parameter is deprecated and will be removed in the 1.0 release.
66+
The logger to use to log any exceptions raised by calls to the decorated function.
67+
If not provided, exceptions will not be logged by this function (though they may be handled by the event loop).
68+
raise_exceptions: bool (default False)
69+
Warning: This parameter is deprecated and will be removed in the 1.0 release.
70+
If True, errors raised by the decorated function will be raised to the event loop's exception handler.
71+
Note that if an error is raised, the repeated execution will stop.
72+
Otherwise, exceptions are just logged and the execution continues to repeat.
73+
See https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.set_exception_handler for more info.
74+
max_repetitions: Optional[int] (default None)
75+
The maximum number of times to call the repeated function. If `None`, the function is repeated forever.
76+
on_complete: Optional[Callable[[], None]] (default None)
77+
A function to call after the final repetition of the decorated function.
78+
on_exception: Optional[Callable[[Exception], None]] (default None)
79+
A function to call when an exception is raised by the decorated function.
80+
"""
81+
82+
def decorator(func: NoArgsNoReturnAnyFuncT) -> NoArgsNoReturnAsyncFuncT:
83+
"""
84+
Converts the decorated function into a repeated, periodically-called version of itself.
85+
"""
86+
87+
@wraps(func)
88+
async def wrapped() -> None:
89+
async def loop() -> None:
90+
if wait_first is not None:
91+
await asyncio.sleep(wait_first)
92+
93+
repetitions = 0
94+
while max_repetitions is None or repetitions < max_repetitions:
95+
try:
96+
await _handle_func(func)
97+
98+
except Exception as exc:
99+
if logger is not None:
100+
warnings.warn(
101+
"'logger' is to be deprecated in favor of 'on_exception' in the 1.0 release.",
102+
DeprecationWarning,
103+
)
104+
formatted_exception = "".join(format_exception(type(exc), exc, exc.__traceback__))
105+
logger.error(formatted_exception)
106+
if raise_exceptions:
107+
warnings.warn(
108+
"'raise_exceptions' is to be deprecated in favor of 'on_exception' in the 1.0 release.",
109+
DeprecationWarning,
110+
)
111+
raise exc
112+
await _handle_exc(exc, on_exception)
113+
114+
repetitions += 1
115+
await asyncio.sleep(seconds)
116+
117+
if on_complete:
118+
await _handle_func(on_complete)
119+
120+
asyncio.ensure_future(loop()) # noqa: RUF006
121+
122+
return wrapped
123+
124+
return decorator

server/poetry.lock

Lines changed: 26 additions & 44 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/pyproject.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,42 @@ biim = { url = "https://github.com/tsukumijima/biim/releases/download/v1.11.0-po
2323
colorama = "^0.4.6"
2424
elevate = "^0.1.3"
2525
fastapi = "^0.115.6"
26-
fastapi-utils = "^0.8.0"
2726
hashids = "^1.3.1"
2827
httpx = {version = "^0.28.1", extras = ["http2"]}
2928
opencv-python-headless = "^4.11.0.86"
3029
passlib = {version = "^1.7.4", extras = ["bcrypt"]}
3130
pillow = "^11.3.0"
3231
ping3 = "^4.0.8"
33-
psutil = "^5.9.6"
32+
psutil = "^6.0.0"
3433
puremagic = "^1.28"
3534
py7zr = "^0.22.0"
3635
pydantic = "^2.10.3"
3736
pymediainfo = "^6.1.0"
38-
pyright = "^1.1.403"
3937
python-jose = {version = "^3.4.0", extras = ["cryptography"]}
4038
python-multipart = "^0.0.20"
4139
pywin32 = {version = "^311", platform = "win32"}
4240
requests = "^2.32.4"
4341
rich = "^13.9.4"
4442
"ruamel.yaml" = "^0.18.10"
45-
ruff = "^0.9.1"
4643
sse-starlette = "^2.1.3"
4744
taskipy = "^1.14.1"
4845
tortoise-orm = "^0.23.0"
4946
typer = "^0.15.4"
5047
typing-extensions = "^4.12.2"
5148
typing-inspect = "^0.9.0"
5249
tweepy = "^4.14.0"
53-
tweepy-authlib = "^1.7.1"
50+
tweepy-authlib = "^1.7.3"
5451
tzdata = "^2024.2"
5552
uvicorn = {version = "^0.34.0", extras = ["standard"]}
5653
uvloop = {version = "^0.21.0", platform = "linux"}
5754
watchfiles = "^1.0.4"
5855
winloop = {version = "^0.1.8", platform = "win32"}
5956
zendriver = "^0.15.0"
6057

58+
[tool.poetry.group.dev.dependencies]
59+
pyright = "^1.1.403"
60+
ruff = "^0.9.1"
61+
6162
[build-system]
6263
requires = ["poetry-core"]
6364
build-backend = "poetry.core.masonry.api"

0 commit comments

Comments
 (0)