From 4ed9cde5e7ead3d2a83115ecaadff26b51bf0738 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Wed, 18 Mar 2026 09:17:45 -0400 Subject: [PATCH 01/15] move get_db_version to cli utils Signed-off-by: Adrian Edwards --- collectoss/application/cli/_cli_util.py | 20 +++++++++++++++++++- collectoss/application/cli/db.py | 17 ++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/collectoss/application/cli/_cli_util.py b/collectoss/application/cli/_cli_util.py index 0e1a7e1aa..4198fc248 100644 --- a/collectoss/application/cli/_cli_util.py +++ b/collectoss/application/cli/_cli_util.py @@ -4,6 +4,7 @@ import psutil import signal from urllib.parse import urlparse +import sqlalchemy as s from collectoss.tasks.init.redis_connection import get_redis_connection @@ -69,4 +70,21 @@ def raise_open_file_limit(num_files): resource.setrlimit(resource.RLIMIT_NOFILE, (num_files, current_hard)) - return \ No newline at end of file + return + +def get_db_version(engine): + """ a helper function to return the database version + used in print_db_version and upgrade_db_version in the db cli module + """ + + db_version_sql = s.sql.text( + """ + SELECT * FROM augur_operations.augur_settings WHERE setting = 'augur_data_version' + """ + ) + + with engine.connect() as connection: + result = int(connection.execute(db_version_sql).fetchone()[2]) + + engine.dispose() + return result \ No newline at end of file diff --git a/collectoss/application/cli/db.py b/collectoss/application/cli/db.py index fd5db52cf..237644ba3 100644 --- a/collectoss/application/cli/db.py +++ b/collectoss/application/cli/db.py @@ -29,6 +29,8 @@ process_repo_group_csv, ) +from ._cli_util import get_db_version + logger = logging.getLogger(__name__) @@ -258,21 +260,6 @@ def add_github_org(ctx, organization_name): controller.add_cli_org(organization_name) -# get_db_version is a helper function to print_db_version and upgrade_db_version -def get_db_version(engine): - db_version_sql = s.sql.text( - """ - SELECT * FROM augur_operations.augur_settings WHERE setting = 'augur_data_version' - """ - ) - - with engine.connect() as connection: - result = int(connection.execute(db_version_sql).fetchone()[2]) - - engine.dispose() - return result - - @cli.command("print-db-version") @test_connection @test_db_connection From 843894dab8c218b42c0ca43ffd5b37293cf98895 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Wed, 18 Mar 2026 09:18:20 -0400 Subject: [PATCH 02/15] first pass new selfcheck CLI command file Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 collectoss/application/cli/selftest.py diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py new file mode 100644 index 000000000..257d05960 --- /dev/null +++ b/collectoss/application/cli/selftest.py @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: MIT +import logging +import click +import sqlalchemy as s + +from collectoss.application.cli import ( + test_connection, + test_db_connection, + with_database, + DatabaseContext, +) + +# from collectoss.application.db.session import DatabaseSession +from datetime import datetime +from collectoss.application.db.models import Repo + +from ._cli_util import get_db_version + + +logger = logging.getLogger(__name__) + + +@click.group("selftest", short_help="CollectOSS self-testing utilities") +@click.pass_context +def cli(ctx): + ctx.obj = DatabaseContext() + From df20b9aebadb3c10b8085bde2a6d56424124097a Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Wed, 18 Mar 2026 12:19:43 -0400 Subject: [PATCH 03/15] add selfcheck report command and the first query Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index 257d05960..e25a35f27 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -9,6 +9,7 @@ with_database, DatabaseContext, ) +from collectoss.application.db.models.augur_data import Commit # from collectoss.application.db.session import DatabaseSession from datetime import datetime @@ -25,3 +26,23 @@ def cli(ctx): ctx.obj = DatabaseContext() + +@cli.command("report") +@test_connection +@test_db_connection +@with_database +@click.pass_context +def run_selftest_report(ctx): + """ + Run queries to evaluate various aspects of the collectoss system's functioning and produce a report + """ + cmt_author_name_issue_3740_query = ( + select(func.count()) + .select_from(Commit) + .where(Commit.cmt_author_name.is_('')) + ) + cmt_author_name_issue_3740_count = None + + with ctx.obj.engine.begin() as connection: + cmt_author_name_issue_3740_count = connection.execute(cmt_author_name_issue_3740_query).one() + click.echo(f'Issue 3740 count: {cmt_author_name_issue_3740_count}') From cc533828bc61822ed1dcaccb1d51ac844ad377d7 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Wed, 18 Mar 2026 14:39:28 -0400 Subject: [PATCH 04/15] fixups Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index e25a35f27..5c20a6245 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -2,6 +2,7 @@ import logging import click import sqlalchemy as s +from sqlalchemy import select, func from collectoss.application.cli import ( test_connection, @@ -36,13 +37,15 @@ def run_selftest_report(ctx): """ Run queries to evaluate various aspects of the collectoss system's functioning and produce a report """ + click.echo('Generating CollectOSS selftest report....') + cmt_author_name_issue_3740_query = ( select(func.count()) .select_from(Commit) - .where(Commit.cmt_author_name.is_('')) + .where(Commit.cmt_author_name == '') ) cmt_author_name_issue_3740_count = None with ctx.obj.engine.begin() as connection: - cmt_author_name_issue_3740_count = connection.execute(cmt_author_name_issue_3740_query).one() - click.echo(f'Issue 3740 count: {cmt_author_name_issue_3740_count}') + cmt_author_name_issue_3740_count = connection.execute(cmt_author_name_issue_3740_query).scalar_one() + click.echo(f'Issue 3740 count: {cmt_author_name_issue_3740_count} commit files in the `commits` table contain authors with an empty string as their name') From 15425c46f88277cef35b32b8426177736f9b3565 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sat, 2 May 2026 13:29:25 -0400 Subject: [PATCH 05/15] 3740 -> 233 new issue number from the fork Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index 5c20a6245..c7dfbfe26 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -39,13 +39,13 @@ def run_selftest_report(ctx): """ click.echo('Generating CollectOSS selftest report....') - cmt_author_name_issue_3740_query = ( + cmt_author_name_issue_233_query = ( select(func.count()) .select_from(Commit) .where(Commit.cmt_author_name == '') ) - cmt_author_name_issue_3740_count = None + cmt_author_name_issue_233_count = None with ctx.obj.engine.begin() as connection: - cmt_author_name_issue_3740_count = connection.execute(cmt_author_name_issue_3740_query).scalar_one() - click.echo(f'Issue 3740 count: {cmt_author_name_issue_3740_count} commit files in the `commits` table contain authors with an empty string as their name') + cmt_author_name_issue_233_count = connection.execute(cmt_author_name_issue_233_query).scalar_one() + click.echo(f'Issue 233 count: {cmt_author_name_issue_233_count} commit files in the `commits` table contain authors with an empty string as their name') From 4ac236ef4bdd46bd8078ac91f85f1a35a4010f55 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sat, 2 May 2026 15:39:38 -0400 Subject: [PATCH 06/15] add function to fetch diff between current DB and the modeled one in the code Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index c7dfbfe26..d205e8b76 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -18,6 +18,8 @@ from ._cli_util import get_db_version +from alembic.config import Config +from alembic import command logger = logging.getLogger(__name__) @@ -28,6 +30,25 @@ def cli(ctx): ctx.obj = DatabaseContext() +def check_database_parity_with_versioned_model() -> list: + """Checks for alignent between the versioned SQLAlchemy models and the actual database state + + Returns: + list: a list of diff entries in alembic's format + """ + alembic_cfg = Config("alembic.ini") + try: + # 2. Call the 'check' command + # This will raise an AutogenerateDiffsDetected error if + # the database is out of sync with the models. + command.check(alembic_cfg) + return [] + + + except AutogenerateDiffsDetected as e: + return list(e.diffs) + + @cli.command("report") @test_connection @test_db_connection @@ -39,6 +60,8 @@ def run_selftest_report(ctx): """ click.echo('Generating CollectOSS selftest report....') + model_diffs = check_database_parity_with_versioned_model() + cmt_author_name_issue_233_query = ( select(func.count()) .select_from(Commit) From befd39b35b40fab4497489cc4376c5cb776c9d32 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sat, 2 May 2026 15:40:20 -0400 Subject: [PATCH 07/15] start working on a function to translate the diff for more human consumption Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 76 +++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index d205e8b76..93fa030de 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -1,8 +1,9 @@ # SPDX-License-Identifier: MIT import logging +from typing import Union import click import sqlalchemy as s -from sqlalchemy import select, func +from sqlalchemy import Constraint, ForeignKeyConstraint, Index, select, func, Table from collectoss.application.cli import ( test_connection, @@ -18,8 +19,10 @@ from ._cli_util import get_db_version -from alembic.config import Config +from alembic.config import Config, MessagingOptions, Namespace from alembic import command +from alembic.util.exc import AutogenerateDiffsDetected + logger = logging.getLogger(__name__) @@ -49,6 +52,71 @@ def check_database_parity_with_versioned_model() -> list: return list(e.diffs) +def output_alembic_diff(diff: list[Union[tuple, list]]): + """Print an alembic diff for consumption + + Args: + diff (list): A diff list created by Alembic + """ + if len(diff) == 0: + print("✅ No drift detected. Database is in sync with models.") + return + + print(f"❌ Database drift detected:") + + + for change in diff: + if isinstance(change, tuple): + action, category = change[0].split("_") + + action_text = "" + + if action == "add": + action_text = "found in models but not database" + elif action == "modify": + action_text = "modified" + elif action == "remove": + action_text = "found in database but not models" + + + if category == "table": + table_def:Table = change[1] + print(f"Table {table_def.name} in schema {table_def.schema}") + + elif category == "column": + _, _, table_name, col_def = change + print(f"Column {col_def.name} in table {table_name}") + + elif category == "fk": + fk_constraint_def:ForeignKeyConstraint = change[1] + fk_name = f" called '{fk_constraint_def.name}'" if fk_constraint_def.name else "" + print(f"Foreign Key{fk_name} on `{fk_constraint_def.table.columns[0]}` linking to `{fk_constraint_def.table}`") + + # TODO: attributes + elif category == "index": + index_def:Index = change[1] + print(f"Index {index_def.name}") + elif category == "constraint": + constraint_def:Constraint = change[1] + constraint_name = f" called '{constraint_def.name}'" if constraint_def.name else "" + constraint_type = constraint_def.__class__.__name__ or "Constraint" + constraint_covers = [c.name for c in constraint_def.columns] + print(f"{constraint_type}{constraint_name} covering {','.join(constraint_covers)}") + + else: + print("unknown type found in alembic diff") + print(change) + + print(f"\t{action_text}") + + + + elif isinstance(change, list): + pass + # TODO: probably a modify_nullable, handle this + + + @cli.command("report") @test_connection @test_db_connection @@ -71,4 +139,6 @@ def run_selftest_report(ctx): with ctx.obj.engine.begin() as connection: cmt_author_name_issue_233_count = connection.execute(cmt_author_name_issue_233_query).scalar_one() - click.echo(f'Issue 233 count: {cmt_author_name_issue_233_count} commit files in the `commits` table contain authors with an empty string as their name') + click.echo(f'Issue 233 count: {cmt_author_name_issue_233_count} commit changes in the `commits` table contain authors with an empty string as their name') + + print(output_alembic_diff(model_diffs)) \ No newline at end of file From 34498482c195daadfb5abefb98d6ef4ae0f56aa8 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sat, 2 May 2026 16:01:45 -0400 Subject: [PATCH 08/15] coerce all of the diff entries into lists so parsing logic is simpler Suggestion from Gemini Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index 93fa030de..32d1aee9c 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -64,9 +64,12 @@ def output_alembic_diff(diff: list[Union[tuple, list]]): print(f"❌ Database drift detected:") + # Alembic diffs use their own bespoke format, documented in https://alembic.sqlalchemy.org/en/latest/api/autogenerate.html#alembic.autogenerate.compare_metadata + for item in diff: + # coerce every item into a mini-diff list to make processing easier + changes_to_process = item if isinstance(item, list) else [item] - for change in diff: - if isinstance(change, tuple): + for change in changes_to_process: action, category = change[0].split("_") action_text = "" @@ -111,12 +114,6 @@ def output_alembic_diff(diff: list[Union[tuple, list]]): - elif isinstance(change, list): - pass - # TODO: probably a modify_nullable, handle this - - - @cli.command("report") @test_connection @test_db_connection From 54750d7a065f296caf1ac5a1bed044d384bbca61 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sat, 2 May 2026 16:02:55 -0400 Subject: [PATCH 09/15] refactor to move side effects (printing) outside the function Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 42 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index 32d1aee9c..4b7fcb375 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -52,17 +52,19 @@ def check_database_parity_with_versioned_model() -> list: return list(e.diffs) -def output_alembic_diff(diff: list[Union[tuple, list]]): - """Print an alembic diff for consumption +def humanize_alembic_diff(diff: list[Union[tuple, list]]) -> list[str]: + """transform an alembic diff for printing in a more intuitive way Args: diff (list): A diff list created by Alembic + + Returns: + list[str]: A list of findings as strings """ - if len(diff) == 0: - print("✅ No drift detected. Database is in sync with models.") - return + output = [] - print(f"❌ Database drift detected:") + if len(diff) == 0: + return output # Alembic diffs use their own bespoke format, documented in https://alembic.sqlalchemy.org/en/latest/api/autogenerate.html#alembic.autogenerate.compare_metadata for item in diff: @@ -70,6 +72,7 @@ def output_alembic_diff(diff: list[Union[tuple, list]]): changes_to_process = item if isinstance(item, list) else [item] for change in changes_to_process: + finding = "" action, category = change[0].split("_") action_text = "" @@ -84,34 +87,36 @@ def output_alembic_diff(diff: list[Union[tuple, list]]): if category == "table": table_def:Table = change[1] - print(f"Table {table_def.name} in schema {table_def.schema}") + finding += f"Table {table_def.name} in schema {table_def.schema}" elif category == "column": _, _, table_name, col_def = change - print(f"Column {col_def.name} in table {table_name}") + finding += f"Column {col_def.name} in table {table_name}" elif category == "fk": fk_constraint_def:ForeignKeyConstraint = change[1] fk_name = f" called '{fk_constraint_def.name}'" if fk_constraint_def.name else "" - print(f"Foreign Key{fk_name} on `{fk_constraint_def.table.columns[0]}` linking to `{fk_constraint_def.table}`") + finding += f"Foreign Key{fk_name} on `{fk_constraint_def.table.columns[0]}` linking to `{fk_constraint_def.table}`" # TODO: attributes elif category == "index": index_def:Index = change[1] - print(f"Index {index_def.name}") + finding += f"Index {index_def.name}" elif category == "constraint": constraint_def:Constraint = change[1] constraint_name = f" called '{constraint_def.name}'" if constraint_def.name else "" constraint_type = constraint_def.__class__.__name__ or "Constraint" constraint_covers = [c.name for c in constraint_def.columns] - print(f"{constraint_type}{constraint_name} covering {','.join(constraint_covers)}") + finding += f"{constraint_type}{constraint_name} covering {','.join(constraint_covers)}" else: - print("unknown type found in alembic diff") - print(change) + finding += "unknown type found in alembic diff:\n" + finding += str(change) - print(f"\t{action_text}") + finding += f"\n\t{action_text}" + output.append(finding) + return output @cli.command("report") @@ -138,4 +143,11 @@ def run_selftest_report(ctx): cmt_author_name_issue_233_count = connection.execute(cmt_author_name_issue_233_query).scalar_one() click.echo(f'Issue 233 count: {cmt_author_name_issue_233_count} commit changes in the `commits` table contain authors with an empty string as their name') - print(output_alembic_diff(model_diffs)) \ No newline at end of file + model_diff_findings = humanize_alembic_diff(model_diffs) + + if len(model_diff_findings) == 0: + print("✅ No drift detected. Database is in sync with models.") + else: + print(f"❌ Database drift detected:") + for f in model_diff_findings: + print(f) \ No newline at end of file From b097366ef3d3fecdaff10f8b523d29cb2d821b1b Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sat, 2 May 2026 18:26:13 -0400 Subject: [PATCH 10/15] remove internet connectivity test from selftest report CLI Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index 4b7fcb375..e8f110f81 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -120,7 +120,6 @@ def humanize_alembic_diff(diff: list[Union[tuple, list]]) -> list[str]: @cli.command("report") -@test_connection @test_db_connection @with_database @click.pass_context From 8dcfc6154d1e644354a9f6e777d919560c9b08df Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sat, 2 May 2026 18:48:18 -0400 Subject: [PATCH 11/15] display attributes on fk's Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index e8f110f81..187d82a0e 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -94,14 +94,27 @@ def humanize_alembic_diff(diff: list[Union[tuple, list]]) -> list[str]: finding += f"Column {col_def.name} in table {table_name}" elif category == "fk": - fk_constraint_def:ForeignKeyConstraint = change[1] - fk_name = f" called '{fk_constraint_def.name}'" if fk_constraint_def.name else "" - finding += f"Foreign Key{fk_name} on `{fk_constraint_def.table.columns[0]}` linking to `{fk_constraint_def.table}`" - - # TODO: attributes + fk_def:ForeignKeyConstraint = change[1] + fk_name = f" called '{fk_def.name}'" if fk_def.name else "" + finding += f"Foreign Key{fk_name} on `{fk_def.table.columns[0]}` linking to `{fk_def.table}`" + + attrs = [] + if fk_def.onupdate: + attrs.append(f"ON UPDATE {fk_def.onupdate}") + if fk_def.ondelete: + attrs.append(f"ON DELETE {fk_def.ondelete}") + if fk_def.deferrable: + attrs.append("DEFERRABLE") + if fk_def.initially: + attrs.append(f"INITIALLY {fk_def.initially}") + + if attrs: + finding += f"\n\tRules: {' | '.join(attrs)}" elif category == "index": index_def:Index = change[1] - finding += f"Index {index_def.name}" + cols = [c.name for c in index_def.columns] + finding += f"Index {index_def.name} on '{index_def.table}' covering {','.join(cols)}" + elif category == "constraint": constraint_def:Constraint = change[1] constraint_name = f" called '{constraint_def.name}'" if constraint_def.name else "" From 0b021dffe5da3fcda213d9f2a1f232f336e332b2 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Sat, 2 May 2026 18:52:06 -0400 Subject: [PATCH 12/15] add debug flag to get the raw output of the analysis Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index 187d82a0e..52febbbae 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -52,7 +52,7 @@ def check_database_parity_with_versioned_model() -> list: return list(e.diffs) -def humanize_alembic_diff(diff: list[Union[tuple, list]]) -> list[str]: +def humanize_alembic_diff(diff: list[Union[tuple, list]], debug=False) -> list[str]: """transform an alembic diff for printing in a more intuitive way Args: @@ -73,6 +73,15 @@ def humanize_alembic_diff(diff: list[Union[tuple, list]]) -> list[str]: for change in changes_to_process: finding = "" + + if debug: + # its not the most ideal to do this here rather than bypassing the whole function, + # but debug mode still benefits from the type coercion above + finding = str(change) + output.append(finding) + + continue + action, category = change[0].split("_") action_text = "" @@ -133,10 +142,11 @@ def humanize_alembic_diff(diff: list[Union[tuple, list]]) -> list[str]: @cli.command("report") +@click.option("--debug", is_flag=True, default=False, help="Show more detailed information for debuging purposes") @test_db_connection @with_database @click.pass_context -def run_selftest_report(ctx): +def run_selftest_report(ctx, debug): """ Run queries to evaluate various aspects of the collectoss system's functioning and produce a report """ @@ -155,7 +165,7 @@ def run_selftest_report(ctx): cmt_author_name_issue_233_count = connection.execute(cmt_author_name_issue_233_query).scalar_one() click.echo(f'Issue 233 count: {cmt_author_name_issue_233_count} commit changes in the `commits` table contain authors with an empty string as their name') - model_diff_findings = humanize_alembic_diff(model_diffs) + model_diff_findings = humanize_alembic_diff(model_diffs, debug=debug) if len(model_diff_findings) == 0: print("✅ No drift detected. Database is in sync with models.") From 500fca0e1355489f16d69e932aca37b20a887b59 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 4 May 2026 13:42:46 -0400 Subject: [PATCH 13/15] add type per the suggestion Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index 52febbbae..d6c463c47 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -61,7 +61,7 @@ def humanize_alembic_diff(diff: list[Union[tuple, list]], debug=False) -> list[s Returns: list[str]: A list of findings as strings """ - output = [] + output: list[str] = [] if len(diff) == 0: return output From b57517a03ad9baa161fbad9912d765d076c3d4d0 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 4 May 2026 13:44:10 -0400 Subject: [PATCH 14/15] print out nullable status too Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index d6c463c47..17aa79639 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -130,7 +130,11 @@ def humanize_alembic_diff(diff: list[Union[tuple, list]], debug=False) -> list[s constraint_type = constraint_def.__class__.__name__ or "Constraint" constraint_covers = [c.name for c in constraint_def.columns] finding += f"{constraint_type}{constraint_name} covering {','.join(constraint_covers)}" - + + elif category == "nullable": + _, schema, table_name, col_name, _, old, new = change + print(f"Column {col_name} in table {table_name} nullable changed: {old} -> {new}") + else: finding += "unknown type found in alembic diff:\n" finding += str(change) From 77dd7b7bcfb6529435aebc0d931a4a9adafb73a6 Mon Sep 17 00:00:00 2001 From: Adrian Edwards Date: Mon, 4 May 2026 14:02:49 -0400 Subject: [PATCH 15/15] remove unused imports Signed-off-by: Adrian Edwards --- collectoss/application/cli/selftest.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/collectoss/application/cli/selftest.py b/collectoss/application/cli/selftest.py index 17aa79639..587d939f6 100644 --- a/collectoss/application/cli/selftest.py +++ b/collectoss/application/cli/selftest.py @@ -2,24 +2,16 @@ import logging from typing import Union import click -import sqlalchemy as s from sqlalchemy import Constraint, ForeignKeyConstraint, Index, select, func, Table from collectoss.application.cli import ( - test_connection, test_db_connection, with_database, DatabaseContext, ) from collectoss.application.db.models.augur_data import Commit -# from collectoss.application.db.session import DatabaseSession -from datetime import datetime -from collectoss.application.db.models import Repo - -from ._cli_util import get_db_version - -from alembic.config import Config, MessagingOptions, Namespace +from alembic.config import Config from alembic import command from alembic.util.exc import AutogenerateDiffsDetected