Public Access
mirror of
https://github.com/xlorepdarkhelm/numinar-coding-project.git
synced 2026-09-11 10:28:45 -04:00
Added in alembic for migrations.
Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
@@ -7,3 +7,4 @@ docstring-convention = google
|
||||
per-file-ignores = tests/*:S101,backend/tests/*:S101
|
||||
rst-roles = class,const,func,meth,mod,ref
|
||||
rst-directives = deprecated
|
||||
exclude = backend/migrations/versions/*
|
||||
|
||||
+10
-1
@@ -27,6 +27,9 @@ COPY pyproject.toml poetry.lock ./
|
||||
|
||||
# Add the poetry plugin for exporting dependencies
|
||||
RUN poetry self add poetry-plugin-export
|
||||
|
||||
# Force the requirements.txt to be generated without cache
|
||||
ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache
|
||||
# Export dependencies to requirements.txt
|
||||
# This will create a requirements.txt file with the dependencies listed in pyproject.toml
|
||||
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
|
||||
@@ -48,7 +51,13 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
# Copy the project source code
|
||||
COPY src/ src/
|
||||
|
||||
# Copy the Alembic configuration and migrations
|
||||
COPY alembic.ini alembic.ini
|
||||
COPY migrations/ migrations/
|
||||
|
||||
COPY .env .env
|
||||
|
||||
# Start fastapi application using uvicorn
|
||||
EXPOSE 8000
|
||||
ENV PYTHONPATH=/app/src
|
||||
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[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/migrations
|
||||
|
||||
# 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
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory. for multiple paths, the path separator
|
||||
# is defined by "path_separator" below.
|
||||
prepend_sys_path = .
|
||||
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to ZoneInfo()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to <script_location>/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "path_separator"
|
||||
# below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
|
||||
|
||||
# path_separator; This indicates what character is used to split lists of file
|
||||
# paths, including version_locations and prepend_sys_path within configparser
|
||||
# files such as alembic.ini.
|
||||
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
|
||||
# to provide os-dependent path splitting.
|
||||
#
|
||||
# Note that in order to support legacy alembic.ini files, this default does NOT
|
||||
# take place if path_separator is not present in alembic.ini. If this
|
||||
# option is omitted entirely, fallback logic is as follows:
|
||||
#
|
||||
# 1. Parsing of the version_locations option falls back to using the legacy
|
||||
# "version_path_separator" key, which if absent then falls back to the legacy
|
||||
# behavior of splitting on spaces and/or commas.
|
||||
# 2. Parsing of the prepend_sys_path option falls back to the legacy
|
||||
# behavior of splitting on spaces, commas, or colons.
|
||||
#
|
||||
# Valid values for path_separator are:
|
||||
#
|
||||
# path_separator = :
|
||||
# path_separator = ;
|
||||
# path_separator = space
|
||||
# path_separator = newline
|
||||
#
|
||||
# Use os.pathsep. Default configuration used for new projects.
|
||||
path_separator = os
|
||||
|
||||
# set to 'true' to search source files recursively
|
||||
# in each "version_locations" directory
|
||||
# new in Alembic version 1.10
|
||||
# recursive_version_locations = false
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
# 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 = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
|
||||
# hooks = ruff
|
||||
# ruff.type = module
|
||||
# ruff.module = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Alternatively, use the exec runner to execute a binary found on your PATH
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration. This is also consumed by the user-maintained
|
||||
# env.py script only.
|
||||
[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
|
||||
@@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Alembic configuration for database migrations."""
|
||||
|
||||
import asyncio
|
||||
from logging.config import fileConfig
|
||||
from typing import cast
|
||||
|
||||
from alembic import context
|
||||
from sqlalchemy import Connection
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||
|
||||
from backend.db import db_url
|
||||
from backend.models import Base
|
||||
|
||||
|
||||
# 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.
|
||||
fileConfig(config.config_file_name) # type: ignore
|
||||
|
||||
# add your model's MetaData object here
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""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")
|
||||
url = db_url
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def do_run_migrations(connection: Connection) -> None:
|
||||
"""Run migrations with the given connection."""
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
"""
|
||||
configuration = cast(dict[str, str], config.get_section(config.config_ini_section))
|
||||
configuration["sqlalchemy.url"] = db_url
|
||||
connectable = async_engine_from_config(
|
||||
configuration,
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
asyncio.run(run_migrations_online())
|
||||
@@ -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"}
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Initial Migration
|
||||
|
||||
Revision ID: 8e7c02c0ca4c
|
||||
Revises:
|
||||
Create Date: 2025-08-22 22:08:41.229866
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence
|
||||
from typing import Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "8e7c02c0ca4c"
|
||||
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! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
pass
|
||||
# ### end Alembic commands ###
|
||||
Generated
+44
-3
@@ -45,6 +45,27 @@ files = [
|
||||
{file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alembic"
|
||||
version = "1.16.4"
|
||||
description = "A database migration tool for SQLAlchemy."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "alembic-1.16.4-py3-none-any.whl", hash = "sha256:b05e51e8e82efc1abd14ba2af6392897e145930c3e0a2faf2b0da2f7f7fd660d"},
|
||||
{file = "alembic-1.16.4.tar.gz", hash = "sha256:efab6ada0dd0fae2c92060800e0bf5c1dc26af15a10e02fb4babff164b4725e2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Mako = "*"
|
||||
SQLAlchemy = ">=1.4.0"
|
||||
tomli = {version = "*", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=4.12"
|
||||
|
||||
[package.extras]
|
||||
tz = ["tzdata"]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
@@ -1464,6 +1485,26 @@ files = [
|
||||
{file = "joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mako"
|
||||
version = "1.3.10"
|
||||
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"},
|
||||
{file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=0.9.2"
|
||||
|
||||
[package.extras]
|
||||
babel = ["Babel"]
|
||||
lingua = ["lingua"]
|
||||
testing = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
@@ -3289,8 +3330,7 @@ version = "2.2.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
markers = "python_full_version <= \"3.11.0a6\""
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||
@@ -3325,6 +3365,7 @@ files = [
|
||||
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||
]
|
||||
markers = {main = "python_version < \"3.11\"", dev = "python_full_version <= \"3.11.0a6\""}
|
||||
|
||||
[[package]]
|
||||
name = "tomlkit"
|
||||
@@ -3780,4 +3821,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.9,<4.0"
|
||||
content-hash = "5c5e256eb66c6d7520e1fd90000fa7cf5cac1845afe19b381c3cd3f28b699fad"
|
||||
content-hash = "75ec5ce48a9d43e45fe8abeaa6bed56830c44237fda6eb063bc52255ef4e9c6b"
|
||||
|
||||
@@ -21,6 +21,7 @@ click = ">=8.0.1"
|
||||
fastapi = {extras = ["standard"], version = "^0.116.1"}
|
||||
asyncpg = "^0.30.0"
|
||||
sqlalchemy = "^2.0.43"
|
||||
alembic = "^1.16.4"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pyyaml = ">=6.0.1"
|
||||
|
||||
@@ -16,7 +16,14 @@ app_configs: dict[str, Any] = {"title": "Numinar Coding Project"}
|
||||
if ENVIRONMENT not in SHOW_DOCS_ENVIRONMENTS:
|
||||
app_configs["openapi_url"] = None # Disable OpenAPI schema
|
||||
|
||||
create_db_and_tables()
|
||||
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Lifespan event to create database tables on startup."""
|
||||
await create_db_and_tables()
|
||||
yield # This is where the lifespan ends
|
||||
|
||||
|
||||
app_configs["lifespan"] = lifespan
|
||||
|
||||
app = FastAPI(**app_configs)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class Booking(Base):
|
||||
__tablename__ = "bookings"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, init=False, autoincrement=True)
|
||||
room_id: Mapped[int] = mapped_column(ForeignKey("room.id"))
|
||||
room_id: Mapped[int] = mapped_column(ForeignKey("rooms.id"))
|
||||
start_time: Mapped[datetime]
|
||||
end_time: Mapped[datetime]
|
||||
|
||||
@@ -75,13 +75,14 @@ class Invitee(Base):
|
||||
__tablename__ = "invitees"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, init=False, autoincrement=True)
|
||||
booking_id: Mapped[int] = mapped_column(ForeignKey("booking.id"))
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
|
||||
booking_id: Mapped[int] = mapped_column(ForeignKey("bookings.id"))
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
|
||||
|
||||
booking: Mapped["Booking"] = relationship(back_populates="invitees", init=False)
|
||||
user: Mapped["User"] = relationship(back_populates="invitees", init=False)
|
||||
|
||||
|
||||
def create_db_and_tables() -> None:
|
||||
async def create_db_and_tables() -> None: # pragma: no cover
|
||||
"""Create the database and tables."""
|
||||
Base.metadata.create_all(engine.sync_engine)
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
+66
-19
@@ -1,4 +1,10 @@
|
||||
"""SQL functions for the Numinar coding project backend."""
|
||||
"""SQL functions for the Numinar coding project backend.
|
||||
|
||||
Note:
|
||||
The session parameter is automatically injected by the `sessionize` decorator.
|
||||
The type hint for session is ignored to avoid needless complications with
|
||||
getting the type checkers to accept it.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TypedDict
|
||||
@@ -17,35 +23,48 @@ from backend.models import User
|
||||
|
||||
|
||||
@sessionize
|
||||
async def get_users(session: AsyncSession):
|
||||
async def get_users(
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Retrieve all users from the database."""
|
||||
result = await session.execute(select(User))
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@sessionize
|
||||
async def get_user_by_email(session: AsyncSession, email: str):
|
||||
async def get_user_by_email(
|
||||
email: str,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Retrieve a user by their email address."""
|
||||
result = await session.execute(select(User).where(User.email == email))
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
@sessionize
|
||||
async def get_rooms(session: AsyncSession):
|
||||
async def get_rooms(
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Retrieve all rooms from the database."""
|
||||
result = await session.execute(select(Room))
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@sessionize
|
||||
async def get_room(session: AsyncSession, room_id: int):
|
||||
async def get_room(
|
||||
room_id: int,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Retrieve a room by its ID."""
|
||||
result = await session.execute(select(Room).where(Room.id == room_id))
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
@sessionize
|
||||
async def new_room(session: AsyncSession, room: Room):
|
||||
async def new_room(
|
||||
room: Room,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Create a new room in the database."""
|
||||
session.add(room)
|
||||
await session.commit()
|
||||
@@ -56,14 +75,16 @@ class RoomParams(TypedDict):
|
||||
"""Parameters for updating a room."""
|
||||
|
||||
name: str
|
||||
location: str | None
|
||||
equipment: str | None
|
||||
capacity: int | None
|
||||
location: str
|
||||
equipment: str
|
||||
capacity: int
|
||||
|
||||
|
||||
@sessionize
|
||||
async def update_room(
|
||||
session: AsyncSession, room_id: int, **kwargs: Unpack[RoomParams]
|
||||
room_id: int,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
**kwargs: Unpack[RoomParams],
|
||||
):
|
||||
"""Update an existing room."""
|
||||
stmt = update(Room).where(Room.id == room_id).values(**kwargs)
|
||||
@@ -73,7 +94,10 @@ async def update_room(
|
||||
|
||||
|
||||
@sessionize
|
||||
async def delete_room(session: AsyncSession, room_id: int):
|
||||
async def delete_room(
|
||||
room_id: int,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Delete a room from the database."""
|
||||
stmt = delete(Room).where(Room.id == room_id)
|
||||
await session.execute(stmt)
|
||||
@@ -81,21 +105,30 @@ async def delete_room(session: AsyncSession, room_id: int):
|
||||
|
||||
|
||||
@sessionize
|
||||
async def get_bookings_for_room(session: AsyncSession, room_id: int):
|
||||
async def get_bookings_for_room(
|
||||
room_id: int,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Retrieve all bookings for a specific room."""
|
||||
result = await session.execute(select(Booking).where(Booking.room_id == room_id))
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@sessionize
|
||||
async def get_booking(session: AsyncSession, booking_id: int):
|
||||
async def get_booking(
|
||||
booking_id: int,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Retrieve a booking by its ID."""
|
||||
result = await session.execute(select(Booking).where(Booking.id == booking_id))
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
@sessionize
|
||||
async def new_booking(session: AsyncSession, booking: Booking):
|
||||
async def new_booking(
|
||||
booking: Booking,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Create a new booking in the database."""
|
||||
session.add(booking)
|
||||
await session.commit()
|
||||
@@ -112,7 +145,9 @@ class BookingParams(TypedDict):
|
||||
|
||||
@sessionize
|
||||
async def update_booking(
|
||||
session: AsyncSession, booking_id: int, **kwargs: Unpack[BookingParams]
|
||||
booking_id: int,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
**kwargs: Unpack[BookingParams],
|
||||
):
|
||||
"""Update an existing booking."""
|
||||
stmt = update(Booking).where(Booking.id == booking_id).values(**kwargs)
|
||||
@@ -122,7 +157,10 @@ async def update_booking(
|
||||
|
||||
|
||||
@sessionize
|
||||
async def delete_booking(session: AsyncSession, booking_id: int):
|
||||
async def delete_booking(
|
||||
booking_id: int,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Delete a booking from the database."""
|
||||
stmt = delete(Booking).where(Booking.id == booking_id)
|
||||
await session.execute(stmt)
|
||||
@@ -130,7 +168,10 @@ async def delete_booking(session: AsyncSession, booking_id: int):
|
||||
|
||||
|
||||
@sessionize
|
||||
async def get_invitees_for_booking(session: AsyncSession, booking_id: int):
|
||||
async def get_invitees_for_booking(
|
||||
booking_id: int,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Retrieve all invitees for a specific booking."""
|
||||
result = await session.execute(
|
||||
select(Invitee.user).where(Invitee.booking_id == booking_id)
|
||||
@@ -139,7 +180,11 @@ async def get_invitees_for_booking(session: AsyncSession, booking_id: int):
|
||||
|
||||
|
||||
@sessionize
|
||||
async def add_invitee_to_booking(session: AsyncSession, booking_id: int, email: str):
|
||||
async def add_invitee_to_booking(
|
||||
booking_id: int,
|
||||
email: str,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Add an invitee to a booking."""
|
||||
user = await get_user_by_email(session, email)
|
||||
if not user:
|
||||
@@ -153,7 +198,9 @@ async def add_invitee_to_booking(session: AsyncSession, booking_id: int, email:
|
||||
|
||||
@sessionize
|
||||
async def remove_invitee_from_booking(
|
||||
session: AsyncSession, booking_id: int, email: str
|
||||
booking_id: int,
|
||||
email: str,
|
||||
session: AsyncSession = None, # type: ignore
|
||||
):
|
||||
"""Remove an invitee from a booking."""
|
||||
user = await get_user_by_email(session, email)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
"""Test for the create_db_and_tables function in backend.models."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from backend.models import Base
|
||||
from backend.models import create_db_and_tables
|
||||
from backend.models import engine
|
||||
|
||||
|
||||
def test_create_db_and_tables() -> None:
|
||||
"""Test the create_db_and_tables function.
|
||||
|
||||
Verifies that the function correctly calls SQLModel.metadata.create_all with
|
||||
the synchronous engine.
|
||||
"""
|
||||
with patch.object(Base.metadata, "create_all") as mock_create_all:
|
||||
create_db_and_tables()
|
||||
mock_create_all.assert_called_once_with(engine.sync_engine)
|
||||
@@ -9,6 +9,9 @@ services:
|
||||
- ./backend/src:/app/src
|
||||
- ./backend/poetry.lock:/app/poetry.lock
|
||||
- ./backend/pyproject.toml:/app/pyproject.toml
|
||||
- ./backend/.env:/app/.env
|
||||
- ./backend/alembic.ini:/app/alembic.ini
|
||||
- ./backend/migrations:/app/migrations
|
||||
environment:
|
||||
ENVIRONMENT: development
|
||||
ports:
|
||||
|
||||
Reference in New Issue
Block a user