Skip to content

Commit 66dcf5d

Browse files
committed
Merge remote-tracking branch 'origin/enterprise' into feat/TG-1121-normalize-run-summary-count-shapes
2 parents 3bdfeab + 68ebb0c commit 66dcf5d

52 files changed

Lines changed: 430 additions & 381 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

deploy/testgen-base.dockerfile

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ RUN apk update && apk upgrade && apk add --no-cache \
2727
openblas=0.3.30-r2 \
2828
openblas-dev=0.3.30-r2 \
2929
unixodbc=2.3.14-r0 \
30-
unixodbc-dev=2.3.14-r0 \
31-
libarrow=21.0.0-r4 \
32-
apache-arrow-dev=21.0.0-r4
30+
unixodbc-dev=2.3.14-r0
3331

3432
COPY --chmod=775 ./deploy/install_linuxodbc.sh /tmp/dk/install_linuxodbc.sh
3533
RUN /tmp/dk/install_linuxodbc.sh
@@ -39,7 +37,7 @@ COPY ./pyproject.toml /tmp/dk/pyproject.toml
3937
RUN mkdir /dk
4038

4139
# Upgrading pip for security
42-
RUN python3 -m pip install --no-cache-dir --upgrade pip==26.0
40+
RUN python3 -m pip install --no-cache-dir --upgrade pip==26.1.2
4341

4442
# hdbcli only ships manylinux wheels (no musl). pip 26+ correctly rejects these on Alpine.
4543
# We download the wheel for the correct arch, then extract it directly into site-packages
@@ -73,8 +71,7 @@ RUN apk del \
7371
openssl \
7472
linux-headers \
7573
openblas-dev \
76-
unixodbc-dev \
77-
apache-arrow-dev
74+
unixodbc-dev
7875

