Initial commit: add Gitea PyPI publish workflow

This commit is contained in:
2026-07-06 11:02:19 -05:00
commit b0d2873cbc
40 changed files with 5831 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
"""Pydantic models for the Conduit API."""
from __future__ import annotations
from .common import (
AuditLogEntry,
CallerIdentity,
ConduitModel,
CreatedKey,
CreateKeyRequest,
EmptyResponse,
KeyInfo,
ServiceInfo,
)
from .compute import (
CloneVmRequest,
ComputeTicketRequest,
ComputeTicketResponse,
CreateContainerRequest,
CreateSnapshotRequest,
CreateVmRequest,
MigrateVmRequest,
NodeTasksQuery,
StorageQuery,
TaskResponse,
UploadStorageRequest,
)
from .dns import (
AddZoneRequest,
BatchRecordUpdate,
BatchUpdateRecordsRequest,
CacheFlushRequest,
ServerReloadRequest,
)
from .media_ingest import (
CreateIngestJobRequest,
CreateIngestSubscriptionRequest,
DeleteIngestJobRequest,
IngestJob,
IngestJobResponse,
IngestJobsBucketed,
IngestPreset,
IngestSubscription,
IngestVersionInfo,
PatchIngestSubscriptionRequest,
)
from .media_library import (
LibraryLoginRequest,
LibraryLoginResponse,
LibrarySearchQuery,
LibrarySession,
LibrarySystemInfo,
)
from .sms import (
DeleteMmsResponse,
DeleteSmsResponse,
MmsMediaItem,
MmsMediaResponse,
MmsRecord,
SendMmsDefaultRequest,
SendMmsRequest,
SendSmsDefaultRequest,
SendSmsRequest,
SendSmsResponse,
SmsRecord,
)
from .torrents import (
AddTorrentRequest,
DeleteTorrentRequest,
DownloadLinkResponse,
FreeSpaceResponse,
MoveQueueRequest,
MoveTorrentRequest,
PatchTorrentRequest,
PortTestResponse,
RenamePathRequest,
)
from .wiki import WikiPageRaw, WriteWikiPageRequest, WriteWikiPageResponse
__all__ = [
"AddTorrentRequest",
"AddZoneRequest",
"AuditLogEntry",
"BatchRecordUpdate",
"BatchUpdateRecordsRequest",
"CacheFlushRequest",
"CallerIdentity",
"CloneVmRequest",
"ComputeTicketRequest",
"ComputeTicketResponse",
"ConduitModel",
"CreateContainerRequest",
"CreateIngestJobRequest",
"CreateIngestSubscriptionRequest",
"CreateSnapshotRequest",
"CreateVmRequest",
"CreatedKey",
"CreateKeyRequest",
"DeleteIngestJobRequest",
"DeleteMmsResponse",
"DeleteSmsResponse",
"DeleteTorrentRequest",
"DownloadLinkResponse",
"EmptyResponse",
"FreeSpaceResponse",
"IngestJob",
"IngestJobResponse",
"IngestJobsBucketed",
"IngestPreset",
"IngestSubscription",
"IngestVersionInfo",
"KeyInfo",
"LibraryLoginRequest",
"LibraryLoginResponse",
"LibrarySearchQuery",
"LibrarySession",
"LibrarySystemInfo",
"MigrateVmRequest",
"MmsMediaItem",
"MmsMediaResponse",
"MmsRecord",
"MoveQueueRequest",
"MoveTorrentRequest",
"NodeTasksQuery",
"PatchIngestSubscriptionRequest",
"PatchTorrentRequest",
"PortTestResponse",
"RenamePathRequest",
"SendMmsDefaultRequest",
"SendMmsRequest",
"SendSmsDefaultRequest",
"SendSmsRequest",
"SendSmsResponse",
"ServerReloadRequest",
"ServiceInfo",
"SmsRecord",
"StorageQuery",
"TaskResponse",
"UploadStorageRequest",
"WikiPageRaw",
"WriteWikiPageRequest",
"WriteWikiPageResponse",
]

View File

