Compare commits

..

10 Commits

Author SHA1 Message Date
c8500a4337 Move meal manager into it's own package 2025-10-12 21:42:28 +02:00
03d823c713 Rename new-registration-app to meal-manager 2025-10-12 21:25:23 +02:00
773f8ad2b6 remove old signup page 2025-10-12 21:19:37 +02:00
3291fbf6a0 Add comments to registrations 2025-10-12 21:12:45 +02:00
84f128806c Set up migration environment with alembic 2025-10-12 20:51:10 +02:00
494170e2ab Drop Sqlmodel and use plain Sqlalchemy 2025-10-11 22:14:04 +02:00
a190471b44 fix: Display issue on subscription page 2025-10-11 15:02:32 +02:00
02ecfa2209 Add info text to subscription 2025-10-11 14:37:34 +02:00
112459964a Minor tweaks
* display date on event page
  * Filter existing subscriptions for new subscriptions dropdown
  * Add Typeahead for Team registration
2025-10-09 12:15:46 +02:00
457418c271 Fix python version to 3.13 2025-10-08 14:07:03 +02:00
75 changed files with 558 additions and 541 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
config.php config.php
test.php test.php
melly-to-grist/.env melly-to-grist/.env
*/__pycache__ **/__pycache__
new-registration-app/database.db new-registration-app/database.db

View File