7976
# Remove interactive ODBC tools — not needed at runtime, and iusql triggers
8077
# false-positive secret detection in security scanners (SECRET-3010)

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ dependencies = [
6161
"xlsxwriter==3.2.0",
6262
"psutil==5.9.8",
6363
"concurrent_log_handler==0.9.25",
64-
"cryptography==46.0.6",
64+
"cryptography==46.0.7",
6565
"validators==0.33.0",
6666
"reportlab==4.2.2",
6767
"cron-converter==1.2.1",
@@ -72,7 +72,7 @@ dependencies = [
7272
"holidays~=0.89",
7373

7474
# Pinned to match the manually compiled libs or for security
75-
"pyarrow==21.0.0",
75+
"pyarrow==23.0.1",
7676
"matplotlib==3.9.2",
7777
"scipy==1.14.1",
7878
"jinja2==3.1.6",
@@ -82,7 +82,7 @@ dependencies = [
8282
# MCP server
8383
"mcp[cli]==1.26.0",
8484
"uvicorn==0.41.0",
85-
"PyJWT==2.12.0",
85+
"PyJWT==2.13.0",
8686
"bcrypt==5.0.0",
8787

8888
# API & OAuth server

testgen/api/runs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
)
3636
def get_test_run(job: JobExecution = resolve_job("view", JobExecution.job_key == JobKey.run_tests)): # noqa: B008
3737
"""Get a test run by the job execution ID that created it."""
38-
test_run = TestRun.get_by_id_or_job(job.id)
38+
test_run = TestRun.get(job.id)
3939

4040
result = None
4141
if test_run:
@@ -69,7 +69,7 @@ def get_test_run(job: JobExecution = resolve_job("view", JobExecution.job_key ==
6969
)
7070
def get_profiling_run(job: JobExecution = resolve_job("view", JobExecution.job_key == JobKey.run_profile)): # noqa: B008
7171
"""Get a profiling run by the job execution ID that created it."""
72-
profiling_run = ProfilingRun.get_by_id_or_job(job.id)
72+
profiling_run = ProfilingRun.get(job.id)
7373

7474
result = None
7575
if profiling_run:

testgen/commands/job_registry.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def run_final_callbacks(job_exec: JobExecution) -> None:
8080
def _notify_profiling_run(job_exec: JobExecution) -> None:
8181
with database_session() as session:
8282
profiling_run = session.scalars(
83-
select(ProfilingRun).where(ProfilingRun.job_execution_id == job_exec.id)
83+
select(ProfilingRun).where(ProfilingRun.id == job_exec.id)
8484
).first()
8585
if not profiling_run:
8686
LOG.warning("No profiling_run found for job %s; skipping notification", job_exec.id)
@@ -90,7 +90,7 @@ def _notify_profiling_run(job_exec: JobExecution) -> None:
9090

9191
def _notify_test_run(job_exec: JobExecution) -> None:
9292
with database_session() as session:
93-
test_run = session.scalars(select(TestRun).where(TestRun.job_execution_id == job_exec.id)).first()
93+
test_run = session.scalars(select(TestRun).where(TestRun.id == job_exec.id)).first()
9494
if not test_run:
9595
LOG.warning("No test_run found for job %s; skipping notification", job_exec.id)
9696
return
@@ -99,7 +99,7 @@ def _notify_test_run(job_exec: JobExecution) -> None:
9999

100100
def _notify_monitor_run(job_exec: JobExecution) -> None:
101101
with database_session() as session:
102-
test_run = session.scalars(select(TestRun).where(TestRun.job_execution_id == job_exec.id)).first()
102+
test_run = session.scalars(select(TestRun).where(TestRun.id == job_exec.id)).first()
103103
if not test_run:
104104
LOG.warning("No test_run found for job %s; skipping monitor notification", job_exec.id)
105105
return

testgen/commands/job_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ def _print_run_summary(job_id: UUID, job_key: str) -> None:
7171
session = get_current_session()
7272
match job_key:
7373
case "run-profile":
74-
run = session.scalars(select(ProfilingRun).where(ProfilingRun.job_execution_id == job_id)).first()
74+
run = session.scalars(select(ProfilingRun).where(ProfilingRun.id == job_id)).first()
7575
if run:
7676
status_msg = "Profiling encountered an error. Check log for details." if run.job_execution.status == JobStatus.ERROR else "Profiling completed."
7777
click.echo(f"\n {status_msg}\n Run ID: {run.id}\n ")
7878
case "run-tests" | "run-monitors":
79-
run = session.scalars(select(TestRun).where(TestRun.job_execution_id == job_id)).first()
79+
run = session.scalars(select(TestRun).where(TestRun.id == job_id)).first()
8080
if run:
8181
status_msg = "Test execution encountered an error. Check log for details." if run.job_execution.status == JobStatus.ERROR else "Test execution completed."
8282
click.echo(f"\n {status_msg}\n Run ID: {run.id}\n ")

testgen/commands/run_data_cleanup.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,9 @@ def run_data_cleanup(project_code: str, retention_days: int) -> None:
4242
with database_session():
4343
protected_profiling_ids = ProfilingRun.find_latest_per_table_group(project_code)
4444
protected_test_run_ids = TestRun.find_latest_per_test_suite(project_code)
45-
# Translate protected run ids → their job_execution_ids so the JE sweep
46-
# can carve them out. Nulls (older runs without a JE) are filtered here.
47-
je_map = {
48-
**ProfilingRun.get_job_execution_ids(list(protected_profiling_ids)),
49-
**TestRun.get_job_execution_ids(list(protected_test_run_ids)),
50-
}
51-
protected_job_execution_ids = {je for je in je_map.values() if je is not None}
45+
# The run id is the job execution id, so the protected run ids are
46+
# already the job executions the sweep must carve out.
47+
protected_job_execution_ids = protected_profiling_ids | protected_test_run_ids
5248

5349
LOG.info(
5450
"Protected latest runs: profiling=%d test=%d job_executions=%d",

testgen/commands/run_profiling.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
import os
32
from datetime import UTC, datetime, timedelta
43
from uuid import UUID
54

@@ -52,14 +51,12 @@ def run_profiling(
5251

5352
LOG.info("Creating profiling run record")
5453
profiling_run = ProfilingRun(
54+
id=job_context.get().job_id,
5555
project_code=table_group.project_code,
5656
connection_id=connection.connection_id,
5757
table_groups_id=table_group.id,
5858
profiling_starttime=datetime.now(UTC) + time_delta,
59-
process_id=os.getpid(),
6059
)
61-
if job_id := job_context.get().job_id:
62-
profiling_run.job_execution_id = job_id
6360

6461
# This runs in a subprocess — commit after every save so progress is visible
6562
# to the UI (separate session) and to execute_db_queries (independent connection).
@@ -96,19 +93,12 @@ def run_profiling(
9693
# run_pairwise_contingency_check(profiling_run.id, table_group.profile_pair_rule_pct)
9794
else:
9895
LOG.info("No columns were selected to profile.")
99-
except Exception as e:
96+
except Exception:
10097
LOG.exception("Profiling encountered an error.")
101-
LOG.info("Setting profiling run status to Error")
102-
profiling_run.log_message = get_exception_message(e)
103-
profiling_run.profiling_endtime = datetime.now(UTC) + time_delta
104-
profiling_run.status = "Error"
105-
profiling_run.save()
106-
session.commit()
98+
end_time = datetime.now(UTC) + time_delta
10799
raise
108100
else:
109-
LOG.info("Setting profiling run status to Completed")
110-
profiling_run.profiling_endtime = datetime.now(UTC) + time_delta
111-
profiling_run.status = "Complete"
101+
end_time = datetime.now(UTC) + time_delta
112102
profiling_run.save()
113103
session.commit()
114104

@@ -122,7 +112,7 @@ def run_profiling(
122112
sampling=table_group.profile_use_sampling,
123113
table_count=profiling_run.table_ct or 0,
124114
column_count=profiling_run.column_ct or 0,
125-
run_duration=(profiling_run.profiling_endtime - profiling_run.profiling_starttime).total_seconds(),
115+
run_duration=(end_time - profiling_run.profiling_starttime.replace(tzinfo=UTC)).total_seconds(),
126116
)
127117

128118
return profiling_run.id

testgen/commands/run_score_update.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def run_score_update(parent_job_id: str, parent_job_key: str) -> None:
3636
def _rollup_profiling(parent_je_id: UUID) -> None:
3737
with database_session() as session:
3838
profiling_run = session.scalars(
39-
select(ProfilingRun).where(ProfilingRun.job_execution_id == parent_je_id)
39+
select(ProfilingRun).where(ProfilingRun.id == parent_je_id)
4040
).first()
4141
if not profiling_run:
4242
LOG.error("No profiling_run found for job execution %s; skipping score rollup", parent_je_id)
@@ -60,7 +60,7 @@ def _rollup_test(parent_je_id: UUID) -> None:
6060
row = session.execute(
6161
select(TestRun.id, TestRun.test_starttime, TestSuite.table_groups_id, TestSuite.project_code)
6262
.join(TestSuite, TestRun.test_suite_id == TestSuite.id)
63-
.where(TestRun.job_execution_id == parent_je_id)
63+
.where(TestRun.id == parent_je_id)
6464
).first()
6565
if not row:
6666
LOG.error("No test_run found for job execution %s; skipping score rollup", parent_je_id)

testgen/commands/run_test_execution.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
import os
32
from collections import defaultdict
43
from datetime import UTC, datetime, timedelta
54
from functools import partial
@@ -24,7 +23,6 @@
2423
from testgen.common.models.table_group import TableGroup
2524
from testgen.common.models.test_run import TestRun
2625
from testgen.common.models.test_suite import TestSuite
27-
from testgen.utils import get_exception_message
2826

2927
from .run_refresh_data_chars import run_data_chars_refresh
3028
from .run_test_validation import run_test_validation
@@ -52,12 +50,10 @@ def run_test_execution(
5250

5351
LOG.info("Creating test run record")
5452
test_run = TestRun(
53+
id=job_context.get().job_id,
5554
test_suite_id=test_suite_id,
5655
test_starttime=datetime.now(UTC) + time_delta,
57-
process_id=os.getpid(),
5856
)
59-
if job_id := job_context.get().job_id:
60-
test_run.job_execution_id = job_id
6157

6258
# This runs in a subprocess — commit after every save so progress is visible
6359
# to the UI (separate session) and to execute_db_queries (independent connection).
@@ -134,22 +130,12 @@ def run_test_execution(
134130
# Refresh needed because previous query updates the test run too
135131
test_run.refresh()
136132

137-
except Exception as e:
133+
except Exception:
138134
LOG.exception("Test execution encountered an error.")
139-
LOG.info("Setting test run status to Error")
140-
test_run.log_message = get_exception_message(e)
141-
test_run.test_endtime = datetime.now(UTC) + time_delta
142-
test_run.status = "Error"
143-
test_run.save()
144-
session.commit()
135+
end_time = datetime.now(UTC) + time_delta
145136
raise
146137
else:
147-
LOG.info("Setting test run status to Completed")
148-
test_run.test_endtime = datetime.now(UTC) + time_delta
149-
test_run.status = "Complete"
150-
test_run.save()
151-
session.commit()
152-
138+
end_time = datetime.now(UTC) + time_delta
153139
LOG.info("Updating latest run for test suite")
154140
test_suite.last_complete_test_run_id = test_run.id
155141
test_suite.save()
@@ -167,7 +153,7 @@ def run_test_execution(
167153
username=username,
168154
sql_flavor=connection.sql_flavor_code,
169155
test_count=test_run.test_ct,
170-
run_duration=(test_run.test_endtime - test_run.test_starttime.replace(tzinfo=UTC)).total_seconds(),
156+
run_duration=(end_time - test_run.test_starttime.replace(tzinfo=UTC)).total_seconds(),
171157
prediction_duration=(datetime.now(UTC) + time_delta - prediction_start).total_seconds(),
172158
)
173159

testgen/common/models/data_column.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from testgen.common.models import get_current_session
2323
from testgen.common.models.entity import Entity, EntityMinimal
2424
from testgen.common.models.hygiene_issue import HygieneIssue
25+
from testgen.common.models.job_execution import JobExecution
2526
from testgen.common.models.profile_result import ProfileResult
2627
from testgen.common.models.profiling_run import ProfilingRun
2728

@@ -559,11 +560,11 @@ def get_column_detail(
559560
cls.dq_score_testing,
560561
func.coalesce(hygiene_subq.c.hygiene_issue_count, 0).label("hygiene_issue_count"),
561562
ProfilingRun.id.label("profile_run_id"),
562-
ProfilingRun.job_execution_id.label("profile_run_je_id"),
563-
ProfilingRun.status.label("profile_run_status"),
563+
ProfilingRun.id.label("profile_run_je_id"),
564+
JobExecution.status.label("profile_run_status"),
564565
ProfilingRun.profiling_starttime.label("profile_run_started_at"),
565-
ProfilingRun.profiling_endtime.label("profile_run_ended_at"),
566-
ProfilingRun.log_message.label("profile_run_log_message"),
566+
JobExecution.completed_at.label("profile_run_ended_at"),
567+
JobExecution.error_message.label("profile_run_log_message"),
567568
)
568569
.outerjoin(DataTable, DataTable.id == cls.table_id)
569570
.outerjoin(
@@ -585,6 +586,7 @@ def get_column_detail(
585586
),
586587
)
587588
.outerjoin(ProfilingRun, ProfilingRun.id == ProfileResult.profile_run_id)
589+
.outerjoin(JobExecution, JobExecution.id == ProfilingRun.id)
588590
.where(
589591
cls.table_groups_id == table_groups_id,
590592
cls.table_name == table_name,

0 commit comments

Comments
 (0)