summaryrefslogtreecommitdiffstats
path: root/db
diff options
context:
space:
mode:
Diffstat (limited to 'db')
-rw-r--r--db/__init__.py0
-rw-r--r--db/migrations/README1
-rw-r--r--db/migrations/env.py82
-rw-r--r--db/migrations/script.py.mako29
-rw-r--r--db/migrations/versions/202607012255-40e20fbf63c9_.py35
-rw-r--r--db/migrations/versions/202607012255-6ebaa6ab0f48_.py39
-rw-r--r--db/models.py13
-rw-r--r--db/session.py21
8 files changed, 220 insertions, 0 deletions
diff --git a/db/__init__.py b/db/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/db/__init__.py
diff --git a/db/migrations/README b/db/migrations/README
new file mode 100644
index 0000000..98e4f9c
--- /dev/null
+++ b/db/migrations/README
@@ -0,0 +1 @@
Generic single-database configuration. \ No newline at end of file
diff --git a/db/migrations/env.py b/db/migrations/env.py
new file mode 100644
index 0000000..f1b3156
--- /dev/null
+++ b/db/migrations/env.py
@@ -0,0 +1,82 @@
1from logging.config import fileConfig
2
3from sqlalchemy import engine_from_config
4from sqlalchemy import pool
5
6from alembic import context
7
8from conf import DATABASE_URL
9from db.models import *
10
11# this is the Alembic Config object, which provides
12# access to the values within the .ini file in use.
13config = context.config
14
15# Interpret the config file for Python logging.
16# This line sets up loggers basically.
17if config.config_file_name is not None:
18 fileConfig(config.config_file_name)
19
20# add your model's MetaData object here
21# for 'autogenerate' support
22# from myapp import mymodel
23# target_metadata = mymodel.Base.metadata
24target_metadata = SQLModel.metadata
25
26# other values from the config, defined by the needs of env.py,
27# can be acquired:
28# my_important_option = config.get_main_option("my_important_option")
29# ... etc.
30config.set_main_option('sqlalchemy.url', DATABASE_URL)
31
32
33def run_migrations_offline() -> None:
34 """Run migrations in 'offline' mode.
35
36 This configures the context with just a URL
37 and not an Engine, though an Engine is acceptable
38 here as well. By skipping the Engine creation
39 we don't even need a DBAPI to be available.
40
41 Calls to context.execute() here emit the given string to the
42 script output.
43
44 """
45 url = config.get_main_option("sqlalchemy.url")
46 context.configure(
47 url=url,
48 target_metadata=target_metadata,
49 literal_binds=True,
50 dialect_opts={"paramstyle": "named"},
51 )
52
53 with context.begin_transaction():
54 context.run_migrations()
55
56
57def run_migrations_online() -> None:
58 """Run migrations in 'online' mode.
59
60 In this scenario we need to create an Engine
61 and associate a connection with the context.
62
63 """
64 connectable = engine_from_config(
65 config.get_section(config.config_ini_section, {}),
66 prefix="sqlalchemy.",
67 poolclass=pool.NullPool,
68 )
69
70 with connectable.connect() as connection:
71 context.configure(
72 connection=connection, target_metadata=target_metadata
73 )
74
75 with context.begin_transaction():
76 context.run_migrations()
77
78
79if context.is_offline_mode():
80 run_migrations_offline()
81else:
82 run_migrations_online()
diff --git a/db/migrations/script.py.mako b/db/migrations/script.py.mako
new file mode 100644
index 0000000..697cf67
--- /dev/null
+++ b/db/migrations/script.py.mako
@@ -0,0 +1,29 @@
1"""${message}
2
3Revision ID: ${up_revision}
4Revises: ${down_revision | comma,n}
5Create Date: ${create_date}
6
7"""
8from typing import Sequence, Union
9
10from alembic import op
11import sqlalchemy as sa
12import sqlmodel
13${imports if imports else ""}
14
15# revision identifiers, used by Alembic.
16revision: str = ${repr(up_revision)}
17down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
18branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
19depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
20
21
22def upgrade() -> None:
23 """Upgrade schema."""
24 ${upgrades if upgrades else "pass"}
25
26
27def downgrade() -> None:
28 """Downgrade schema."""
29 ${downgrades if downgrades else "pass"}
diff --git a/db/migrations/versions/202607012255-40e20fbf63c9_.py b/db/migrations/versions/202607012255-40e20fbf63c9_.py
new file mode 100644
index 0000000..a32131a
--- /dev/null
+++ b/db/migrations/versions/202607012255-40e20fbf63c9_.py
@@ -0,0 +1,35 @@
1"""empty message
2
3Revision ID: 40e20fbf63c9
4Revises: 6ebaa6ab0f48
5Create Date: 2026-07-01 22:55:55.017591
6
7"""
8from typing import Sequence, Union
9
10from alembic import op
11import sqlalchemy as sa
12import sqlmodel
13
14
15# revision identifiers, used by Alembic.
16revision: str = '40e20fbf63c9'
17down_revision: Union[str, Sequence[str], None] = '6ebaa6ab0f48'
18branch_labels: Union[str, Sequence[str], None] = None
19depends_on: Union[str, Sequence[str], None] = None
20
21
22def upgrade() -> None:
23 """Upgrade schema."""
24 # ### commands auto generated by Alembic - please adjust! ###
25 op.add_column('user', sa.Column('external_id', sa.Uuid(), nullable=False, server_default=sa.text('gen_random_uuid()')))
26 op.create_index(op.f('ix_user_external_id'), 'user', ['external_id'], unique=True)
27 # ### end Alembic commands ###
28
29
30def downgrade() -> None:
31 """Downgrade schema."""
32 # ### commands auto generated by Alembic - please adjust! ###
33 op.drop_index(op.f('ix_user_external_id'), table_name='user')
34 op.drop_column('user', 'external_id')
35 # ### end Alembic commands ###
diff --git a/db/migrations/versions/202607012255-6ebaa6ab0f48_.py b/db/migrations/versions/202607012255-6ebaa6ab0f48_.py
new file mode 100644
index 0000000..9d4daa2
--- /dev/null
+++ b/db/migrations/versions/202607012255-6ebaa6ab0f48_.py
@@ -0,0 +1,39 @@
1"""empty message
2
3Revision ID: 6ebaa6ab0f48
4Revises:
5Create Date: 2026-07-01 22:55:14.146711
6
7"""
8from typing import Sequence, Union
9
10from alembic import op
11import sqlalchemy as sa
12import sqlmodel
13
14
15# revision identifiers, used by Alembic.
16revision: str = '6ebaa6ab0f48'
17down_revision: Union[str, Sequence[str], None] = None
18branch_labels: Union[str, Sequence[str], None] = None
19depends_on: Union[str, Sequence[str], None] = None
20
21
22def upgrade() -> None:
23 """Upgrade schema."""
24 # ### commands auto generated by Alembic - please adjust! ###
25 op.create_table('user',
26 sa.Column('id', sa.Integer(), nullable=False),
27 sa.Column('username', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
28 sa.PrimaryKeyConstraint('id')
29 )
30 op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True)
31 # ### end Alembic commands ###
32
33
34def downgrade() -> None:
35 """Downgrade schema."""
36 # ### commands auto generated by Alembic - please adjust! ###
37 op.drop_index(op.f('ix_user_username'), table_name='user')
38 op.drop_table('user')
39 # ### end Alembic commands ###
diff --git a/db/models.py b/db/models.py
new file mode 100644
index 0000000..b66f5bc
--- /dev/null
+++ b/db/models.py
@@ -0,0 +1,13 @@
1from uuid import uuid4, UUID
2
3from sqlmodel import SQLModel, Field
4
5
6class BaseModel(SQLModel):
7 id: int | None = Field(primary_key=True, default=None)
8
9class ExternalModelMixin:
10 external_id: UUID = Field(index=True, unique=True, default_factory=uuid4)
11
12class User(BaseModel, ExternalModelMixin, table=True):
13 username: str = Field(unique=True, index=True)
diff --git a/db/session.py b/db/session.py
new file mode 100644
index 0000000..4d83649
--- /dev/null
+++ b/db/session.py
@@ -0,0 +1,21 @@
1from contextlib import contextmanager
2
3from sqlalchemy import create_engine
4from sqlalchemy.orm import sessionmaker, scoped_session
5from sqlmodel import Session
6
7from conf import DATABASE_URL
8from utils.json import json_serializer
9
10
11engine = create_engine(DATABASE_URL, echo=True, future=True, json_serializer=json_serializer, pool_size=100, pool_recycle=3600)
12session_factory = sessionmaker(engine, class_=Session)
13ScopedSession = scoped_session(session_factory=session_factory)
14
15def get_session():
16 with ScopedSession() as session:
17 yield session
18
19@contextmanager