Skip to content

Commit 80156fc

Browse files
committed
Make migrations for both tasks and OSM DB
1 parent 8493228 commit 80156fc

9 files changed

Lines changed: 207 additions & 60 deletions

File tree

alembic.ini

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
[alembic]
2-
script_location = alembic
1+
[task]
2+
script_location = alembic_task
3+
sqlalchemy.url = postgresql+asyncpg://postgres:postgres@localhost:5432/tasking_manager
4+
5+
[osm]
6+
script_location = alembic_osm
37
sqlalchemy.url = postgresql+asyncpg://postgres:postgres@localhost:5432/tasking_manager
48

59
[loggers]

alembic/versions/9221408912dd_add_user_role_table.py

Lines changed: 0 additions & 47 deletions
This file was deleted.

alembic_osm/env.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import asyncio
2+
import importlib
3+
import os
4+
import sys
5+
from logging.config import fileConfig
6+
from pathlib import Path
7+
8+
# Add the project root directory to the Python path
9+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
10+
11+
from sqlalchemy import pool
12+
from sqlalchemy.engine import Connection
13+
from sqlalchemy.ext.asyncio import async_engine_from_config
14+
15+
from alembic import context
16+
from api.core.config import settings
17+
from api.core.database import Base
18+
19+
# Automatically import all models
20+
src_path = Path(__file__).parent.parent / "api" / "src"
21+
for path in src_path.rglob("*.py"):
22+
if path.name != "__init__.py":
23+
module_path = str(path.relative_to(Path(__file__).parent.parent)).replace(
24+
os.sep, "."
25+
)[:-3]
26+
try:
27+
importlib.import_module(module_path)
28+
except Exception as e:
29+
print(f"Failed to import {module_path}: {e}")
30+
31+
# this is the Alembic Config object
32+
config = context.config
33+
34+
# Interpret the config file for Python logging
35+
if config.config_file_name is not None:
36+
fileConfig(config.config_file_name)
37+
38+
# Set sqlalchemy.url from settings
39+
config.set_main_option("sqlalchemy.url", settings.OSM_DATABASE_URL)
40+
41+
# Add your model's MetaData object here for 'autogenerate' support
42+
target_metadata = Base.metadata
43+
44+
45+
def run_migrations_offline() -> None:
46+
"""Run migrations in 'offline' mode."""
47+
url = config.get_main_option("sqlalchemy.url")
48+
context.configure(
49+
url=url,
50+
target_metadata=target_metadata,
51+
literal_binds=True,
52+
dialect_opts={"paramstyle": "named"},
53+
)
54+
55+
with context.begin_transaction():
56+
context.run_migrations()
57+
58+
59+
def do_run_migrations(connection: Connection) -> None:
60+
context.configure(connection=connection, target_metadata=target_metadata)
61+
62+
with context.begin_transaction():
63+
context.run_migrations()
64+
65+
66+
async def run_async_migrations() -> None:
67+
"""In this scenario we need to create an Engine
68+
and associate a connection with the context."""
69+
70+
connectable = async_engine_from_config(
71+
config.get_section(config.config_ini_section, {}),
72+
prefix="sqlalchemy.",
73+
poolclass=pool.NullPool,
74+
)
75+
76+
async with connectable.connect() as connection:
77+
await connection.run_sync(do_run_migrations)
78+
79+
await connectable.dispose()
80+
81+
82+
def run_migrations_online() -> None:
83+
"""Run migrations in 'online' mode."""
84+
85+
asyncio.run(run_async_migrations())
86+
87+
88+
if context.is_offline_mode():
89+
run_migrations_offline()
90+
else:
91+
run_migrations_online()
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""add user role table
2+
3+
Revision ID: 9221408912dd
4+
Revises:
5+
Create Date: 2026-01-29 14:54:10.669000
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
from sqlalchemy import inspect, text
12+
import sqlalchemy as sa
13+
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = '9221408912dd'
17+
down_revision: Union[str, None] = None
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
bind = op.get_bind()
24+
insp = inspect(bind)
25+
26+
# Add unique constraint on users.auth_uid (if not already present)
27+
constraint_exists = bind.execute(
28+
text("SELECT 1 FROM pg_constraint WHERE conname = 'auth_uid_unique'")
29+
).scalar()
30+
if not constraint_exists:
31+
op.create_unique_constraint('auth_uid_unique', 'users', ['auth_uid'])
32+
33+
# Create the workspace_role enum type (if not already present)
34+
result = bind.execute(
35+
text("SELECT 1 FROM pg_type WHERE typname = 'workspace_role'")
36+
)
37+
if not result.scalar():
38+
workspace_role = sa.Enum('lead', 'validator', 'contributor', name='workspace_role')
39+
workspace_role.create(bind)
40+
41+
# Create the user_workspace_roles table (if not already present)
42+
if not insp.has_table('user_workspace_roles'):
43+
op.create_table(
44+
'user_workspace_roles',
45+
sa.Column('user_auth_uid', sa.Uuid(), nullable=False),
46+
sa.Column('workspace_id', sa.BigInteger(), nullable=False),
47+
sa.Column('role', sa.Enum('lead', 'validator', 'contributor', name='workspace_role', create_type=False), nullable=False),
48+
sa.ForeignKeyConstraint(['user_auth_uid'], ['users.auth_uid']),
49+
sa.PrimaryKeyConstraint('user_auth_uid', 'workspace_id')
50+
)
51+
52+
53+
def downgrade() -> None:
54+
bind = op.get_bind()
55+
insp = inspect(bind)
56+
57+
if insp.has_table('user_workspace_roles'):
58+
op.drop_table('user_workspace_roles')
59+
60+
# Drop the enum type
61+
result = bind.execute(
62+
text("SELECT 1 FROM pg_type WHERE typname = 'workspace_role'")
63+
)
64+
if result.scalar():
65+
workspace_role = sa.Enum('lead', 'validator', 'contributor', name='workspace_role')
66+
workspace_role.drop(bind)
67+
68+
constraint_exists = bind.execute(
69+
text("SELECT 1 FROM pg_constraint WHERE conname = 'auth_uid_unique'")
70+
).scalar()
71+
if constraint_exists:
72+
op.drop_constraint('auth_uid_unique', 'users', type_='unique')

