setup basic project with Python Robyn
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s

This commit is contained in:
2026-08-02 17:39:41 +02:00
parent 9d01925097
commit 8eecd47399
11 changed files with 900 additions and 45 deletions
+79
View File
@@ -0,0 +1,79 @@
"""
Main entypoint for kontor-robyn
"""
from contextvars import ContextVar
import time
from robyn import Request, Response, Robyn
from kontor_robyn.apis.version1 import api_router
from kontor_robyn.db.models import Base, engine
from .core.log_conf import logger
_request_start: ContextVar[float] = ContextVar("request_start")
def create_tables() -> None:
"""
Create table with SQLAlchemy
"""
logger.info("create tables")
Base.metadata.create_all(bind=engine)
logger.info("tables created")
def add_logging(app: Robyn) -> None:
"""
set logging for every request
"""
def logging_before_handler(request: Request):
_request_start.set(time.perf_counter())
return request
def log_request(request: Request, response: Response):
"""
log used time and return code
"""
start = _request_start.get(None)
duration_ms = (time.perf_counter() - start) * 1000 if start is not None else 0.0
logger.info(
"%s %s -> %s (%.2fms)",
request.method,
request.url.path,
response.status_code,
duration_ms,
)
return response
logging_before_handler = app.before_request()(logging_before_handler) # type: ignore # noqa: F823
log_request = app.after_request()(log_request) # type: ignore # noqa: F823
def include_router(app: Robyn):
"""
Add routes
"""
app.include_router(api_router)
def start_application() -> None:
"""
Start Robyn framework and configure routes
"""
app = Robyn(__file__)
add_logging(app)
include_router(app)
create_tables()
app.start(port=8280)
def main() -> None:
"""
Start Framework Robyn
"""
logger.info("starting kontor-robyn")
start_application()
logger.info("kontor-robyn exited")
@@ -0,0 +1,16 @@
"""
Provide subrouter for api/v1
"""
from robyn import SubRouter
api_router = SubRouter(prefix="/api/v1")
@api_router.get("/health")
def health():
"""
Return health info
"""
return {"status": "ok"}
@@ -0,0 +1,43 @@
"""
Define logging configuration for kontor-robyn
"""
import logging
import logging.config
from typing import Any
LOGGING_CONFIG: dict[str, Any] = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s",
},
"access": {
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
},
"access_file": {
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
"use_colors": False,
},
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
},
"error": {
"formatter": "access",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
},
"loggers": {
"root": {"handlers": ["default"], "level": "INFO", "propagate": False},
"kontor": {"handlers": ["default"], "level": "INFO", "propagate": False},
},
}
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("kontor")
@@ -0,0 +1,46 @@
"""
Definition of models
"""
from typing import Optional
import uuid
from datetime import datetime
from sqlalchemy import create_engine, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, sessionmaker
DATABASE_URL = "sqlite:///./kontor.db"
engine = create_engine(DATABASE_URL)
SESSION_LOCAL = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Base(DeclarativeBase):
"""
Base class for all Models
"""
class BaseMixin:
"""
Base mixin to provide standard fields id, version, created_date, last_modified_date
"""
id: Mapped[str] = mapped_column(primary_key=True, default=str(uuid.uuid4()))
created_date: Mapped[datetime] = mapped_column(default=func.now())
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
version: Mapped[int] = mapped_column(default=0)
class BaseVideoMixin:
"""
Base mixin to provide additional fields for video links.
"""
cloud_link: Mapped[Optional[str]]
file_name: Mapped[Optional[str]]
path: Mapped[str]
review: Mapped[bool]
title: Mapped[str]
url: Mapped[str]
should_download: Mapped[bool]
@@ -0,0 +1,102 @@
from typing import List, Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, relationship, mapped_column
from kontor_robyn.db.models import Base, BaseMixin, BaseVideoMixin
class MediaFile(Base, BaseMixin, BaseVideoMixin):
"""
MediaFile represents video link.
"""
__tablename__ = "media_file"
media_actor_files: Mapped[List["MediaActorFile"]] = relationship(
back_populates="media_file"
)
def __repr__(self):
return f"MediaFile({self.id} {self.title} {self.title})"
def __str__(self):
return f"{self.title}({self.id})"
def update_title(self):
"""
Update title from url.
"""
class MediaActor(Base, BaseMixin):
"""
MediaActor represents actor for MediaFile
"""
__tablename__ = "media_actor"
name: Mapped[str]
url: Mapped[Optional[str]] = mapped_column(unique=True)
media_actor_files = relationship("MediaActorFile")
def __repr__(self) -> str:
return f"MediaActor({self.id} {self.name} {self.url})"
def __str__(self) -> str:
return f"{self.url}({self.id})"
class MediaActorFile(Base, BaseMixin):
"""
MediaActorFile defines the connection between MediaFile and MediaActor
"""
__tablename__ = "media_actor_file"
media_actor_id: Mapped[str] = mapped_column(
ForeignKey("media_actor.id"), nullable=False
)
media_actor: Mapped[MediaActor] = relationship(back_populates="media_actor_files")
media_file_id: Mapped[str] = mapped_column(
ForeignKey("media_file.id"), nullable=True
)
media_file: Mapped[MediaFile] = relationship(back_populates="media_actor_files")
def __repr__(self):
return f"MediaActorFile({self.id} {self.media_actor_id} {self.media_file_id})"
def __str__(self) -> str:
return f"{self.id} {self.media_actor_id} {self.media_file_id}"
class MediaArticle(Base, BaseMixin):
"""
MediaArticle represents a link to an article
"""
__tablename__ = "media_article"
review: Mapped[bool]
title: Mapped[str]
url: Mapped[str] = mapped_column(unique=True)
class MediaVideo(Base, BaseMixin):
"""
MediaFile represents video link.
"""
__tablename__ = "media_video"
cloud_link: Mapped[str]
file_name: Mapped[str]
path: Mapped[str]
review: Mapped[bool]
title: Mapped[str]
url: Mapped[str] = mapped_column(unique=True)
should_download: Mapped[bool]
def __repr__(self):
return f"MediaFile({self.id} {self.title} {self.url})"
def __str__(self):
if self.title is None:
return f"{self.url}({self.id})"
else:
return f"{self.title}({self.id})"