Compare commits

..

3 Commits

Author SHA1 Message Date
c0a3901951 move grpc to proper location 2026-02-06 08:22:50 -03:00
318741d8ca corrected generator bug and models 2026-02-06 08:09:55 -03:00
022baa407f ignore media folder contents 2026-02-06 07:48:52 -03:00
16 changed files with 50 additions and 33 deletions

3
.gitignore vendored
View File

@@ -17,7 +17,8 @@ env/
*.pot *.pot
*.pyc *.pyc
db.sqlite3 db.sqlite3
media/ media/*
!media/.gitkeep
# Node # Node
node_modules/ node_modules/

View File

@@ -25,7 +25,7 @@ def system_status():
def worker_status(): def worker_status():
"""Worker status from gRPC.""" """Worker status from gRPC."""
try: try:
from mpr.grpc.client import get_client from grpc.client import get_client
client = get_client() client = get_client()
status = client.get_worker_status() status = client.get_worker_status()

View File

@@ -26,3 +26,6 @@ MPR_EXECUTOR=local
GRPC_HOST=grpc GRPC_HOST=grpc
GRPC_PORT=50051 GRPC_PORT=50051
GRPC_MAX_WORKERS=10 GRPC_MAX_WORKERS=10
# Vite
VITE_ALLOWED_HOSTS=your-domain.local

View File

@@ -99,7 +99,7 @@ services:
build: build:
context: .. context: ..
dockerfile: ctrl/Dockerfile dockerfile: ctrl/Dockerfile
command: python -m mpr.grpc.server command: python -m grpc.server
ports: ports:
- "50052:50051" - "50052:50051"
environment: environment:

View File

@@ -29,18 +29,18 @@ python -m modelgen from-schema \
# Protobuf for gRPC # Protobuf for gRPC
python -m modelgen from-schema \ python -m modelgen from-schema \
--schema schema/models \ --schema schema/models \
--output mpr/grpc/protos/worker.proto \ --output grpc/protos/worker.proto \
--targets proto --targets proto
# Generate gRPC stubs from proto # Generate gRPC stubs from proto
echo "Generating gRPC stubs..." echo "Generating gRPC stubs..."
python -m grpc_tools.protoc \ python -m grpc_tools.protoc \
-I mpr/grpc/protos \ -I grpc/protos \
--python_out=mpr/grpc \ --python_out=grpc \
--grpc_python_out=mpr/grpc \ --grpc_python_out=grpc \
mpr/grpc/protos/worker.proto grpc/protos/worker.proto
# Fix relative import in generated grpc stub # Fix relative import in generated grpc stub
sed -i 's/^import worker_pb2/from . import worker_pb2/' mpr/grpc/worker_pb2_grpc.py sed -i 's/^import worker_pb2/from . import worker_pb2/' grpc/worker_pb2_grpc.py
echo "Done!" echo "Done!"

0
media/.gitkeep Normal file
View File

View File

@@ -1,7 +1,8 @@
""" """
Django ORM Models - GENERATED FILE Django ORM Models - GENERATED FILE
Do not edit directly. Regenerate using modelgen. Do not edit directly. Modify schema/models/*.py and run:
python schema/generate.py --django
""" """
import uuid import uuid
@@ -12,6 +13,7 @@ class AssetStatus(models.TextChoices):
READY = "ready", "Ready" READY = "ready", "Ready"
ERROR = "error", "Error" ERROR = "error", "Error"
class JobStatus(models.TextChoices): class JobStatus(models.TextChoices):
PENDING = "pending", "Pending" PENDING = "pending", "Pending"
PROCESSING = "processing", "Processing" PROCESSING = "processing", "Processing"
@@ -19,13 +21,14 @@ class JobStatus(models.TextChoices):
FAILED = "failed", "Failed" FAILED = "failed", "Failed"
CANCELLED = "cancelled", "Cancelled" CANCELLED = "cancelled", "Cancelled"
class MediaAsset(models.Model): class MediaAsset(models.Model):
"""A video/audio file registered in the system.""" """A video/audio file registered in the system."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
filename = models.CharField(max_length=500) filename = models.CharField(max_length=500)
file_path = models.CharField(max_length=1000) file_path = models.CharField(max_length=1000)
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING) status = models.CharField(max_length=20, choices=AssetStatus.choices, default=AssetStatus.PENDING)
error_message = models.TextField(blank=True, default='') error_message = models.TextField(blank=True, default='')
file_size = models.BigIntegerField(null=True, blank=True) file_size = models.BigIntegerField(null=True, blank=True)
duration = models.FloatField(null=True, blank=True, default=None) duration = models.FloatField(null=True, blank=True, default=None)
@@ -89,7 +92,7 @@ class TranscodeJob(models.Model):
output_filename = models.CharField(max_length=500) output_filename = models.CharField(max_length=500)
output_path = models.CharField(max_length=1000, null=True, blank=True) output_path = models.CharField(max_length=1000, null=True, blank=True)
output_asset_id = models.UUIDField(null=True, blank=True) output_asset_id = models.UUIDField(null=True, blank=True)
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING) status = models.CharField(max_length=20, choices=JobStatus.choices, default=JobStatus.PENDING)
progress = models.FloatField(default=0.0) progress = models.FloatField(default=0.0)
current_frame = models.IntegerField(null=True, blank=True, default=None) current_frame = models.IntegerField(null=True, blank=True, default=None)
current_time = models.FloatField(null=True, blank=True, default=None) current_time = models.FloatField(null=True, blank=True, default=None)

View File

@@ -40,7 +40,7 @@ DJANGO_TYPES: dict[Any, str] = {
"list": "models.JSONField(default=list, blank=True)", "list": "models.JSONField(default=list, blank=True)",
"text": "models.TextField(blank=True, default='')", "text": "models.TextField(blank=True, default='')",
"bigint": "models.BigIntegerField({opts})", "bigint": "models.BigIntegerField({opts})",
"enum": "models.CharField(max_length=20, choices=Status.choices{opts})", "enum": "models.CharField(max_length=20, choices={enum_name}.choices{opts})",
} }
DJANGO_SPECIAL: dict[str, str] = { DJANGO_SPECIAL: dict[str, str] = {
@@ -69,9 +69,11 @@ TS_RESOLVERS: dict[Any, Callable[[Any], str]] = {
"UUID": lambda _: "string", "UUID": lambda _: "string",
"datetime": lambda _: "string", "datetime": lambda _: "string",
"dict": lambda _: "Record<string, unknown>", "dict": lambda _: "Record<string, unknown>",
"list": lambda base: f"{TS_RESOLVERS.get(get_args(base)[0], lambda _: 'string')(None)}[]" "list": lambda base: (
f"{TS_RESOLVERS.get(get_args(base)[0], lambda _: 'string')(None)}[]"
if get_args(base) if get_args(base)
else "string[]", else "string[]"
),
"enum": lambda base: base.__name__, "enum": lambda base: base.__name__,
} }
@@ -80,9 +82,11 @@ PROTO_RESOLVERS: dict[Any, Callable[[Any], str]] = {
int: lambda _: "int32", int: lambda _: "int32",
float: lambda _: "float", float: lambda _: "float",
bool: lambda _: "bool", bool: lambda _: "bool",
"list": lambda base: f"repeated {PROTO_RESOLVERS.get(get_args(base)[0], lambda _: 'string')(None)}" "list": lambda base: (
f"repeated {PROTO_RESOLVERS.get(get_args(base)[0], lambda _: 'string')(None)}"
if get_args(base) if get_args(base)
else "repeated string", else "repeated string"
),
} }
@@ -172,13 +176,14 @@ def resolve_django_type(name: str, type_hint: Any, default: Any) -> str:
# Enum # Enum
if isinstance(base, type) and issubclass(base, Enum): if isinstance(base, type) and issubclass(base, Enum):
enum_name = base.__name__
extra = [] extra = []
if optional: if optional:
extra.append("null=True, blank=True") extra.append("null=True, blank=True")
if default is not dc.MISSING and isinstance(default, Enum): if default is not dc.MISSING and isinstance(default, Enum):
extra.append(f"default=Status.{default.name}") extra.append(f"default={enum_name}.{default.name}")
return DJANGO_TYPES["enum"].format( return DJANGO_TYPES["enum"].format(
opts=", " + ", ".join(extra) if extra else "" enum_name=enum_name, opts=", " + ", ".join(extra) if extra else ""
) )
# Text fields # Text fields
@@ -216,6 +221,15 @@ def resolve_django_type(name: str, type_hint: Any, default: Any) -> str:
return DJANGO_TYPES[str].format(max_length=255, opts=", " + opts if opts else "") return DJANGO_TYPES[str].format(max_length=255, opts=", " + opts if opts else "")
def generate_django_enum(enum_cls: type) -> list[str]:
"""Generate Django TextChoices enum."""
lines = [f"class {enum_cls.__name__}(models.TextChoices):"]
for member in enum_cls:
label = member.name.replace("_", " ").title()
lines.append(f' {member.name} = "{member.value}", "{label}"')
return lines
def generate_django_model(cls: type) -> list[str]: def generate_django_model(cls: type) -> list[str]:
"""Generate Django model lines from dataclass.""" """Generate Django model lines from dataclass."""
lines = [ lines = [
@@ -227,17 +241,6 @@ def generate_django_model(cls: type) -> list[str]:
hints = get_type_hints(cls) hints = get_type_hints(cls)
fields = {f.name: f for f in dc.fields(cls)} fields = {f.name: f for f in dc.fields(cls)}
# Add Status inner class for enum fields
for type_hint in hints.values():
base, _ = unwrap_optional(type_hint)
if isinstance(base, type) and issubclass(base, Enum):
lines.append(" class Status(models.TextChoices):")
for member in base:
label = member.name.replace("_", " ").title()
lines.append(f' {member.name} = "{member.value}", "{label}"')
lines.append("")
break
# Fields # Fields
for name, type_hint in hints.items(): for name, type_hint in hints.items():
if name.startswith("_"): if name.startswith("_"):
@@ -283,7 +286,13 @@ def generate_django() -> str:
"", "",
] ]
# Generate enums first
body = [] body = []
for enum_cls in ENUMS:
body.extend(generate_django_enum(enum_cls))
body.extend(["", ""])
# Generate models
for cls in DATACLASSES: for cls in DATACLASSES:
body.extend(generate_django_model(cls)) body.extend(generate_django_model(cls))
body.extend(["", ""]) body.extend(["", ""])

View File

@@ -6,6 +6,7 @@ export default defineConfig({
server: { server: {
host: "0.0.0.0", host: "0.0.0.0",
port: 5173, port: 5173,
allowedHosts: process.env.VITE_ALLOWED_HOSTS?.split(",") || [],
proxy: { proxy: {
"/api": { "/api": {
target: "http://fastapi:8702", target: "http://fastapi:8702",

View File

@@ -8,8 +8,8 @@ from typing import Any, Dict, Optional
from celery import shared_task from celery import shared_task
from grpc.server import update_job_progress
from worker.executor import get_executor from worker.executor import get_executor
from worker_grpc.server import update_job_progress
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)