@@ -1,130 +0,0 @@
import typing
from datetime import datetime
from xmlrpc.client import DateTime
from sqlmodel import Field, Relationship, SQLModel, String
WorkTypes = typing.Literal["cooking", "dishes", "tables"]
class Event(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
title: str = Field(nullable=False)
event_time: datetime = Field(nullable=False)
registration_deadline: datetime = Field(nullable=False)
description: str
recipe_link: str
# Min and max number of people needed for cooking, doing the dishes and preparing the tables
team_cooking_min: int = 3
team_cooking_max: int = 5
team_dishes_min: int = 3
team_dishes_max: int = 5
# Todo: Rename to "table"
team_prep_min: int = 1
team_prep_max: int = 1
registrations: list["Registration"] = Relationship()
team: list["TeamRegistration"] = Relationship()
def team_min_reached(self, work_type: WorkTypes):
threshold = {
"cooking": self.team_cooking_min,
"dishes": self.team_dishes_min,
"tables": self.team_prep_min,
}[work_type]
return sum(1 for t in self.team if t.work_type == work_type) >= threshold
def team_max_reached(self, work_type: WorkTypes):
threshold = {
"cooking": self.team_cooking_max,
"dishes": self.team_dishes_max,
"tables": self.team_prep_max,
}[work_type]
return sum(1 for t in self.team if t.work_type == work_type) >= threshold
def all_teams_min(self):
return all(
self.team_min_reached(work_type) for work_type in typing.get_args(WorkTypes)
)
def all_teams_max(self):
return all(
self.team_max_reached(work_type) for work_type in typing.get_args(WorkTypes)
)
class TeamRegistration(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
event_id: int | None = Field(default=None, foreign_key="event.id")
person_name: str = Field(nullable=False)
work_type: WorkTypes = Field(nullable=False, sa_type=String)
comment: str | None
class Household(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(nullable=False)
class Registration(SQLModel, table=True):
event_id: int | None = Field(default=None, foreign_key="event.id", primary_key=True)
household_id: int | None = Field(
default=None, foreign_key="household.id", primary_key=True
)
num_adult_meals: int
num_children_meals: int
num_small_children_meals: int
comment: str | None
household: Household = Relationship()
class Subscription(SQLModel, table=True):
household_id: int | None = Field(
default=None, foreign_key="household.id", primary_key=True
)
num_adult_meals: int
num_children_meals: int
num_small_children_meals: int
comment: str | None
last_modified: datetime = Field(default_factory=datetime.now, nullable=False)
monday: bool = True
tuesday: bool = True
wednesday: bool = True
thursday: bool = True
friday: bool = True
saturday: bool = True
sunday: bool = True
household: Household = Relationship()
def day_string_de(self) -> str:
"""
Generates a string representation of selected days in German short form.
"""
result = []
if self.monday:
result.append("Mo")
if self.tuesday:
result.append("Di")
if self.wednesday:
result.append("Mi")
if self.thursday:
result.append("Do")
if self.friday:
result.append("Fr")
if self.saturday:
result.append("Sa")
if self.sunday:
result.append("So")
if len(result) < 7:
return ", ".join(result)
else:
return "Alle"

View File

@@ -1,17 +0,0 @@
[project]
name = "new-registration-app"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi[standard]>=0.116.0",
"sqlmodel>=0.0.24",
"uvicorn[standard]>=0.35.0",
]
[dependency-groups]
dev = [
"black>=25.1.0",
"isort>=6.0.1",
]

42
pyproject.toml Normal file
View File

@@ -0,0 +1,42 @@
[project]
name = "meal-manager"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = "~=3.13.0"
dependencies = [
"alembic>=1.17.0",
"fastapi[standard]>=0.116.0",
"sqlalchemy>=2.0.44",
"uvicorn[standard]>=0.35.0",
]
[build-system]
requires = ["uv_build>=0.9.0,<0.10.0"]
build-backend = "uv_build"
[dependency-groups]
dev = [
"black>=25.1.0",
"isort>=6.0.1",
]
[tool.isort]
profile = "black"
[tool.alembic]
# path to migration scripts.
# this is typically a path given in POSIX (e.g. forward slashes)
# format, relative to the token %(here)s which refers to the location of this
# ini file
script_location = "%(here)s/alembic"
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
file_template = "%%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s"
# additional paths to be prepended to sys.path. defaults to the current working directory.
prepend_sys_path = [
"."
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,98 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Termin hinzufügen</title>
<link rel="stylesheet" href="style.css"> <!-- Link to the CSS file -->
</head>
<body>
<header>
<div class="container">
<div id="branding">
<img src="Logo.png" alt="Logo">
<h1>Termin hinzufügen</h1>
</div>
</div>
</header>
<div class="container">
<h2>Neuen Termin hinzufügen</h2>
<form action="add.php" method="POST">
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
<label for="melly">Melly Link:</label>
<input type="text" id="melly" name="melly" required>
<label for="date">Event Date:</label>
<input type="date" id="date" name="date" required>
<label for="signup_deadline">Anmeldung bis:</label>
<input type="datetime-local" id="signup_deadline" name="signup_deadline" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<button type="submit" class="btn">Hinzfügen</button>
</form>
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$config = require 'config.php';
// Database connection parameters
$host = $config['db']['host'];
$dbname = $config['db']['dbname'];
$username = $config['db']['username'];
$password = $config['db']['password'];
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Retrieve form data
$title = $_POST['title'];
$melly = $_POST['melly'];
$date = $_POST['date'];
$deadline = $_POST['signup_deadline'];
$webform_password = $_POST['password'];
if($webform_password != $config['webform_password']) {
echo 'Invalid Password!';
} else {
try {
// Create connection
$dsn = "pgsql:host=$host;dbname=$dbname";
$pdo = new PDO($dsn, $username, $password);
// Set error mode to exception for easier debugging
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// SQL query to insert a new dinner option
$sql = "INSERT INTO meals (title, link, event_date, registration_closes) VALUES (:title, :melly, :date, :deadline)";
$stmt = $pdo->prepare($sql);
// Bind parameters
$stmt->bindParam(':title', $title);
$stmt->bindParam(':melly', $melly);
$stmt->bindParam(':date', $date);
$stmt->bindParam(':deadline', $deadline);
// Execute the statement
$stmt->execute();
echo '<p class="success">Dinner option added successfully!</p>';
} catch (PDOException $e) {
// Handle connection or query error
echo "Error: " . $e->getMessage();
}
}
}
?>
</div>
</body>
</html>

View File

@@ -1,13 +0,0 @@
CREATE TABLE meals (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
link TEXT NOT NULL,
event_date DATE NOT NULL,
registration_closes TIMESTAMP NOT NULL
);
INSERT INTO meals (title, link, event_date, registration_closes)
VALUES ('Kidneybohnen Burger mit veganem Coleslaw','https://melly.de/plan/2ZSNYWR37VB8','2025-03-05', '2025-03-02T17:30:30'),
('Gemüselasagne mit Salat','hhttps://melly.de/plan/M4XU9XMVM2HP','2025-02-28', '2025-02-23T17:30:30'),
RETURNING *;

View File

@@ -1,108 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Allmende-Essen</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="container">
<div id="branding">
<img src="Logo.png" alt="Logo"/>
<h1>Gemeinsames Essen in der Allmende</h1>
</div>
</div>
</header>
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$config = require 'config.php';
// Database connection parameters
$host = $config['db']['host'];
$dbname = $config['db']['dbname'];
$username = $config['db']['username'];
$password = $config['db']['password'];
// Create connection
$dsn = "pgsql:host=$host;dbname=$dbname";
$pdo = new PDO($dsn, $username, $password);
// Query to fetch future dinner options
$sql = "SELECT id, title, link, event_date, registration_closes FROM meals WHERE event_date >= now()::date order by registration_closes < now(), event_date";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$days = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
$today = strtotime(date('Y-m-d'));
$now = strtotime(date("Y-m-d H:i:s"));
?>
<div class="container">
<?php // Display dinner options
for($i = 0; $i < sizeof($result); $i++) {
$row = $result[$i];
$event_date = strtotime($row["event_date"]);
$weekday = date("w", $event_date);
$date = new DateTimeImmutable($row["event_date"]);
$end_of_registration = strtotime($row["registration_closes"]);
echo '<div class="dinner-option">';
echo '<h2>' . $days[$weekday] . " " . $date->format('d.m.Y') .'</h2>';
echo '<p>' . htmlspecialchars($row["title"]) . '</p>';
if ($end_of_registration > $now) {
echo '<a href="' . $row["link"] . '" class="btn">Zur Anmeldung</a>';
} else {
echo '<a href="' . $row["link"] . '" class="btn btn-grey">Anmeldungen ansehen</a>';
}
echo '</div>';
}
// close container
echo '</div>';
// Query to fetch past dinner options
$sql = "SELECT id, title, link, event_date FROM meals WHERE event_date < now()::date order by event_date";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
if(sizeof($result) > 0) {
?>
<div class="container">
<h2>Vergangene Essen</h2>
<?php
for($i = 0; $i < sizeof($result); $i++) {
$row = $result[$i];
$event_date = strtotime($row["event_date"]);
$weekday = date("w", $event_date);
$date = new DateTimeImmutable($row["event_date"]);
echo '<div class="dinner-option">';
echo '<h2>' . $days[$weekday] . " " . $date->format('d.m.Y') .'</h2>';
echo '<p>' . htmlspecialchars($row["title"]) . '</p>';
echo '<a href="' . $row["link"] . '" class="btn btn-grey">Anmeldungen ansehen</a>';
echo '</div>';
}
// close container
echo '</div>';
}
?>
</body>
</html>

View File

@@ -1,75 +0,0 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
color: #333;
}
.container {
width: 80%;
margin: auto;
overflow: hidden;
}
header {
background: #333;
color: #fff;
padding-top: 30px;
min-height: 70px;
border-bottom: #77aaff 3px solid;
}
header a {
color: #fff;
text-decoration: none;
text-transform: uppercase;
font-size: 16px;
}
header ul {
padding: 0;
list-style: none;
}
header li {
float: left;
display: inline;
padding: 0 20px 0 20px;
}
header #branding {
float: left;
}
header #branding img {
height: 50px;
width: 40px;
margin-right: 10px;
}
header #branding h1 {
margin: 0;
}
header nav {
float: right;
margin-top: 10px;
}
.dinner-option {
background: #fff;
padding: 20px;
margin: 20px 0;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.dinner-option h2 {
margin-top: 0;
}
.btn {
background: #77aaff;
color: #fff;
padding: 10px 20px;
text-decoration: none;
border-radius: 5px;
display: inline-block;
margin-top: 10px;
}
.btn-grey {
background: #ccc;
color: #333;
}
.btn:hover {
background: #5a99d0;
}

View File

View File

@@ -0,0 +1,43 @@
# A generic, single database configuration.
[alembic]
# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url = sqlite:///database.db
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@@ -0,0 +1 @@
pyproject configuration, based on the generic configuration.

View File

@@ -0,0 +1,76 @@
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config, pool
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from meal_manager.models import Base
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -0,0 +1,28 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
"""Upgrade schema."""
${upgrades if upgrades else "pass"}
def downgrade() -> None:
"""Downgrade schema."""
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,110 @@
"""inital revision
Revision ID: 299a83240036
Revises:
Create Date: 2025-10-12 20:46:13.452705
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "299a83240036"
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"event",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("event_time", sa.DateTime(), nullable=False),
sa.Column("registration_deadline", sa.DateTime(), nullable=False),
sa.Column("description", sa.String(), nullable=False),
sa.Column("recipe_link", sa.String(), nullable=False),
sa.Column("team_cooking_min", sa.Integer(), nullable=False),
sa.Column("team_cooking_max", sa.Integer(), nullable=False),
sa.Column("team_dishes_min", sa.Integer(), nullable=False),
sa.Column("team_dishes_max", sa.Integer(), nullable=False),
sa.Column("team_prep_min", sa.Integer(), nullable=False),
sa.Column("team_prep_max", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"household",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"registration",
sa.Column("event_id", sa.Integer(), nullable=False),
sa.Column("household_id", sa.Integer(), nullable=False),
sa.Column("num_adult_meals", sa.Integer(), nullable=False),
sa.Column("num_children_meals", sa.Integer(), nullable=False),
sa.Column("num_small_children_meals", sa.Integer(), nullable=False),
sa.Column("comment", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["event_id"],
["event.id"],
),
sa.ForeignKeyConstraint(
["household_id"],
["household.id"],
),
sa.PrimaryKeyConstraint("event_id", "household_id"),
)
op.create_table(
"subscription",
sa.Column("household_id", sa.Integer(), nullable=False),
sa.Column("num_adult_meals", sa.Integer(), nullable=False),
sa.Column("num_children_meals", sa.Integer(), nullable=False),
sa.Column("num_small_children_meals", sa.Integer(), nullable=False),
sa.Column("comment", sa.String(), nullable=True),
sa.Column("last_modified", sa.DateTime(), nullable=False),
sa.Column("monday", sa.Boolean(), nullable=False),
sa.Column("tuesday", sa.Boolean(), nullable=False),
sa.Column("wednesday", sa.Boolean(), nullable=False),
sa.Column("thursday", sa.Boolean(), nullable=False),
sa.Column("friday", sa.Boolean(), nullable=False),
sa.Column("saturday", sa.Boolean(), nullable=False),
sa.Column("sunday", sa.Boolean(), nullable=False),
sa.ForeignKeyConstraint(
["household_id"],
["household.id"],
),
sa.PrimaryKeyConstraint("household_id"),
)
op.create_table(
"team_registration",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("event_id", sa.Integer(), nullable=False),
sa.Column("person_name", sa.String(), nullable=False),
sa.Column("work_type", sa.Text(), nullable=False),
sa.Column("comment", sa.String(), nullable=True),
sa.ForeignKeyConstraint(
["event_id"],
["event.id"],
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("team_registration")
op.drop_table("subscription")
op.drop_table("registration")
op.drop_table("household")
op.drop_table("event")
# ### end Alembic commands ###

View File

@@ -8,10 +8,17 @@ from fastapi import Depends, FastAPI, Request
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlmodel import Session, SQLModel, create_engine, select from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from models import (Event, Household, Registration, Subscription, from meal_manager.models import (
TeamRegistration) Base,
Event,
Household,
Registration,
Subscription,
TeamRegistration,
)
sqlite_file_name = "database.db" sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}" sqlite_url = f"sqlite:///{sqlite_file_name}"
@@ -28,7 +35,7 @@ def get_session():
def create_db_and_tables(): def create_db_and_tables():
SQLModel.metadata.create_all(engine) Base.metadata.create_all(engine)
@asynccontextmanager @asynccontextmanager
@@ -38,9 +45,9 @@ async def on_startup(app_: FastAPI):
app = FastAPI(lifespan=on_startup) app = FastAPI(lifespan=on_startup)
app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/static", StaticFiles(directory="src/meal_manager/static"), name="static")
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="src/meal_manager/templates")
SessionDep = Annotated[Session, Depends(get_session)] SessionDep = Annotated[Session, Depends(get_session)]
@@ -55,7 +62,7 @@ async def index(request: Request, session: SessionDep):
.order_by(Event.event_time) .order_by(Event.event_time)
.where(Event.event_time >= now - timedelta(days=1)) .where(Event.event_time >= now - timedelta(days=1))
) )
events = session.exec(statement).all() events = session.scalars(statement)
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="index.html", name="index.html",
@@ -72,7 +79,7 @@ async def past_events(request: Request, session: SessionDep):
.order_by(Event.event_time) .order_by(Event.event_time)
.where(Event.event_time < now - timedelta(days=1)) .where(Event.event_time < now - timedelta(days=1))
) )
events = session.exec(statement).all() events = session.scalars(statement)
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="index.html", name="index.html",
@@ -83,16 +90,14 @@ async def past_events(request: Request, session: SessionDep):
@app.get("/subscribe") @app.get("/subscribe")
async def subscribe(request: Request, session: SessionDep): async def subscribe(request: Request, session: SessionDep):
statement = select(Household) statement = select(Household)
households = session.exec(statement).all() households = session.scalars(statement)
# filter out households with existing registrations subscriptions = session.scalars(select(Subscription))
# households = [
# h
# for h in households
# if h.id not in [reg.household_id for reg in event.registrations]
# ]
subscriptions = session.exec(select(Subscription)).all() # filter out households with existing subscriptions
households = [
h for h in households if h.id not in [sub.household_id for sub in subscriptions]
]
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
@@ -141,7 +146,7 @@ async def add_subscribe(request: Request, session: SessionDep):
async def delete_subscription(request: Request, session: SessionDep, household_id: int): async def delete_subscription(request: Request, session: SessionDep, household_id: int):
statement = select(Subscription).where(Subscription.household_id == household_id) statement = select(Subscription).where(Subscription.household_id == household_id)
sub = session.exec(statement).one() sub = session.scalars(statement).one()
session.delete(sub) session.delete(sub)
session.commit() session.commit()
@@ -186,10 +191,10 @@ async def add_event(request: Request, session: SessionDep):
@app.get("/event/{event_id}") @app.get("/event/{event_id}")
async def read_event(request: Request, event_id: int, session: SessionDep): async def read_event(request: Request, event_id: int, session: SessionDep):
statement = select(Event).where(Event.id == event_id) statement = select(Event).where(Event.id == event_id)
event = session.exec(statement).one() event = session.scalars(statement).one()
statement = select(Household) statement = select(Household)
households = session.exec(statement).all() households = session.scalars(statement)
# filter out households with existing registrations # filter out households with existing registrations
households = [ households = [
@@ -225,6 +230,7 @@ async def add_registration(request: Request, event_id: int, session: SessionDep)
num_adult_meals=num_adult_meals, num_adult_meals=num_adult_meals,
num_children_meals=num_children_meals, num_children_meals=num_children_meals,
num_small_children_meals=num_small_children_meals, num_small_children_meals=num_small_children_meals,
comment=form_data["comment"],
) )
session.add(registration) session.add(registration)
session.commit() session.commit()
@@ -243,7 +249,7 @@ async def delete_registration(
statement = select(Registration).where( statement = select(Registration).where(
Registration.household_id == household_id, Registration.event_id == event_id Registration.household_id == household_id, Registration.event_id == event_id
) )
session.delete(session.exec(statement).one()) session.delete(session.scalars(statement).one())
session.commit() session.commit()
return RedirectResponse(url=f"/event/{event_id}", status_code=status.HTTP_302_FOUND) return RedirectResponse(url=f"/event/{event_id}", status_code=status.HTTP_302_FOUND)
@@ -259,13 +265,12 @@ async def add_team_registration(request: Request, event_id: int, session: Sessio
TeamRegistration.person_name == person, TeamRegistration.work_type == work_type TeamRegistration.person_name == person, TeamRegistration.work_type == work_type
) )
# if the person has already registered for the same work type, just ignore # if the person has already registered for the same work type, just ignore
if session.exec(statement).one_or_none() is None: if session.scalars(statement).one_or_none() is None:
registration = TeamRegistration( registration = TeamRegistration(
person_name=person, person_name=person,
event_id=event_id, event_id=event_id,
work_type=form_data["workType"], work_type=form_data["workType"],
) )
TeamRegistration.model_validate(registration)
session.add(registration) session.add(registration)
session.commit() session.commit()
return RedirectResponse(url=f"/event/{event_id}", status_code=status.HTTP_302_FOUND) return RedirectResponse(url=f"/event/{event_id}", status_code=status.HTTP_302_FOUND)
@@ -279,6 +284,6 @@ async def delete_team_registration(
session: SessionDep, session: SessionDep,
): ):
statement = select(TeamRegistration).where(TeamRegistration.id == entry_id) statement = select(TeamRegistration).where(TeamRegistration.id == entry_id)
session.delete(session.exec(statement).one()) session.delete(session.scalars(statement).one())
session.commit() session.commit()
return RedirectResponse(url=f"/event/{event_id}", status_code=status.HTTP_302_FOUND) return RedirectResponse(url=f"/event/{event_id}", status_code=status.HTTP_302_FOUND)

143
src/meal_manager/models.py Normal file
View File

@@ -0,0 +1,143 @@
import typing
from datetime import datetime
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.types import Text
class Base(DeclarativeBase):
pass
WorkTypes = typing.Literal["cooking", "dishes", "tables"]
class Event(Base):
__tablename__ = "event"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str] = mapped_column(nullable=False)
event_time: Mapped[datetime] = mapped_column(nullable=False)
registration_deadline: Mapped[datetime] = mapped_column(nullable=False)
description: Mapped[str] = mapped_column()
recipe_link: Mapped[str] = mapped_column()
# Min and max number of people needed for cooking, doing the dishes and preparing the tables
team_cooking_min: Mapped[int] = mapped_column(default=3, nullable=False)
team_cooking_max: Mapped[int] = mapped_column(default=5, nullable=False)
team_dishes_min: Mapped[int] = mapped_column(default=3, nullable=False)
team_dishes_max: Mapped[int] = mapped_column(default=5, nullable=False)
# Todo: Rename to "table"
team_prep_min: Mapped[int] = mapped_column(default=1, nullable=False)
team_prep_max: Mapped[int] = mapped_column(default=1, nullable=False)
registrations: Mapped[list["Registration"]] = relationship("Registration")
team: Mapped[list["TeamRegistration"]] = relationship("TeamRegistration")
def team_min_reached(self, work_type: WorkTypes):
threshold = {
"cooking": self.team_cooking_min,
"dishes": self.team_dishes_min,
"tables": self.team_prep_min,
}[work_type]
return sum(1 for t in self.team if t.work_type == work_type) >= threshold
def team_max_reached(self, work_type: WorkTypes):
threshold = {
"cooking": self.team_cooking_max,
"dishes": self.team_dishes_max,
"tables": self.team_prep_max,
}[work_type]
return sum(1 for t in self.team if t.work_type == work_type) >= threshold
def all_teams_min(self):
return all(
self.team_min_reached(work_type) for work_type in typing.get_args(WorkTypes)
)
def all_teams_max(self):
return all(
self.team_max_reached(work_type) for work_type in typing.get_args(WorkTypes)
)
class TeamRegistration(Base):
__tablename__ = "team_registration"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
event_id: Mapped[int] = mapped_column(ForeignKey("event.id"))
person_name: Mapped[str] = mapped_column(nullable=False)
work_type: Mapped[WorkTypes] = mapped_column(Text, nullable=False)
comment: Mapped[str | None] = mapped_column()
class Household(Base):
__tablename__ = "household"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(nullable=False)
class Registration(Base):
__tablename__ = "registration"
event_id: Mapped[int] = mapped_column(ForeignKey("event.id"), primary_key=True)
household_id: Mapped[int] = mapped_column(
ForeignKey("household.id"), primary_key=True
)
num_adult_meals: Mapped[int] = mapped_column(nullable=False)
num_children_meals: Mapped[int] = mapped_column(nullable=False)
num_small_children_meals: Mapped[int] = mapped_column(nullable=False)
comment: Mapped[str | None] = mapped_column()
household: Mapped["Household"] = relationship()
class Subscription(Base):
__tablename__ = "subscription"
household_id: Mapped[int] = mapped_column(
ForeignKey("household.id"), primary_key=True
)
num_adult_meals: Mapped[int] = mapped_column(nullable=False)
num_children_meals: Mapped[int] = mapped_column(nullable=False)
num_small_children_meals: Mapped[int] = mapped_column(nullable=False)
comment: Mapped[str | None] = mapped_column()
last_modified: Mapped[datetime] = mapped_column(
default=datetime.now, nullable=False
)
monday: Mapped[bool] = mapped_column(default=True, nullable=False)
tuesday: Mapped[bool] = mapped_column(default=True, nullable=False)
wednesday: Mapped[bool] = mapped_column(default=True, nullable=False)
thursday: Mapped[bool] = mapped_column(default=True, nullable=False)
friday: Mapped[bool] = mapped_column(default=True, nullable=False)
saturday: Mapped[bool] = mapped_column(default=True, nullable=False)
sunday: Mapped[bool] = mapped_column(default=True, nullable=False)
household: Mapped["Household"] = relationship()
def day_string_de(self) -> str:
"""
Generates a string representation of selected days in German short form.
"""
result = []
if self.monday:
result.append("Mo")
if self.tuesday:
result.append("Di")
if self.wednesday:
result.append("Mi")
if self.thursday:
result.append("Do")
if self.friday:
result.append("Fr")
if self.saturday:
result.append("Sa")
if self.sunday:
result.append("So")
if len(result) < 7:
return ", ".join(result)
else:
return "Alle"

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -32,7 +32,6 @@
</div> </div>
<button type="submit" class="btn btn-primary">Event erstellen</button> <button type="submit" class="btn btn-primary">Event erstellen</button>
<a href="/new-registration-app/static" class="btn btn-secondary">Abbrechen</a>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -14,10 +14,11 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<p class="h1">{{ event.title }}</p> <p class="h1">{{ event.title }}</p>
<p class="text-muted">{{ event.event_time.strftime('%A, %d.%m.%Y') }}</p>
<p>{{ event.description }}</p> <p>{{ event.description }}</p>
<hr class="hr"/> <hr class="hr"/>
<p class="h3">Anmeldungen</p>
<div class="container"> <div class="container">
<p class="h3">Anmeldungen</p>
<div class="row m-2"> <div class="row m-2">
<div class="col-md-4 d-flex justify-content-center align-items-center flex-column"> <div class="col-md-4 d-flex justify-content-center align-items-center flex-column">
<p class="text-muted w-100 mb-2">Anmeldung schließt {{ event.registration_deadline.strftime('%A, %d.%m.%Y, %H:%M Uhr') }}</p> <p class="text-muted w-100 mb-2">Anmeldung schließt {{ event.registration_deadline.strftime('%A, %d.%m.%Y, %H:%M Uhr') }}</p>
@@ -25,7 +26,7 @@
<button type="button" class="btn btn-primary mb-2 w-100" {% if event.registration_deadline < now %}disabled{% endif%} data-bs-toggle="modal" data-bs-target="#registration"> <button type="button" class="btn btn-primary mb-2 w-100" {% if event.registration_deadline < now %}disabled{% endif%} data-bs-toggle="modal" data-bs-target="#registration">
Anmeldung hinzufügen Anmeldung hinzufügen
</button> </button>
<button type="button" class="btn btn-primary mb-2 w-100" {% if event.all_teams_max() or event.registration_deadline < now %}disabled{% endif %} data-bs-toggle="modal" data-bs-target="#teamRegistration"> <button type="button" class="btn btn-primary mb-2 w-100" {% if event.all_teams_max() or event.event_time < now %}disabled{% endif %} data-bs-toggle="modal" data-bs-target="#teamRegistration">
Dienst übernehmen Dienst übernehmen
</button> </button>
{% if event.recipe_link %} {% if event.recipe_link %}
@@ -103,6 +104,7 @@
<th scope="col">Erwachsene</th> <th scope="col">Erwachsene</th>
<th scope="col">Kinder</th> <th scope="col">Kinder</th>
<th scope="col">Kleinkinder</th> <th scope="col">Kleinkinder</th>
<th scope="col">Kommentar</th>
<th scope="col">Löschen</th> <th scope="col">Löschen</th>
</tr> </tr>
</thead> </thead>
@@ -113,6 +115,7 @@
<td>{{ reg.num_adult_meals }}</td> <td>{{ reg.num_adult_meals }}</td>
<td>{{ reg.num_children_meals }}</td> <td>{{ reg.num_children_meals }}</td>
<td>{{ reg.num_small_children_meals }}</td> <td>{{ reg.num_small_children_meals }}</td>
<td>{{ reg.comment }}</td>
<td><a href="/event/{{event.id}}/registration/{{reg.household_id}}/delete"><i class="bi bi-trash"></i></a></td> <td><a href="/event/{{event.id}}/registration/{{reg.household_id}}/delete"><i class="bi bi-trash"></i></a></td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -132,18 +135,22 @@
</a> </a>
</div> </div>
<div class="row mt-3"> <div class="row mt-3">
<div class="col-4 text-center"> <div class="col-3 text-center">
<div class="text-muted small">Erwachsene</div> <div class="text-muted small">Erwachsene</div>
<div class="fw-bold">{{ reg.num_adult_meals }}</div> <div class="fw-bold">{{ reg.num_adult_meals }}</div>
</div> </div>
<div class="col-4 text-center"> <div class="col-3 text-center">
<div class="text-muted small">Kinder</div> <div class="text-muted small">Kinder</div>
<div class="fw-bold">{{ reg.num_children_meals }}</div> <div class="fw-bold">{{ reg.num_children_meals }}</div>
</div> </div>
<div class="col-4 text-center"> <div class="col-3 text-center">
<div class="text-muted small">Kleinkinder</div> <div class="text-muted small">Kleinkinder</div>
<div class="fw-bold">{{ reg.num_small_children_meals }}</div> <div class="fw-bold">{{ reg.num_small_children_meals }}</div>
</div> </div>
<div class="col-3 text-center">
<div class="text-muted small">Kommentar</div>
<div class="small">{{ reg.comment }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -187,6 +194,11 @@
<input name="numSmallKids" id="InputSmallKids" type="number" class="form-control" <input name="numSmallKids" id="InputSmallKids" type="number" class="form-control"
aria-label="Anzahl Kinder <7" min="0" step="1" inputmode="numeric"> aria-label="Anzahl Kinder <7" min="0" step="1" inputmode="numeric">
</div> </div>
<div class="mb-3 mt-3">
<label for="InputComment" class="form-label">Kommentar</label>
<input name="comment" id="InputComment" class="form-control"
aria-label="Kommentar">
</div>
</div> </div>
</div> </div>
@@ -214,8 +226,15 @@
<div class="col-md-6"> <div class="col-md-6">
<label for="personName" class="form-label">Name</label> <label for="personName" class="form-label">Name</label>
<input name="personName" id="personName" type="text" class="form-control" <input name="personName" id="personName" type="text" class="form-control"
aria-label="Name"> aria-label="Name" list="people">
</div> </div>
<datalist id="people">
{% for household in households %}
{% for person in household.name.split(",") %}
<option value="{{ person.strip() }}">
{% endfor %}
{% endfor %}
</datalist>
<div class="col-md-6"> <div class="col-md-6">
<label for="workType" class="form-label">Dienst-Art</label> <label for="workType" class="form-label">Dienst-Art</label>
<select id="workType" name="workType" class="form-select" aria-label="Multiple select example"> <select id="workType" name="workType" class="form-select" aria-label="Multiple select example">

View File

@@ -23,6 +23,13 @@
Abend eingetragen. Danach Abend eingetragen. Danach
können sie auch noch gelöscht bzw. bearbeitet werden. können sie auch noch gelöscht bzw. bearbeitet werden.
</p> </p>
<!-- Info box about 7-day limitation -->
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i>
<strong>Hinweis:</strong> Neu angelegte dauerhafte Anmeldungen werden erst nach einer Woche aktiv. Für Kochabende, die in weniger als 7 Tagen stattfinden, musst du dich noch separat anmelden.
</div>
<!-- Household selection --> <!-- Household selection -->
<div class="mb-3"> <div class="mb-3">
<select name="household" class="form-select" required> <select name="household" class="form-select" required>
@@ -81,7 +88,6 @@
<!-- Footer --> <!-- Footer -->
<div class="card-footer d-flex justify-content-end gap-2"> <div class="card-footer d-flex justify-content-end gap-2">
<button type="submit" class="btn btn-primary">Dauerhaft anmelden</button> <button type="submit" class="btn btn-primary">Dauerhaft anmelden</button>
<a href="/" class="btn btn-secondary">Abbrechen</a>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,6 +1,20 @@
version = 1 version = 1
revision = 3 revision = 3
requires-python = ">=3.13" requires-python = "==3.13.*"
[[package]]
name = "alembic"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mako" },
{ name = "sqlalchemy" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6b/45/6f4555f2039f364c3ce31399529dcf48dd60726ff3715ad67f547d87dfd2/alembic-1.17.0.tar.gz", hash = "sha256:4652a0b3e19616b57d652b82bfa5e38bf5dbea0813eed971612671cb9e90c0fe", size = 1975526, upload-time = "2025-10-11T18:40:13.585Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/1f/38e29b06bfed7818ebba1f84904afdc8153ef7b6c7e0d8f3bc6643f5989c/alembic-1.17.0-py3-none-any.whl", hash = "sha256:80523bc437d41b35c5db7e525ad9d908f79de65c27d6a5a5eab6df348a352d99", size = 247449, upload-time = "2025-10-11T18:40:16.288Z" },
]
[[package]] [[package]]
name = "annotated-types" name = "annotated-types"
@@ -173,13 +187,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" },
{ url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" },
{ url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" },
{ url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" },
{ url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" },
{ url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" },
{ url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" },
{ url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" },
{ url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" },
{ url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
] ]
[[package]] [[package]]
@@ -264,6 +271,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
] ]
[[package]]
name = "mako"
version = "1.3.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" },
]
[[package]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
version = "4.0.0" version = "4.0.0"
@@ -314,21 +333,13 @@ wheels = [
] ]
[[package]] [[package]]
name = "mypy-extensions" name = "meal-manager"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "new-registration-app"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "alembic" },
{ name = "fastapi", extra = ["standard"] }, { name = "fastapi", extra = ["standard"] },
{ name = "sqlmodel" }, { name = "sqlalchemy" },
{ name = "uvicorn", extra = ["standard"] }, { name = "uvicorn", extra = ["standard"] },
] ]
@@ -340,8 +351,9 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "alembic", specifier = ">=1.17.0" },
{ name = "fastapi", extras = ["standard"], specifier = ">=0.116.0" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.116.0" },
{ name = "sqlmodel", specifier = ">=0.0.24" }, { name = "sqlalchemy", specifier = ">=2.0.44" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.35.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.35.0" },
] ]
@@ -351,6 +363,15 @@ dev = [
{ name = "isort", specifier = ">=6.0.1" }, { name = "isort", specifier = ">=6.0.1" },
] ]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "25.0" version = "25.0"
@@ -525,8 +546,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/3a/7e7ea6f0d31d3f5beb0f2cf2c4c362672f5f7f125714458673fc579e2bed/rignore-0.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:91dc94b1cc5af8d6d25ce6edd29e7351830f19b0a03b75cb3adf1f76d00f3007", size = 1134598, upload-time = "2025-07-19T19:24:15.039Z" }, { url = "https://files.pythonhosted.org/packages/3b/3a/7e7ea6f0d31d3f5beb0f2cf2c4c362672f5f7f125714458673fc579e2bed/rignore-0.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:91dc94b1cc5af8d6d25ce6edd29e7351830f19b0a03b75cb3adf1f76d00f3007", size = 1134598, upload-time = "2025-07-19T19:24:15.039Z" },
{ url = "https://files.pythonhosted.org/packages/7e/06/1b3307f6437d29bede5a95738aa89e6d910ba68d4054175c9f60d8e2c6b1/rignore-0.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4d1918221a249e5342b60fd5fa513bf3d6bf272a8738e66023799f0c82ecd788", size = 1108862, upload-time = "2025-07-19T19:24:26.765Z" }, { url = "https://files.pythonhosted.org/packages/7e/06/1b3307f6437d29bede5a95738aa89e6d910ba68d4054175c9f60d8e2c6b1/rignore-0.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4d1918221a249e5342b60fd5fa513bf3d6bf272a8738e66023799f0c82ecd788", size = 1108862, upload-time = "2025-07-19T19:24:26.765Z" },
{ url = "https://files.pythonhosted.org/packages/b0/d5/b37c82519f335f2c472a63fc6215c6f4c51063ecf3166e3acf508011afbd/rignore-0.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:240777332b859dc89dcba59ab6e3f1e062bc8e862ffa3e5f456e93f7fd5cb415", size = 1120002, upload-time = "2025-07-19T19:24:38.952Z" }, { url = "https://files.pythonhosted.org/packages/b0/d5/b37c82519f335f2c472a63fc6215c6f4c51063ecf3166e3acf508011afbd/rignore-0.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:240777332b859dc89dcba59ab6e3f1e062bc8e862ffa3e5f456e93f7fd5cb415", size = 1120002, upload-time = "2025-07-19T19:24:38.952Z" },
{ url = "https://files.pythonhosted.org/packages/ac/72/2f05559ed5e69bdfdb56ea3982b48e6c0017c59f7241f7e1c5cae992b347/rignore-0.6.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b0e548753e55cc648f1e7b02d9f74285fe48bb49cec93643d31e563773ab3f", size = 949454, upload-time = "2025-07-19T19:23:38.664Z" },
{ url = "https://files.pythonhosted.org/packages/0b/92/186693c8f838d670510ac1dfb35afbe964320fbffb343ba18f3d24441941/rignore-0.6.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6971ac9fdd5a0bd299a181096f091c4f3fd286643adceba98eccc03c688a6637", size = 974663, upload-time = "2025-07-19T19:23:28.24Z" },
] ]
[[package]] [[package]]
@@ -562,36 +581,23 @@ wheels = [
[[package]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"
version = "2.0.43" version = "2.0.44"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" } sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3", size = 2130598, upload-time = "2025-08-11T15:51:15.903Z" }, { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" },
{ url = "https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa", size = 2118415, upload-time = "2025-08-11T15:51:17.256Z" }, { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" },
{ url = "https://files.pythonhosted.org/packages/cf/ff/22ab2328148492c4d71899d62a0e65370ea66c877aea017a244a35733685/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9", size = 3248707, upload-time = "2025-08-11T15:52:38.444Z" }, { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" },
{ url = "https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f", size = 3253602, upload-time = "2025-08-11T15:56:37.348Z" }, { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" },
{ url = "https://files.pythonhosted.org/packages/b8/61/987b6c23b12c56d2be451bc70900f67dd7d989d52b1ee64f239cf19aec69/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738", size = 3183248, upload-time = "2025-08-11T15:52:39.865Z" }, { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" },
{ url = "https://files.pythonhosted.org/packages/86/85/29d216002d4593c2ce1c0ec2cec46dda77bfbcd221e24caa6e85eff53d89/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164", size = 3219363, upload-time = "2025-08-11T15:56:39.11Z" }, { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" },
{ url = "https://files.pythonhosted.org/packages/b6/e4/bd78b01919c524f190b4905d47e7630bf4130b9f48fd971ae1c6225b6f6a/sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d", size = 2096718, upload-time = "2025-08-11T15:55:05.349Z" }, { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" },
{ url = "https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197", size = 2123200, upload-time = "2025-08-11T15:55:07.932Z" }, { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" },
{ url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" },
]
[[package]]
name = "sqlmodel"
version = "0.0.24"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "sqlalchemy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/4b/c2ad0496f5bdc6073d9b4cef52be9c04f2b37a5773441cc6600b1857648b/sqlmodel-0.0.24.tar.gz", hash = "sha256:cc5c7613c1a5533c9c7867e1aab2fd489a76c9e8a061984da11b4e613c182423", size = 116780, upload-time = "2025-03-07T05:43:32.887Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/16/91/484cd2d05569892b7fef7f5ceab3bc89fb0f8a8c0cde1030d383dbc5449c/sqlmodel-0.0.24-py3-none-any.whl", hash = "sha256:6778852f09370908985b667d6a3ab92910d0d5ec88adcaf23dbc242715ff7193", size = 28622, upload-time = "2025-03-07T05:43:30.37Z" },
] ]
[[package]] [[package]]
@@ -721,26 +727,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" },
{ url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" },
{ url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" },
{ url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" },
{ url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" },
{ url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" },
{ url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" },
{ url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" },
{ url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" },
{ url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" },
{ url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" },
{ url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" },
{ url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" },
{ url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" },
{ url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" },
{ url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" },
{ url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" },
{ url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" },
{ url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" },
{ url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" },
{ url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" },
{ url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" },
{ url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" },
] ]
[[package]] [[package]]