@@ -0,0 +1,85 @@
"""Shared Pydantic models used across multiple Conduit façades."""
from __future__ import annotations
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict
class ConduitModel(BaseModel):
"""Base model for all Conduit API models."""
model_config = ConfigDict(
populate_by_name=True,
str_strip_whitespace=True,
extra="ignore",
)
class CallerIdentity(ConduitModel):
"""Response from GET /auth/me."""
subject: str
actor_type: str
auth_method: str
scopes: list[str]
grantable_scopes: list[str]
class ServiceInfo(ConduitModel):
"""Provider registry entry from GET /services."""
id: str
name: str
prefixes: list[str]
enabled: bool
class KeyInfo(ConduitModel):
"""API key metadata from GET /keys."""
key_prefix: str
display_name: str | None = None
scopes: list[str]
created_at: datetime
expires_at: datetime | None = None
last_used_at: datetime | None = None
revoked_at: datetime | None = None
class CreatedKey(ConduitModel):
"""One-time response from POST /keys."""
key: str
key_prefix: str
display_name: str | None = None
scopes: list[str]
created_at: datetime
expires_at: datetime | None = None
class CreateKeyRequest(ConduitModel):
"""Body for POST /keys."""
display_name: str | None = None
scopes: list[str]
class AuditLogEntry(ConduitModel):
"""Single entry from GET /admin/audit."""
timestamp: datetime
actor_subject: str
actor_type: str | None = None
scope: str
endpoint: str
result: str
metadata: dict[str, Any] | None = None
class EmptyResponse(ConduitModel):
"""Placeholder for endpoints that return no meaningful body."""
pass

View File

@@ -0,0 +1,96 @@
"""Models for the Proxmox compute façade."""
from __future__ import annotations
from pydantic import ConfigDict, Field
from .common import ConduitModel
class _AllowExtra(ConduitModel):
"""Mixin that allows unknown fields to be forwarded to the API."""
model_config = ConfigDict(
populate_by_name=True,
str_strip_whitespace=True,
extra="allow",
)
class ComputeTicketRequest(ConduitModel):
"""Body for POST /compute/auth/ticket."""
username: str
password: str
class ComputeTicketResponse(ConduitModel):
"""Response from POST /compute/auth/ticket."""
ticket: str
csrfpreventiontoken: str = Field(..., alias="CSRFPreventionToken")
username: str
class TaskResponse(ConduitModel):
"""Response from async compute actions."""
task: str
class CreateVmRequest(_AllowExtra):
"""Body for POST /compute/vms."""
node: str
vmid: int | None = None
class CreateContainerRequest(_AllowExtra):
"""Body for POST /compute/containers."""
node: str
ostemplate: str
vmid: int | None = None
class CloneVmRequest(ConduitModel):
"""Body for POST /compute/vms/{vmid}/clone."""
newid: int
name: str | None = None
full: bool | None = None
target: str | None = None
class MigrateVmRequest(ConduitModel):
"""Body for POST /compute/vms/{vmid}/migrate."""
target: str
online: bool | None = None
class CreateSnapshotRequest(ConduitModel):
"""Body for POST /compute/vms/{vmid}/snapshots."""
snapname: str
vmstate: bool | None = None
description: str | None = None
class UploadStorageRequest(ConduitModel):
"""Body for POST /compute/storage/{storage}/upload."""
content: str
filename: str
class NodeTasksQuery(ConduitModel):
"""Query parameters for GET /compute/nodes/{node}/tasks."""
limit: int | None = None
class StorageQuery(ConduitModel):
"""Query parameters for storage listing endpoints."""
node: str | None = None

View File

@@ -0,0 +1,56 @@
"""Models for the BIND9 DNS façade."""
from __future__ import annotations
from typing import Literal
from pydantic import ConfigDict
from .common import ConduitModel
class _AllowExtra(ConduitModel):
"""Mixin that allows unknown fields to be forwarded to the API."""
model_config = ConfigDict(
populate_by_name=True,
str_strip_whitespace=True,
extra="allow",
)
class ServerReloadRequest(ConduitModel):
"""Body for POST /dns/servers/{server_id}/reload."""
mode: Literal["reload", "reconfig"] = "reload"
class CacheFlushRequest(ConduitModel):
"""Body for POST /dns/servers/{server_id}/cache/flush."""
mode: Literal["flush", "flushname", "flushtree"] = "flush"
name: str | None = None
class AddZoneRequest(_AllowExtra):
"""Body for POST /dns/zones."""
zone: str
klass: str | None = None
view: str | None = None
class BatchRecordUpdate(ConduitModel):
"""Single update within a record batch update."""
op: Literal["add", "delete", "replace"] | None = None
name: str | None = None
ttl: int | None = None
rtype: str | None = None
rdata: str | list[str] | None = None
class BatchUpdateRecordsRequest(ConduitModel):
"""Body for POST /dns/zones/{zone}/records:batchUpdate."""
updates: list[BatchRecordUpdate]

