setup basic project with Python Robyn
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s
This commit is contained in:
@@ -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})"
|
||||
Reference in New Issue
Block a user