diff --git a/.gitignore b/.gitignore index 5b950ce..1cae045 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ config.php test.php melly-to-grist/.env -*/__pycache__ +**/__pycache__ new-registration-app/database.db \ No newline at end of file diff --git a/new-registration-app/alembic.ini b/new-registration-app/alembic.ini new file mode 100644 index 0000000..cef3554 --- /dev/null +++ b/new-registration-app/alembic.ini @@ -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 diff --git a/new-registration-app/alembic/README b/new-registration-app/alembic/README new file mode 100644 index 0000000..fdacc05 --- /dev/null +++ b/new-registration-app/alembic/README @@ -0,0 +1 @@ +pyproject configuration, based on the generic configuration. \ No newline at end of file diff --git a/new-registration-app/alembic/env.py b/new-registration-app/alembic/env.py new file mode 100644 index 0000000..68c9ea7 --- /dev/null +++ b/new-registration-app/alembic/env.py @@ -0,0 +1,78 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# 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 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() diff --git a/new-registration-app/alembic/script.py.mako b/new-registration-app/alembic/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/new-registration-app/alembic/script.py.mako @@ -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"} diff --git a/new-registration-app/alembic/versions/2025_10_12_2046-299a83240036_inital_revision.py b/new-registration-app/alembic/versions/2025_10_12_2046-299a83240036_inital_revision.py new file mode 100644 index 0000000..a8fa23e --- /dev/null +++ b/new-registration-app/alembic/versions/2025_10_12_2046-299a83240036_inital_revision.py @@ -0,0 +1,111 @@ +"""inital revision + +Revision ID: 299a83240036 +Revises: +Create Date: 2025-10-12 20:46:13.452705 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# 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 ### diff --git a/new-registration-app/pyproject.toml b/new-registration-app/pyproject.toml index fd2893b..d1995ac 100644 --- a/new-registration-app/pyproject.toml +++ b/new-registration-app/pyproject.toml @@ -5,6 +5,7 @@ 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", @@ -17,3 +18,23 @@ dev = [ ] [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 = [ + "." +] diff --git a/new-registration-app/uv.lock b/new-registration-app/uv.lock index 180fa37..df168a7 100644 --- a/new-registration-app/uv.lock +++ b/new-registration-app/uv.lock @@ -2,6 +2,20 @@ version = 1 revision = 3 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]] name = "annotated-types" version = "0.7.0" @@ -257,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" }, ] +[[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]] name = "markdown-it-py" version = "4.0.0" @@ -320,6 +346,7 @@ name = "new-registration-app" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "alembic" }, { name = "fastapi", extra = ["standard"] }, { name = "sqlalchemy" }, { name = "uvicorn", extra = ["standard"] }, @@ -333,6 +360,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "alembic", specifier = ">=1.17.0" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.116.0" }, { name = "sqlalchemy", specifier = ">=2.0.44" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.35.0" },