add base files
This commit is contained in:
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
*.log
|
||||||
|
*.sqlite3
|
||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
node_modules/
|
||||||
|
uv.lock
|
||||||
114
.gitea/changelog-config.json
Normal file
114
.gitea/changelog-config.json
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"title": "## 🚀 Features",
|
||||||
|
"labels": [
|
||||||
|
"add",
|
||||||
|
"Add",
|
||||||
|
"Kind/Feature",
|
||||||
|
"feat",
|
||||||
|
"Feature",
|
||||||
|
"Feat"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## 🧰 Enhancements",
|
||||||
|
"labels": [
|
||||||
|
"enhancement",
|
||||||
|
"Enhancement",
|
||||||
|
"Kind/Enhancement",
|
||||||
|
"improvement",
|
||||||
|
"Improvement",
|
||||||
|
"Kind/Improvement"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## 🐛 Fixes",
|
||||||
|
"labels": [
|
||||||
|
"fix",
|
||||||
|
"Fix",
|
||||||
|
"Kind/Bug",
|
||||||
|
"Kind/Security"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## 🧪 Upgrade",
|
||||||
|
"labels": [
|
||||||
|
"upgrade",
|
||||||
|
"Upgrade",
|
||||||
|
"Clean"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## 📝 Documentation",
|
||||||
|
"labels": [
|
||||||
|
"docs",
|
||||||
|
"Docs",
|
||||||
|
"Kind/Documentation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## 🛠️ Maintenance",
|
||||||
|
"labels": [
|
||||||
|
"maintenance",
|
||||||
|
"Maintenance",
|
||||||
|
"Kind/Maintenance",
|
||||||
|
"chore",
|
||||||
|
"Chore",
|
||||||
|
"Kind/Chore"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## ⏪ Reverts",
|
||||||
|
"labels": [
|
||||||
|
"revert",
|
||||||
|
"Revert",
|
||||||
|
"Kind/Revert",
|
||||||
|
"Kind/Reverts",
|
||||||
|
"reverts",
|
||||||
|
"Reverts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## 🗑️ Deprecation",
|
||||||
|
"labels": [
|
||||||
|
"deprecation",
|
||||||
|
"Deprecation",
|
||||||
|
"Kind/Deprecation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## ⚡️ Performance Improvements",
|
||||||
|
"labels": [
|
||||||
|
"perf",
|
||||||
|
"Perf",
|
||||||
|
"Kind/Performance"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## 🎨 Styling",
|
||||||
|
"labels": [
|
||||||
|
"style",
|
||||||
|
"Style",
|
||||||
|
"Kind/Style"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "## 🎯 Other Changes",
|
||||||
|
"labels": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"label_extractor": [
|
||||||
|
{
|
||||||
|
"pattern": "(\\w+) (.+)",
|
||||||
|
"target": "$1",
|
||||||
|
"on_property": "title"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort": "ASC",
|
||||||
|
"template": "${{CHANGELOG}}",
|
||||||
|
"pr_template": "- ${{TITLE}}\n - PR: #${{NUMBER}}",
|
||||||
|
"empty_template": "- no changes",
|
||||||
|
"max_pull_requests": 1000,
|
||||||
|
"max_back_track_time_days": 1000
|
||||||
|
}
|
||||||
117
.gitea/workflows/release.yml
Normal file
117
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
github_release:
|
||||||
|
description: 'Create Gitea Release'
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
docker_release:
|
||||||
|
description: 'Push Docker images'
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
bump:
|
||||||
|
description: 'Bump type'
|
||||||
|
required: true
|
||||||
|
default: 'patch'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- 'major'
|
||||||
|
- 'minor'
|
||||||
|
- 'patch'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@master
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.REGISTRY }}
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.TOKEN }}
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
- name: Set up Python
|
||||||
|
run: uv python install
|
||||||
|
with:
|
||||||
|
python-version-file: "pyproject.toml"
|
||||||
|
|
||||||
|
- name: Install the project
|
||||||
|
run: uv sync --all-extras --dev
|
||||||
|
|
||||||
|
- name: Create requirements.txt
|
||||||
|
run: |
|
||||||
|
echo "Creating requirements.txt"
|
||||||
|
uv export --format requirements.txt -o requirements.txt
|
||||||
|
echo "requirements.txt created"
|
||||||
|
|
||||||
|
- name: Set Git identity
|
||||||
|
run: |
|
||||||
|
git config user.name "Gitea CI"
|
||||||
|
git config user.email "ci@git.theprivateserver.de"
|
||||||
|
|
||||||
|
- name: Build Changelog
|
||||||
|
id: build_changelog
|
||||||
|
uses: https://github.com/mikepenz/release-changelog-builder-action@v5
|
||||||
|
with:
|
||||||
|
platform: "gitea"
|
||||||
|
baseURL: "http://gitea:3000"
|
||||||
|
configuration: ".gitea/changelog-config.json"
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
|
||||||
|
|
||||||
|
- name: Bump version
|
||||||
|
id: bump
|
||||||
|
run: |
|
||||||
|
uv tool install bump-my-version
|
||||||
|
uv tool run bump-my-version bump ${{ github.event.inputs.bump }} --allow-dirty
|
||||||
|
# echo the version to github env, the version is shown by using uv tool run bump-my-version show current_version
|
||||||
|
echo "VERSION<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$(uv tool run bump-my-version show current_version)" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
- name: Check version
|
||||||
|
run: echo ${{ env.VERSION }}
|
||||||
|
- name: Build and store Docker image
|
||||||
|
if: ${{ github.event.inputs.docker_release == 'true' }}
|
||||||
|
run: |
|
||||||
|
REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--tag ${{ secrets.REGISTRY }}/${REPO_NAME}:latest \
|
||||||
|
--tag ${{ secrets.REGISTRY }}/${REPO_NAME}:${{ env.VERSION }} \
|
||||||
|
--push .
|
||||||
|
- name: Push changes
|
||||||
|
uses: ad-m/github-push-action@master
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
branch: ${{ github.ref }}
|
||||||
|
- name: Create release
|
||||||
|
id: create_release
|
||||||
|
if: ${{ github.event.inputs.github_release == 'true' }}
|
||||||
|
uses: softprops/action-gh-release@master
|
||||||
|
with:
|
||||||
|
tag_name: v${{ env.VERSION }}
|
||||||
|
release_name: Release ${{ env.VERSION }}
|
||||||
|
body: ${{steps.build_changelog.outputs.changelog}}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
make_latest: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.TOKEN }}
|
||||||
|
|
||||||
10
.gitignore
vendored
Executable file
10
.gitignore
vendored
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
1
.python-version
Executable file
1
.python-version
Executable file
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Install system deps (optional: to build some wheels faster). Kept minimal here.
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy dependency list and install first (leverages Docker layer cache)
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY app ./app
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Run the FastAPI app with Uvicorn
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
88
app/main.py
Executable file
88
app/main.py
Executable file
@@ -0,0 +1,88 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from xml.etree.ElementTree import Element, SubElement, tostring
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Form, HTTPException, Request, status
|
||||||
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
|
# Serve static files (CSS, images)
|
||||||
|
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||||
|
|
||||||
|
# add somewhere near the top-level constants
|
||||||
|
EMAIL_REGEX = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
||||||
|
|
||||||
|
# SMTP / email configuration via environment
|
||||||
|
SMTP_HOST = os.getenv("SMTP_HOST", "smtp")
|
||||||
|
SMTP_PORT = int(os.getenv("SMTP_PORT", "25"))
|
||||||
|
MAIL_FROM = os.getenv("MAIL_FROM", "noreply@example.com")
|
||||||
|
MAIL_TO = os.getenv("MAIL_TO", "destination@example.com")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
async def show_form(request: Request):
|
||||||
|
return templates.TemplateResponse("form.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/submit")
|
||||||
|
async def handle_form(
|
||||||
|
request: Request,
|
||||||
|
name: str = Form(...),
|
||||||
|
lastname: str = Form(...),
|
||||||
|
title: str = Form(...),
|
||||||
|
telno: str = Form(...),
|
||||||
|
mail: str = Form(...),
|
||||||
|
apparatsname: str = Form(...),
|
||||||
|
subject: str = Form(...),
|
||||||
|
semester_type: str = Form(...), # "summer" or "winter"
|
||||||
|
semester_year: str = Form(...),
|
||||||
|
authorname: list[str] = Form(...),
|
||||||
|
year: list[str] = Form(...),
|
||||||
|
booktitle: list[str] = Form(...),
|
||||||
|
signature: list[str] = Form(...),
|
||||||
|
):
|
||||||
|
# Build XML
|
||||||
|
root = Element("form_submission")
|
||||||
|
|
||||||
|
static_data = SubElement(root, "static")
|
||||||
|
SubElement(static_data, "name").text = name
|
||||||
|
SubElement(static_data, "lastname").text = lastname
|
||||||
|
SubElement(static_data, "title").text = title
|
||||||
|
SubElement(static_data, "telno").text = telno
|
||||||
|
SubElement(static_data, "mail").text = mail
|
||||||
|
SubElement(static_data, "apparatsname").text = apparatsname
|
||||||
|
SubElement(static_data, "subject").text = subject
|
||||||
|
SubElement(static_data, "semester").text = f"{semester_type} {semester_year}"
|
||||||
|
# inside handle_form(), right after parameters are received, before building XML:
|
||||||
|
# Basic email validation (server-side)
|
||||||
|
if not EMAIL_REGEX.match(mail):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Invalid email address format.",
|
||||||
|
)
|
||||||
|
books = SubElement(root, "books")
|
||||||
|
for i in range(len(authorname)):
|
||||||
|
book = SubElement(books, "book")
|
||||||
|
SubElement(book, "authorname").text = authorname[i]
|
||||||
|
SubElement(book, "year").text = year[i]
|
||||||
|
SubElement(book, "title").text = booktitle[i]
|
||||||
|
SubElement(book, "signature").text = signature[i]
|
||||||
|
|
||||||
|
xml_data = tostring(root, encoding="unicode")
|
||||||
|
|
||||||
|
# Send mail
|
||||||
|
msg = MIMEText(xml_data, "xml")
|
||||||
|
msg["Subject"] = "New Form Submission"
|
||||||
|
msg["From"] = MAIL_FROM
|
||||||
|
msg["To"] = MAIL_TO
|
||||||
|
|
||||||
|
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
|
||||||
|
server.send_message(msg)
|
||||||
|
|
||||||
|
return RedirectResponse("/", status_code=303)
|
||||||
291
app/static/styles.css
Normal file
291
app/static/styles.css
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #f7f8fb;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--text: #1f2937;
|
||||||
|
--muted: #6b7280;
|
||||||
|
--primary: #0b7bd6; /* Accessible blue */
|
||||||
|
--primary-600: #0a6ec0;
|
||||||
|
--primary-700: #095fa6;
|
||||||
|
--border: #e5e7eb;
|
||||||
|
--ring: rgba(11, 123, 214, 0.35);
|
||||||
|
--table-head-bg: #f3f4f6;
|
||||||
|
--input-bg: #ffffff;
|
||||||
|
--control-accent: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme variables */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg: #0b1220;
|
||||||
|
--card-bg: #121a2a;
|
||||||
|
--text: #e5e7eb;
|
||||||
|
--muted: #9aa4b2;
|
||||||
|
--primary: #5aa1ff;
|
||||||
|
--primary-600: #4b90ea;
|
||||||
|
--primary-700: #3c7ace;
|
||||||
|
--border: #243045;
|
||||||
|
--ring: rgba(90, 161, 255, 0.35);
|
||||||
|
--table-head-bg: #172136;
|
||||||
|
--input-bg: #0f1726;
|
||||||
|
--control-accent: #2a3448;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
|
html, body { height: 100%; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--bg);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.site-header {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.header-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
.logo { height: 48px; width: auto; }
|
||||||
|
.brand-title { font-weight: 700; letter-spacing: .2px; }
|
||||||
|
.brand-sub { color: var(--muted); font-size: .95rem; }
|
||||||
|
|
||||||
|
.theme-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
/* padding: 8px 10px; */
|
||||||
|
scale: 2;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: none;/*1px solid var(--border); */
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.theme-toggle:hover { filter: brightness(1.05); }
|
||||||
|
.theme-toggle:focus-visible { outline: none; box-shadow: 0 0 0 4px var(--ring); }
|
||||||
|
|
||||||
|
/* Icon emphasis depending on theme */
|
||||||
|
[data-theme="light"] .theme-toggle .sun { opacity: 1; }
|
||||||
|
[data-theme="light"] .theme-toggle .moon { opacity: 0.5; }
|
||||||
|
[data-theme="dark"] .theme-toggle .sun { opacity: 0.5; }
|
||||||
|
[data-theme="dark"] .theme-toggle .moon { opacity: 1; }
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
margin: 24px 0;
|
||||||
|
padding: 22px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 8px 30px rgba(0,0,0,.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 14px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 20px 0 10px;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.table-wrapper { overflow-x: auto; }
|
||||||
|
|
||||||
|
.form-table,
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
background: var(--card-bg);
|
||||||
|
/* Ensure full rounded corners and visible outer border */
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw the outer border inside the rounded area to avoid clipping */
|
||||||
|
.form-table::after,
|
||||||
|
.data-table::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table td,
|
||||||
|
.form-table th {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td,
|
||||||
|
.data-table th {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-table tr:last-child td,
|
||||||
|
.data-table tr:last-child td { border-bottom: 0; }
|
||||||
|
|
||||||
|
.data-table th {
|
||||||
|
background: var(--table-head-bg);
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inputs */
|
||||||
|
input[type="text"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="number"],
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--input-bg);
|
||||||
|
color: var(--text);
|
||||||
|
outline: none;
|
||||||
|
transition: border-color .2s ease, box-shadow .2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus {
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 4px var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] { accent-color: var(--control-accent); }
|
||||||
|
|
||||||
|
.radio { margin-right: 12px; }
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: background-color .2s ease, color .2s ease, border-color .2s ease, transform .02s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active { transform: translateY(1px); }
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn-primary:hover { background: var(--primary-600); }
|
||||||
|
.btn-primary:focus { box-shadow: 0 0 0 4px var(--ring); }
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #e7eef8;
|
||||||
|
color: var(--primary-700);
|
||||||
|
border-color: #cfd9ea;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover { background: #dfe8f6; }
|
||||||
|
|
||||||
|
[data-theme="dark"] .btn-secondary {
|
||||||
|
background: #1c2637;
|
||||||
|
color: var(--text);
|
||||||
|
border-color: #2a3852;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .btn-secondary:hover { background: #1f2b3f; }
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-note {
|
||||||
|
margin-top: 18px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.footer-note a { color: var(--primary-700); text-decoration: none; }
|
||||||
|
.footer-note a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.header-inner { padding: 12px 0; }
|
||||||
|
.logo { height: 40px; }
|
||||||
|
.form-table td:first-child { width: 40%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Static Information grid */
|
||||||
|
.grid-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 12px 16px; /* row x column gap */
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.form-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.form-field label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
/* Inline info message under Apparatsname */
|
||||||
|
.field-info {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--primary-700);
|
||||||
|
}
|
||||||
|
[data-theme="dark"] .field-info { color: #a9c8ff; }
|
||||||
|
.hidden { display: none !important; }
|
||||||
|
.inline-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.inline-controls input[type="number"] {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive tweaks for the grid */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.grid-form { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
}
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.grid-form { grid-template-columns: 1fr; }
|
||||||
|
.inline-controls { flex-wrap: wrap; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Darken secondary button and add spacing after tables */
|
||||||
|
.data-table + .btn { margin-top: 14px; }
|
||||||
197
app/templates/form.html
Executable file
197
app/templates/form.html
Executable file
@@ -0,0 +1,197 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Semesterapparatsantrag</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" href="/static/styles.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css">
|
||||||
|
<!-- removed legacy inline styles that forced black borders on tables/td/th -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="container header-inner">
|
||||||
|
<div class="header-left">
|
||||||
|
<img class="logo" src="https://www.ph-freiburg.de/_assets/cc3dd7db45300ecc1f3aeed85bda5532/Images/logo-big-blue.svg" alt="PH Freiburg Logo" />
|
||||||
|
<div class="brand">
|
||||||
|
<div class="brand-title">Bibliothek der Pädagogischen Hochschule Freiburg</div>
|
||||||
|
<div class="brand-sub">Hochschulbibliothek · Semesterapparate</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" title="Switch to dark mode">
|
||||||
|
<span class="mdi mdi-theme-light-dark" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<section class="card">
|
||||||
|
<h1>Static Information</h1>
|
||||||
|
<form method="post" action="/submit" class="request-form">
|
||||||
|
<!-- Replace table with two-row grid layout -->
|
||||||
|
<div class="grid-form">
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" name="name" id="name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="lastname">Nachname</label>
|
||||||
|
<input type="text" name="lastname" id="lastname" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="title">Titel</label>
|
||||||
|
<input type="text" name="title" id="title" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="telno">Telefonnummer</label>
|
||||||
|
<input type="number" name="telno" id="telno" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="mail">Email</label>
|
||||||
|
<input type="email" name="mail" id="mail" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="apparatsname">Apparatsname</label>
|
||||||
|
<input type="text" name="apparatsname" id="apparatsname" maxlength="40" required>
|
||||||
|
<div id="apparatsname-warning" class="field-info hidden" role="status" aria-live="polite">Name will be changed to keep in line with requirements</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="subject">Fach</label>
|
||||||
|
<select name="subject" id="subject" required>
|
||||||
|
<option value="">-- Auswählen --</option>
|
||||||
|
<option>Biologie</option>
|
||||||
|
<option>Chemie</option>
|
||||||
|
<option>Deutsch</option>
|
||||||
|
<option>Englisch</option>
|
||||||
|
<option>Erziehungswirtschaft</option>
|
||||||
|
<option>Französisch</option>
|
||||||
|
<option>Geographie</option>
|
||||||
|
<option>Geschichte</option>
|
||||||
|
<option>Gesundheitspädagogik</option>
|
||||||
|
<option>Haushalt / Textil</option>
|
||||||
|
<option>Kunst</option>
|
||||||
|
<option>Mathematik / Informatik</option>
|
||||||
|
<option>Medien in der Bildung</option>
|
||||||
|
<option>Musik</option>
|
||||||
|
<option>Philosophie</option>
|
||||||
|
<option>Physik</option>
|
||||||
|
<option>Politikwissenschaft</option>
|
||||||
|
<option>Prorektorat Lehre und Studium</option>
|
||||||
|
<option>Psychologie</option>
|
||||||
|
<option>Soziologie</option>
|
||||||
|
<option>Sport</option>
|
||||||
|
<option>Technik</option>
|
||||||
|
<option>Theologie</option>
|
||||||
|
<option>Wirtschaftslehre</option>
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label>Semester</label>
|
||||||
|
<div class="inline-controls">
|
||||||
|
<label class="radio"><input type="radio" name="semester_type" value="summer" required>Sommer</label>
|
||||||
|
<label class="radio"><input type="radio" name="semester_type" value="winter" required>Winter</label>
|
||||||
|
<input type="number" name="semester_year" placeholder="Jahr" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Medien</h2>
|
||||||
|
<table id="book-table" class="data-table">
|
||||||
|
<tr>
|
||||||
|
<th>Autorenname</th><th>Jahr/Auflage</th><th>Titel</th><th>Signatur</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input type="text" name="authorname" required></td>
|
||||||
|
<td><input type="text" name="year" required></td>
|
||||||
|
<td><input type="text" name="booktitle" required></td>
|
||||||
|
<td><input type="text" name="signature" required></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="addRow()">+ Medium hinzufügen</button>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button type="submit" class="btn btn-primary">Absenden</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="footer-note">
|
||||||
|
Hinweis: Weitere Informationen zu den Semesterapparaten finden Sie auf den Seiten der Hochschulbibliothek der PH Freiburg.
|
||||||
|
<br>
|
||||||
|
<a href="https://www.ph-freiburg.de/bibliothek.html" target="_blank" rel="noopener noreferrer">Zur Bibliothek</a>
|
||||||
|
<!-- add a spacer -->
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<a href="https://www.ph-freiburg.de/bibliothek/lernen/semesterapparate.html" target="_blank" rel="noopener noreferrer">Zu den Semesterapparaten</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Provide global addRow used by the "+ Medium hinzufügen" button
|
||||||
|
function addRow() {
|
||||||
|
const table = document.getElementById("book-table");
|
||||||
|
const row = table.insertRow(-1);
|
||||||
|
row.innerHTML = `
|
||||||
|
<td><input type="text" name="authorname" required></td>
|
||||||
|
<td><input type="text" name="year" required></td>
|
||||||
|
<td><input type="text" name="booktitle" required></td>
|
||||||
|
<td><input type="text" name="signature" required></td>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const STORAGE_KEY = 'theme';
|
||||||
|
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
const saved = localStorage.getItem(STORAGE_KEY);
|
||||||
|
|
||||||
|
function setTheme(theme) {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
localStorage.setItem(STORAGE_KEY, theme);
|
||||||
|
const btn = document.getElementById('theme-toggle');
|
||||||
|
if (btn) {
|
||||||
|
const label = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
|
||||||
|
btn.setAttribute('aria-label', label);
|
||||||
|
btn.title = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTheme(saved || (prefersDark ? 'dark' : 'light'));
|
||||||
|
|
||||||
|
const btn = document.getElementById('theme-toggle');
|
||||||
|
if (btn) {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const current = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
|
||||||
|
setTheme(current === 'dark' ? 'light' : 'dark');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Apparatsname dynamic warning: show when length exceeds (37 - len(lastname))
|
||||||
|
(function() {
|
||||||
|
function updateWarning() {
|
||||||
|
const last = document.getElementById('lastname');
|
||||||
|
const app = document.getElementById('apparatsname');
|
||||||
|
const warn = document.getElementById('apparatsname-warning');
|
||||||
|
if (!last || !app || !warn) return;
|
||||||
|
const lastLen = (last.value || '').length;
|
||||||
|
const appLen = (app.value || '').length;
|
||||||
|
const threshold = Math.max(0, 37 - lastLen);
|
||||||
|
if (appLen > threshold) {
|
||||||
|
warn.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
warn.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('input', (e) => {
|
||||||
|
if (e.target && (e.target.id === 'lastname' || e.target.id === 'apparatsname')) {
|
||||||
|
updateWarning();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.addEventListener('DOMContentLoaded', updateWarning);
|
||||||
|
// run once on load
|
||||||
|
updateWarning();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
container_name: semapform
|
||||||
|
environment:
|
||||||
|
- SMTP_HOST=${SMTP_HOST:-smtp}
|
||||||
|
- SMTP_PORT=${SMTP_PORT:-25}
|
||||||
|
- MAIL_FROM=${MAIL_FROM:-noreply@example.com}
|
||||||
|
- MAIL_TO=${MAIL_TO:-destination@example.com}
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
restart: unless-stopped
|
||||||
6
main.py
Normal file
6
main.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from app.main import app
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=5001)
|
||||||
12
pyproject.toml
Executable file
12
pyproject.toml
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
[project]
|
||||||
|
name = "semapform"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"fastapi>=0.117.1",
|
||||||
|
"jinja2>=3.1.6",
|
||||||
|
"python-multipart>=0.0.20",
|
||||||
|
"uvicorn>=0.37.0",
|
||||||
|
]
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi>=0.117.1
|
||||||
|
jinja2>=3.1.6
|
||||||
|
python-multipart>=0.0.20
|
||||||
|
uvicorn>=0.37.0
|
||||||
243
uv.lock
generated
Executable file
243
uv.lock
generated
Executable file
@@ -0,0 +1,243 @@
|
|||||||
|
version = 1
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.11.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "sniffio" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastapi"
|
||||||
|
version = "0.117.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "starlette" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7e/7e/d9788300deaf416178f61fb3c2ceb16b7d0dc9f82a08fdb87a5e64ee3cc7/fastapi-0.117.1.tar.gz", hash = "sha256:fb2d42082d22b185f904ca0ecad2e195b851030bd6c5e4c032d1c981240c631a", size = 307155 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/45/d9d3e8eeefbe93be1c50060a9d9a9f366dba66f288bb518a9566a23a8631/fastapi-0.117.1-py3-none-any.whl", hash = "sha256:33c51a0d21cab2b9722d4e56dbb9316f3687155be6b276191790d8da03507552", size = 95959 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jinja2"
|
||||||
|
version = "3.1.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markupsafe" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markupsafe"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.11.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.33.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-multipart"
|
||||||
|
version = "0.0.20"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semapform"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "fastapi" },
|
||||||
|
{ name = "jinja2" },
|
||||||
|
{ name = "python-multipart" },
|
||||||
|
{ name = "uvicorn" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "fastapi", specifier = ">=0.117.1" },
|
||||||
|
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||||
|
{ name = "python-multipart", specifier = ">=0.0.20" },
|
||||||
|
{ name = "uvicorn", specifier = ">=0.37.0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "starlette"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-inspection"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uvicorn"
|
||||||
|
version = "0.37.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "h11" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976 },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user