Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/initializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@ def init_parameter(path: str = None) -> None:
default_parameter = json.load(f)

with session_ctx() as session:
# A parameter is identified by name and matrix, so skip the ones already
# stored instead of letting every re-run trip the unique constraint.
existing = set(
session.execute(select(Parameter.parameter_name, Parameter.matrix)).all()
)

for param in default_parameter:
if (param["parameter_name"], param["matrix"]) in existing:
continue
try:
parameter_obj = Parameter(
parameter_name=param["parameter_name"],
Expand Down
83 changes: 83 additions & 0 deletions tests/test_seed_prereqs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Reference data has to load before the seed touches it.

Migrations insert a handful of lexicon terms of their own, so "the table has
rows" says nothing about whether core/lexicon.json was ever loaded.
"""

import contextlib

import pytest
from sqlalchemy import func, select

from core.initializers import init_parameter
from db.engine import session_ctx
from db.parameter import Parameter
from transfers import seed as seed_module
from transfers.seed import (
REQUIRED_LEXICON_CATEGORIES,
assert_lexicon_ready,
ensure_seed_prereqs,
get_terms_by_category,
)


def test_ensure_seed_prereqs_loads_reference_data_even_when_tables_have_rows(
monkeypatch,
):
calls = []
monkeypatch.setattr(
"core.initializers.init_lexicon", lambda: calls.append("lexicon")
)
monkeypatch.setattr(
"core.initializers.init_parameter", lambda: calls.append("parameter")
)

ensure_seed_prereqs()

assert calls == ["lexicon", "parameter"]


def test_assert_lexicon_ready_names_the_empty_categories(monkeypatch):
empty = {"organization", "note_type"}

@contextlib.contextmanager
def fake_session_ctx():
yield object()

monkeypatch.setattr(seed_module, "session_ctx", fake_session_ctx)
monkeypatch.setattr(
seed_module,
"get_terms_by_category",
lambda _session, category: [] if category in empty else ["term"],
)

with pytest.raises(RuntimeError) as excinfo:
assert_lexicon_ready()

message = str(excinfo.value)
assert "organization" in message
assert "note_type" in message
assert "sample_method" not in message


def test_required_categories_have_terms_after_reference_data_loads():
with session_ctx() as session:
empty = [
category
for category in REQUIRED_LEXICON_CATEGORIES
if not get_terms_by_category(session, category)
]

assert empty == []


def test_init_parameter_leaves_existing_parameters_alone():
with session_ctx() as session:
before = session.scalar(select(func.count()).select_from(Parameter))

init_parameter()

with session_ctx() as session:
after = session.scalar(select(func.count()).select_from(Parameter))

assert after == before
54 changes: 46 additions & 8 deletions transfers/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,55 @@ def get_terms_by_category(s, category_name: str) -> list[LexiconTerm]:
)


# Lexicon categories the seed below draws terms from. Every one of them has to
# have at least one term or the seed cannot build a coherent row.
REQUIRED_LEXICON_CATEGORIES = (
"organization",
"relation",
"analysis_method_type",
"sample_method",
"activity_type",
"sensor_type",
"email_type",
"phone_type",
"address_type",
"well_purpose",
"casing_material",
"monitoring_frequency",
"note_type",
"participant_role",
)


def ensure_seed_prereqs() -> None:
"""Ensure that lexicon and parameter data exist before seeding."""
"""Load the reference lexicon and parameters that the seed data depends on.

Both initializers leave existing rows alone, so this runs unconditionally.
It used to skip them whenever the tables held any rows at all, which broke
once migrations started inserting lexicon terms of their own: on a fresh
database those few rows made the real lexicon look already-loaded, and the
seed then failed on empty categories.
"""
from core.initializers import init_lexicon, init_parameter

with session_ctx() as s:
has_lexicon = s.scalar(select(LexiconTerm.id).limit(1)) is not None
has_parameter = s.scalar(select(Parameter.id).limit(1)) is not None
init_lexicon()
init_parameter()

if not has_lexicon:
init_lexicon()
if not has_parameter:
init_parameter()

def assert_lexicon_ready() -> None:
"""Fail with the empty categories named, rather than deep inside the seed."""
with session_ctx() as s:
empty = [
category
for category in REQUIRED_LEXICON_CATEGORIES
if not get_terms_by_category(s, category)
]

if empty:
raise RuntimeError(
"Reference lexicon is incomplete; no terms for "
f"{', '.join(empty)}. Seeding cannot continue."
)


def contact_data_exists() -> bool:
Expand All @@ -79,6 +116,7 @@ def seed_all(n: int = 5, skip_if_exists: bool = False):
print("Contact data exists; skipping seeding.")
return
ensure_seed_prereqs()
assert_lexicon_ready()
new_mexico_bounds = [
(36.9, -106.6), # Taos area
(35.1, -106.6), # Albuquerque
Expand Down
Loading