View File

@@ -0,0 +1,21 @@
"""Models for the top-level Conduit gateway endpoints."""
from __future__ import annotations
from datetime import datetime
from pydantic import Field
from .common import ConduitModel
class AuditLogQuery(ConduitModel):
"""Query parameters for GET /admin/audit."""
actor_subject: str | None = None
scope: str | None = None
result: str | None = None
endpoint: str | None = None
since: datetime | None = None
until: datetime | None = None
limit: int | None = Field(default=None, ge=1, le=500)

View File

@@ -0,0 +1,100 @@
"""Models for the MeTube media ingestion façade."""
from __future__ import annotations
from .common import ConduitModel
class IngestJob(ConduitModel):
"""A single MeTube job."""
id: str
url: str | None = None
status: str | None = None
quality: str | None = None
format: str | None = None
folder: str | None = None
title: str | None = None
error: str | None = None
class IngestJobsBucketed(ConduitModel):
"""Response from GET /media/ingest/jobs - jobs grouped by status."""
done: list[IngestJob] | None = None
downloading: list[IngestJob] | None = None
error: list[IngestJob] | None = None
pending: list[IngestJob] | None = None
class CreateIngestJobRequest(ConduitModel):
"""Body for POST /media/ingest/jobs."""
url: str
quality: str | None = None
format: str | None = None
codec: str | None = None
folder: str | None = None
auto_start: bool | None = None
add_meta_tags: bool | None = None
class IngestJobResponse(ConduitModel):
"""Response from POST /media/ingest/jobs."""
id: str
status: str | None = None
class IngestPreset(ConduitModel):
"""MeTube quality/format preset."""
name: str | None = None
quality: str | None = None
format: str | None = None
class IngestSubscription(ConduitModel):
"""MeTube subscription."""
id: str | None = None
url: str
name: str | None = None
quality: str | None = None
format: str | None = None
folder: str | None = None
auto_start: bool | None = None
class CreateIngestSubscriptionRequest(ConduitModel):
"""Body for POST /media/ingest/subscriptions."""
url: str
name: str | None = None
quality: str | None = None
format: str | None = None
folder: str | None = None
auto_start: bool | None = None
class PatchIngestSubscriptionRequest(ConduitModel):
"""Body for PATCH /media/ingest/subscriptions/{sub_id}."""
name: str | None = None
quality: str | None = None
format: str | None = None
folder: str | None = None
auto_start: bool | None = None
class IngestVersionInfo(ConduitModel):
"""Response from GET /media/ingest/version."""
metube_version: str | None = None
ytdlp_version: str | None = None
class DeleteIngestJobRequest(ConduitModel):
"""Optional body for DELETE /media/ingest/jobs/{job_id}."""
delete_from_file: bool = False

View File

@@ -0,0 +1,48 @@
"""Models for the Jellyfin media library façade."""
from __future__ import annotations
from .common import ConduitModel
class LibraryLoginRequest(ConduitModel):
"""Body for POST /media/library/auth/login."""
username: str
password: str = ""
class LibraryLoginResponse(ConduitModel):
"""Response from POST /media/library/auth/login."""
access_token: str | None = None
user_id: str | None = None
class LibrarySearchQuery(ConduitModel):
"""Query parameters for GET /media/library/search."""
term: str | None = None
item_type: str | None = None
filters: str | None = None
parent_id: str | None = None
limit: int | None = None
offset: int | None = None
fields: str | None = None
class LibrarySession(ConduitModel):
"""Active Jellyfin session."""
id: str | None = None
user_id: str | None = None
device_name: str | None = None
client: str | None = None
class LibrarySystemInfo(ConduitModel):
"""Response from GET /media/library/system/info."""
version: str | None = None
id: str | None = None
public: bool | None = None

View File

