mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
chore(video): drop internal spec refs + minio/test suppressions (folded hygiene)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
A thin wrapper around a singleton `minio.Minio` built from settings. The client
|
||||
is sync (minio-py is sync); every call site wraps the call in
|
||||
`asyncio.to_thread` — same pattern as `remotion_client._save`.
|
||||
`asyncio.to_thread` — same pattern as `video_renderer_client._save`.
|
||||
|
||||
Unconfigured guard: when `settings.minio_endpoint` is empty, `get_client()`
|
||||
returns `None`. The write/serve paths (chunks 3/4) check `get_client()` and
|
||||
@@ -26,15 +26,12 @@ from roboco.config import settings
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
_client: Minio | None = None
|
||||
_initialised = False
|
||||
_cache: dict[str, Minio | None] = {}
|
||||
|
||||
|
||||
def _reset_client() -> None:
|
||||
"""Test-only: drop the cached singleton so the next `get_client()` rebuilds."""
|
||||
global _client, _initialised # noqa: PLW0603 - singleton cache, by design
|
||||
_client = None
|
||||
_initialised = False
|
||||
_cache.clear()
|
||||
|
||||
|
||||
def get_client() -> Minio | None:
|
||||
@@ -44,25 +41,24 @@ def get_client() -> Minio | None:
|
||||
write/serve paths fall back to local disk. Settings are load-time, so a
|
||||
plain module-level singleton is fine (no runtime-config drift to guard).
|
||||
"""
|
||||
global _client, _initialised # noqa: PLW0603 - singleton cache, by design
|
||||
if _initialised:
|
||||
return _client
|
||||
_initialised = True
|
||||
if "c" in _cache:
|
||||
return _cache["c"]
|
||||
endpoint = settings.minio_endpoint.strip()
|
||||
if not endpoint:
|
||||
_client = None
|
||||
_cache["c"] = None
|
||||
return None
|
||||
parsed = urlparse(endpoint if "://" in endpoint else f"//{endpoint}")
|
||||
secure = parsed.scheme == "https"
|
||||
host = parsed.netloc or endpoint # no scheme → use as-is
|
||||
_client = Minio(
|
||||
client = Minio(
|
||||
endpoint=host,
|
||||
access_key=settings.minio_access_key or None,
|
||||
secret_key=settings.minio_secret_key or None,
|
||||
secure=secure,
|
||||
region=settings.minio_region or None,
|
||||
)
|
||||
return _client
|
||||
_cache["c"] = client
|
||||
return client
|
||||
|
||||
|
||||
def put_object(data: bytes, key: str) -> None:
|
||||
|
||||
@@ -6,9 +6,6 @@ refresh-token rotation. Mirrors ``x_client.py``'s Null/Live/build shape;
|
||||
``NullTikTokPoster`` lives in ``video_post_service`` (the Protocol's home)
|
||||
and is re-exported here as the "no credentials" branch of
|
||||
``build_tiktok_poster``.
|
||||
|
||||
See docs/internal/specs/2026-07-04-video-generation-remotion-design.md §11.3
|
||||
for the verified sequence and constraints.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -48,7 +45,7 @@ _INIT_URL = f"{_API_BASE}/post/publish/inbox/video/init/"
|
||||
_STATUS_URL = f"{_API_BASE}/post/publish/status/fetch/"
|
||||
_TOKEN_URL = f"{_API_BASE}/oauth/token/"
|
||||
|
||||
# Asymmetric chunking (§11.3): every chunk 5-64 MB except the final one, which
|
||||
# Asymmetric chunking: every chunk 5-64 MB except the final one, which
|
||||
# absorbs the remainder up to 128 MB — a small dangling tail is folded into
|
||||
# one larger final PUT instead of being sent as its own tiny chunk.
|
||||
_CHUNK_SIZE_BYTES = 64 * 1024 * 1024
|
||||
@@ -130,7 +127,7 @@ class LiveTikTokPoster(TikTokPoster):
|
||||
async def upload_to_inbox(
|
||||
self, *, mp4_path: str, caption: str
|
||||
) -> TikTokUploadResult:
|
||||
# Inbox-upload's init request carries only source_info (§11.3) — no
|
||||
# Inbox-upload's init request carries only source_info — no
|
||||
# title/caption field exists on this endpoint; the creator composes
|
||||
# the post manually in-app. Kept in the signature only because the
|
||||
# TikTokPoster Protocol is shared with future direct-post modes.
|
||||
|
||||
@@ -7,10 +7,8 @@ user-context signed, reusing ``x_client``'s signer — the same
|
||||
tweet. Mirrors ``x_client.py``'s Null/Live/build shape; ``NullXVideoPoster``
|
||||
itself lives in ``video_post_service`` (the Protocol's home) and is
|
||||
re-exported here as the "no credentials" branch of ``build_x_video_poster``.
|
||||
|
||||
See docs/internal/specs/2026-07-04-video-generation-remotion-design.md §11.2
|
||||
for the verified sequence and constraints (no documented v2 chunk ceiling;
|
||||
this client chunks conservatively).
|
||||
The v2 media-upload API documents no hard chunk ceiling, so this client
|
||||
chunks conservatively.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -45,7 +43,7 @@ _API_BASE = "https://api.x.com/2"
|
||||
_MEDIA_UPLOAD_URL = f"{_API_BASE}/media/upload"
|
||||
_TWEETS_URL = f"{_API_BASE}/tweets"
|
||||
_MEDIA_CATEGORY = "tweet_video"
|
||||
# Conservative chunk size — v2 documents no hard ceiling (§11.2); 4 MB keeps
|
||||
# Conservative chunk size — v2 documents no hard ceiling; 4 MB keeps
|
||||
# each append well within any reasonable request-body limit.
|
||||
_CHUNK_SIZE_BYTES = 4 * 1024 * 1024
|
||||
_STATUS_POLL_MAX_ATTEMPTS = 60
|
||||
|
||||
@@ -166,7 +166,9 @@ async def _seed_draft(
|
||||
return task
|
||||
|
||||
|
||||
def _build_app(db_session: AsyncSession, role: AgentRole, agent_id: UUID) -> FastAPI:
|
||||
def _build_app(
|
||||
db_session: AsyncSession | None, role: AgentRole, agent_id: UUID
|
||||
) -> FastAPI:
|
||||
app = FastAPI()
|
||||
app.include_router(video_router, prefix="/api/video")
|
||||
app.include_router(tiktok_router, prefix="/api/tiktok")
|
||||
@@ -614,7 +616,7 @@ async def test_media_serves_from_minio_when_configured(
|
||||
monkeypatch.setattr(minio_client, "get_object_stream", _minio_stream)
|
||||
|
||||
# CEO 200 — streamed from MinIO.
|
||||
app = _build_app(None, AgentRole.CEO, uuid4()) # type: ignore[arg-type]
|
||||
app = _build_app(None, AgentRole.CEO, uuid4())
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
resp = await client.get(f"/api/video/posts/{task_id}/media?cut=vertical")
|
||||
@@ -624,7 +626,7 @@ async def test_media_serves_from_minio_when_configured(
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
# Non-CEO 403 — _require_ceo still gates end-to-end (no presigned URL).
|
||||
app = _build_app(None, AgentRole.DEVELOPER, uuid4()) # type: ignore[arg-type]
|
||||
app = _build_app(None, AgentRole.DEVELOPER, uuid4())
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
resp = await client.get(f"/api/video/posts/{task_id}/media?cut=vertical")
|
||||
@@ -647,7 +649,7 @@ async def test_media_falls_back_to_local_file_when_minio_unconfigured(
|
||||
_patch_task_service(monkeypatch, _make_task(str(vertical), task_id))
|
||||
monkeypatch.setattr(minio_client, "get_client", lambda: None)
|
||||
|
||||
app = _build_app(None, AgentRole.CEO, uuid4()) # type: ignore[arg-type]
|
||||
app = _build_app(None, AgentRole.CEO, uuid4())
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
resp = await client.get(f"/api/video/posts/{task_id}/media?cut=vertical")
|
||||
@@ -685,7 +687,7 @@ async def test_media_falls_back_to_local_file_when_minio_missing(
|
||||
|
||||
monkeypatch.setattr(minio_client, "get_object_stream", _stream_must_not_be_called)
|
||||
|
||||
app = _build_app(None, AgentRole.CEO, uuid4()) # type: ignore[arg-type]
|
||||
app = _build_app(None, AgentRole.CEO, uuid4())
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
resp = await client.get(f"/api/video/posts/{task_id}/media?cut=vertical")
|
||||
|
||||
Reference in New Issue
Block a user