Skip to content

Commit 0ce6d1d

Browse files
pinkwahlars-petter-haugeUsamaEquinorAFK
committed
Allow sequential simulations
Co-authored-by: Zohar Malamant <git@wah.pink> Co-authored-by: Lars Petter Hauge <lars-petter-hauge@users.noreply.github.com> Co-authored-by: Muhammad Usama <110888193+UsamaEquinorAFK@users.noreply.github.com>
1 parent daa1248 commit 0ce6d1d

30 files changed

Lines changed: 854 additions & 278 deletions

.envrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ eval "$(devenv direnvrc)"
44

55
# The use_devenv function supports passing flags to the devenv command
66
# For example: use devenv --impure --option services.postgres.enable:bool true
7-
use devenv
7+
use devenv -P pg

.github/workflows/python-pgsql.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@ jobs:
4343
pipx install poetry
4444
poetry sync -E pg
4545
46+
- name: Create test database
47+
run: psql -h localhost -U postgres -c 'CREATE DATABASE "acidwatch_test";'
48+
env:
49+
PGPASSWORD: password
50+
4651
- name: Run pytest
4752
run: |
4853
poetry run alembic upgrade head
49-
poetry run pytest tests
54+
poetry run pytest tests --test-alembic
5055
env:
5156
ACIDWATCH_DATABASE: postgresql://postgres:password@localhost:5432/postgres
57+
ACIDWATCH_TEST_DATABASE: postgresql://postgres:password@localhost:5432/acidwatch_test