alembic/env.py renamed to alembic_task/env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
if config.config_file_name is not None:
3636
fileConfig(config.config_file_name)
3737

38-
# Set sqlalchemy.url
38+
# Set sqlalchemy.url from settings
3939
config.set_main_option("sqlalchemy.url", settings.TASK_DATABASE_URL)
4040

4141
# Add your model's MetaData object here for 'autogenerate' support

alembic_task/script.py.mako

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
${imports if imports else ""}
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = ${repr(up_revision)}
16+
down_revision: Union[str, None] = ${repr(down_revision)}
17+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19+
20+
21+
def upgrade() -> None:
22+
${upgrades if upgrades else "pass"}
23+
24+
25+
def downgrade() -> None:
26+
${downgrades if downgrades else "pass"}

api/utils/migrations.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,18 @@ def run_migrations():
1515
current_dir = os.path.dirname(os.path.abspath(__file__))
1616
sys.path.insert(0, current_dir)
1717

18-
# Use sys.executable to run the Alembic module
19-
result = subprocess.run(
20-
[sys.executable, "-m", "alembic", "upgrade", "head"],
21-
capture_output=True,
22-
text=True,
23-
check=True,
24-
)
18+
# Run migrations for each database
19+
for db_name in ("task", "osm"):
20+
print(f"Running migrations for '{db_name}' database...")
21+
result = subprocess.run(
22+
[sys.executable, "-m", "alembic", "-n", db_name, "upgrade", "head"],
23+
capture_output=True,
24+
text=True,
25+
check=True,
26+
)
2527

26-
# Print the output if there's any
27-
if result.stdout:
28-
print("Migration output:", result.stdout)
28+
if result.stdout:
29+
print(f"Migration output ({db_name}):", result.stdout)
2930

3031
print("Migrations completed successfully!")
3132

0 commit comments

Comments
 (0)