@@ -0,0 +1,99 @@
"""Models for the SMS/MMS façade."""
from __future__ import annotations
from pydantic import Field
from .common import ConduitModel
class SmsRecord(ConduitModel):
"""Single SMS record returned by GET /sms."""
id: int | str
did: str | None = None
dst: str | None = None
message: str | None = None
date: str | None = None
class MmsRecord(ConduitModel):
"""Single MMS record returned by GET /mms."""
id: int | str
did: str | None = None
dst: str | None = None
message: str | None = None
date: str | None = None
media1: str | None = None
media2: str | None = None
media3: str | None = None
class SendSmsRequest(ConduitModel):
"""Body for POST /sms/send."""
did: str
dst: str
message: str = Field(..., max_length=160)
class SendSmsDefaultRequest(ConduitModel):
"""Body for POST /sms/send/default."""
dst: str
message: str = Field(..., max_length=160)
class SendMmsRequest(ConduitModel):
"""Body for POST /mms/send."""
did: str
dst: str
message: str | None = Field(default=None, max_length=2048)
media1: str | None = None
media2: str | None = None
media3: str | None = None
class SendMmsDefaultRequest(ConduitModel):
"""Body for POST /mms/send/default."""
dst: str
message: str | None = Field(default=None, max_length=2048)
media1: str | None = None
media2: str | None = None
media3: str | None = None
class SendSmsResponse(ConduitModel):
"""Response from POST /sms/send."""
id: int | str
class MmsMediaItem(ConduitModel):
"""Media item within an MMS media response."""
url: str | None = None
type: str | None = None
class MmsMediaResponse(ConduitModel):
"""Response from GET /mms/{id}/media."""
id: int | str
date: str | None = None
media: list[MmsMediaItem] | None = None
class DeleteSmsResponse(ConduitModel):
"""Response from DELETE /sms/{id}."""
status: str
class DeleteMmsResponse(ConduitModel):
"""Response from DELETE /mms/{id}."""
status: str

View File

@@ -0,0 +1,86 @@
"""Models for the Transmission torrents façade."""
from __future__ import annotations
from typing import Literal
from pydantic import Field, model_validator
from .common import ConduitModel
class AddTorrentRequest(ConduitModel):
"""Body for POST /torrents."""
url: str | None = None
filename: str | None = None
metainfo: str | None = None
labels: list[str] | None = None
paused: bool | None = None
download_dir: str | None = None
@model_validator(mode="after")
def _require_source(self) -> AddTorrentRequest:
if not any((self.url, self.filename, self.metainfo)):
raise ValueError("One of url, filename, or metainfo is required")
return self
class MoveQueueRequest(ConduitModel):
"""Body for POST /torrents/queue/move."""
direction: Literal["top", "up", "down", "bottom"]
class FreeSpaceResponse(ConduitModel):
"""Response from GET /torrents/free-space."""
path: str
size_bytes: int | None = Field(default=None, alias="size-bytes")
total_size: int | None = None
class PortTestResponse(ConduitModel):
"""Response from GET /torrents/port-test."""
port_is_open: bool | None = None
class DownloadLinkResponse(ConduitModel):
"""Response from GET /torrents/{id}/download-link."""
torrent_link: str | None = None
file_links: list[str] | None = None
class PatchTorrentRequest(ConduitModel):
"""Body for PATCH /torrents/{id}."""
labels: list[str] | None = None
priority: Literal[-1, 0, 1] | None = None
files_wanted: list[int] | None = None
files_unwanted: list[int] | None = None
speed_limit_down: int | None = None
speed_limit_up: int | None = None
queue_position: int | None = None
ratio_limit: float | None = None
class MoveTorrentRequest(ConduitModel):
"""Body for POST /torrents/{id}/move."""
location: str
move: bool = True
class RenamePathRequest(ConduitModel):
"""Body for POST /torrents/{id}/rename-path."""
path: str
name: str
class DeleteTorrentRequest(ConduitModel):
"""Optional body for DELETE /torrents/{id}."""
delete_local_data: bool = False

View File

@@ -0,0 +1,28 @@
"""Models for the DokuWiki publishing façade."""
from __future__ import annotations
from .common import ConduitModel
class WikiPageRaw(ConduitModel):
"""Response from GET /wiki/pages/{page_id}/raw."""
id: str
text: str
class WriteWikiPageRequest(ConduitModel):
"""Body for PUT /wiki/pages/{page_id}."""
text: str
summary: str = "API publish"
verify: bool = True
class WriteWikiPageResponse(ConduitModel):
"""Response from PUT /wiki/pages/{page_id}."""
id: str
url: str
verified: bool