backend/alembic/env.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@ def run_migrations_online() -> None:
6262
and associate a connection with the context.
6363
6464
"""
65-
connectable = engine_from_config(
66-
config.get_section(config.config_ini_section, {}),
67-
prefix="sqlalchemy.",
68-
poolclass=pool.NullPool,
69-
)
65+
# Allow for overriding the connection during tests
66+
connectable = context.config.attributes.get("connection", None)
67+
68+
if connectable is None:
69+
connectable = engine_from_config(
70+
config.get_section(config.config_ini_section, {}),
71+
prefix="sqlalchemy.",
72+
poolclass=pool.NullPool,
73+
)
7074

7175
with connectable.connect() as connection:
7276
context.configure(connection=connection, target_metadata=target_metadata)
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""refactor simulation to use model_inputs table
2+
3+
Revision ID: 01aaa143d690
4+
Revises: c35588effcc4
5+
Create Date: 2026-01-21 16:50:46.128272
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
from alembic import op
13+
from sqlalchemy.dialects import postgresql
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "01aaa143d690"
17+
down_revision: Union[str, Sequence[str], None] = "c35588effcc4"
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+
"""Upgrade schema."""
24+
# ### commands auto generated by Alembic - please adjust! ###
25+
op.create_table(
26+
"model_inputs",
27+
sa.Column("simulation_id", sa.Uuid(), nullable=False),
28+
sa.Column("previous_model_input_id", sa.Uuid(), nullable=True),
29+
sa.Column("model_id", sa.String(), nullable=False),
30+
sa.Column("parameters", sa.JSON(), nullable=False),
31+
sa.Column("id", sa.Uuid(), nullable=False),
32+
sa.Column("created_at", sa.DateTime(), nullable=False),
33+
sa.Column("updated_at", sa.DateTime(), nullable=False),
34+
sa.ForeignKeyConstraint(
35+
["simulation_id"],
36+
["simulations.id"],
37+
),
38+
sa.ForeignKeyConstraint(
39+
["previous_model_input_id"],
40+
["model_inputs.id"],
41+
),
42+
sa.PrimaryKeyConstraint("id"),
43+
)
44+
op.add_column("results", sa.Column("model_input_id", sa.Uuid(), nullable=True))
45+
46+
op.execute("""
47+
INSERT INTO model_inputs (id, simulation_id, parameters, created_at, updated_at, model_id)
48+
SELECT
49+
gen_random_uuid(),
50+
simulations.id,
51+
simulations.parameters,
52+
simulations.created_at,
53+
simulations.updated_at,
54+
simulations.model_id
55+
FROM simulations
56+
""")
57+
58+
op.execute("""
59+
UPDATE results
60+
SET model_input_id = model_inputs.id
61+
FROM model_inputs
62+
WHERE results.simulation_id = model_inputs.simulation_id
63+
""")
64+
65+
op.alter_column("results", "model_input_id", nullable=False)
66+
op.drop_constraint(
67+
op.f("results_simulation_id_fkey"), "results", type_="foreignkey"
68+
)
69+
op.create_foreign_key(None, "results", "model_inputs", ["model_input_id"], ["id"])
70+
op.drop_column("results", "simulation_id")
71+
op.drop_column("simulations", "model_id")
72+
op.drop_column("simulations", "parameters")
73+
# ### end Alembic commands ###
74+
75+
76+
def downgrade() -> None:
77+
"""Downgrade schema."""
78+
# ### commands auto generated by Alembic - please adjust! ###
79+
op.add_column(
80+
"simulations",
81+
sa.Column(
82+
"parameters",
83+
postgresql.JSON(astext_type=sa.Text()),
84+
autoincrement=False,
85+
nullable=True,
86+
),
87+
)
88+
op.add_column(
89+
"simulations",
90+
sa.Column("model_id", sa.VARCHAR(), autoincrement=False, nullable=True),
91+
)
92+
op.add_column(
93+
"results",
94+
sa.Column("simulation_id", sa.UUID(), autoincrement=False, nullable=True),
95+
)
96+
97+
op.execute("""
98+
UPDATE simulations
99+
SET parameters = model_inputs.parameters,
100+
model_id = model_inputs.model_id
101+
FROM model_inputs
102+
WHERE model_inputs.simulation_id = simulations.id
103+
""")
104+
105+
op.execute("""
106+
UPDATE results
107+
SET simulation_id = model_inputs.simulation_id
108+
FROM model_inputs
109+
WHERE results.model_input_id = model_inputs.id
110+
""")
111+
112+
op.alter_column("simulations", "parameters", nullable=False)
113+
op.alter_column("simulations", "model_id", nullable=False)
114+
op.alter_column("results", "simulation_id", nullable=False)
115+
116+
op.create_foreign_key(
117+
op.f("results_simulation_id_fkey"),
118+
"results",
119+
"simulations",
120+
["simulation_id"],
121+
["id"],
122+
)
123+
op.drop_column("results", "model_input_id")
124+
op.drop_table("model_inputs")
125+
# ### end Alembic commands ###

backend/poetry.lock

Lines changed: 23 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ mypy = "*"
3939
pytest = "*"
4040
ruff = "*"
4141
pytest-asyncio = "*"
42+
pytest-alembic = "^0.12.1"
4243

4344
[build-system]
4445
requires = ["poetry-core>=1.0.0"]

backend/src/acidwatch_api/database.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,36 @@ class Simulation(Base):
4444
__tablename__ = "simulations"
4545

4646
owner_id: Mapped[UUID | None] = mapped_column(Uuid)
47-
model_id: Mapped[str] = mapped_column()
4847
concentrations: Mapped[dict[str, float]] = mapped_column(JSON)
48+
49+
model_inputs: Mapped[list[ModelInput]] = relationship(back_populates="simulation")
50+
51+
52+
class ModelInput(Base):
53+
__tablename__ = "model_inputs"
54+
55+
simulation_id: Mapped[UUID] = mapped_column(ForeignKey("simulations.id"))
56+
previous_model_input_id: Mapped[UUID | None] = mapped_column(
57+
ForeignKey("model_inputs.id")
58+
)
59+
model_id: Mapped[str] = mapped_column()
4960
parameters: Mapped[dict[str, Any]] = mapped_column(JSON)
5061

51-
result: Mapped[Result | None] = relationship(back_populates="simulation")
62+
simulation: Mapped[Simulation] = relationship("Simulation")
63+
previous_model_input: Mapped[ModelInput | None] = relationship()
64+
result: Mapped[ModelResult | None] = relationship(back_populates="model_input")
5265

5366

54-
class Result(Base):
67+
class ModelResult(Base):
5568
__tablename__ = "results"
5669

57-
simulation_id: Mapped[UUID] = mapped_column(ForeignKey("simulations.id"))
70+
model_input_id: Mapped[UUID] = mapped_column(ForeignKey("model_inputs.id"))
5871
concentrations: Mapped[dict[str, float]] = mapped_column(JSON)
5972
panels: Mapped[list[Any]] = mapped_column(JSON)
6073
python_exception: Mapped[BaseException | None] = mapped_column(PickleType)
6174
error: Mapped[str | None] = mapped_column()
6275

63-
simulation: Mapped[Simulation] = relationship("Simulation")
76+
model_input: Mapped[ModelInput] = relationship("ModelInput")
6477

6578

6679
class AppState(TypedDict):

0 commit comments

Comments
 (0)