From 63b5f2b90800f70a476cc452b743492ea6fee1ab Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Fri, 12 Dec 2025 16:57:54 +0000 Subject: [PATCH 1/5] Make batch_id a string to match the recently modified tracking schema --- src/npgtracking/db/schema.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/npgtracking/db/schema.py b/src/npgtracking/db/schema.py index 0b55bcc..2ad3c04 100644 --- a/src/npgtracking/db/schema.py +++ b/src/npgtracking/db/schema.py @@ -42,6 +42,7 @@ with the import from sqlalchemy.dialects.mysql """ + class Base(DeclarativeBase): pass @@ -637,7 +638,7 @@ class Run(Base): actual_cycle_count: Mapped[Optional[int]] = mapped_column(BIGINT) expected_cycle_count: Mapped[Optional[int]] = mapped_column(BIGINT) id_run_pair: Mapped[Optional[int]] = mapped_column(BIGINT) - batch_id: Mapped[Optional[int]] = mapped_column(BIGINT) + batch_id: Mapped[Optional[int]] = mapped_column(String(64)) flowcell_id: Mapped[Optional[str]] = mapped_column(String(64)) folder_name: Mapped[Optional[str]] = mapped_column(String(64)) folder_path_glob: Mapped[Optional[str]] = mapped_column(String(256)) From 2ebce582f231ad66996bf93926804d5ce3f73f49 Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Fri, 12 Dec 2025 16:59:01 +0000 Subject: [PATCH 2/5] Add a rudimentary getter for Run objects --- src/npgtracking/db/retrieval.py | 12 +++++++++++ tests/data/db_fixtures/300-Run.yml | 13 ++++++++++++ tests/test_schema.py | 34 ++++++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/npgtracking/db/retrieval.py b/src/npgtracking/db/retrieval.py index 71a4c80..4058195 100644 --- a/src/npgtracking/db/retrieval.py +++ b/src/npgtracking/db/retrieval.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from sqlalchemy import select from sqlalchemy.orm import Session from npgtracking.db.schema import ( @@ -66,3 +67,14 @@ def get_runs_by_currentstatus( Manufacturer.name == manufacturer_name, ) return query.all() + + +def get_run_by_batch_and_flowcell( + session: Session, batch_id: str, flowcell_id: str +) -> Run | None: + result = session.execute( + select(Run) + .where(Run.batch_id == batch_id) + .where(Run.flowcell_id == flowcell_id) + ).scalar_one_or_none() + return result diff --git a/tests/data/db_fixtures/300-Run.yml b/tests/data/db_fixtures/300-Run.yml index 2c15cc9..ea35f2d 100644 --- a/tests/data/db_fixtures/300-Run.yml +++ b/tests/data/db_fixtures/300-Run.yml @@ -51,3 +51,16 @@ is_paired: 0 priority: 1 team: 'SR' +- actual_cycle_count: ~ + batch_id: O44 batch 24798 pool 1 + expected_cycle_count: ~ + flowcell_id: 430591 + folder_name: 430591-20251204_1628 + folder_path_glob: + id_instrument: 130 + id_instrument_format: 25 + id_run: 51533 + id_run_pair: ~ + is_paired: 0 + priority: 1 + team: 'SR' \ No newline at end of file diff --git a/tests/test_schema.py b/tests/test_schema.py index 2aad2ee..cacfc42 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -17,7 +17,10 @@ from pytest import mark as m -from npgtracking.db.retrieval import get_runs_by_currentstatus +from npgtracking.db.retrieval import ( + get_run_by_batch_and_flowcell, + get_runs_by_currentstatus, +) @m.describe("SchemaModel") @@ -28,7 +31,9 @@ class TestSchemaModel(object): def test_schema_no_runs(self, tracking_session): status = "run mirrored" manufacturer = "Ultima Genomics" - tracking_runs = get_runs_by_currentstatus(tracking_session, status, manufacturer) + tracking_runs = get_runs_by_currentstatus( + tracking_session, status, manufacturer + ) assert len(tracking_runs) == 0 @m.context("When retrieving run records from tracking DB") @@ -39,7 +44,9 @@ def test_schema_no_runs(self, tracking_session): def test_schema_single_run(self, tracking_session): status = "run in progress" manufacturer = "Ultima Genomics" - tracking_runs = get_runs_by_currentstatus(tracking_session, status, manufacturer) + tracking_runs = get_runs_by_currentstatus( + tracking_session, status, manufacturer + ) assert len(tracking_runs) == 1 run = tracking_runs.pop() @@ -61,7 +68,9 @@ def test_schema_single_run(self, tracking_session): def test_schema_multiple_runs(self, tracking_session): status = "off-tool automation in progress" manufacturer = "Ultima Genomics" - tracking_runs = get_runs_by_currentstatus(tracking_session, status, manufacturer) + tracking_runs = get_runs_by_currentstatus( + tracking_session, status, manufacturer + ) assert len(tracking_runs) == 2 for run in tracking_runs: @@ -71,3 +80,20 @@ def test_schema_multiple_runs(self, tracking_session): ).pop() assert run.instrument_format.manufacturer.name == manufacturer assert current_run_status.run_status_dict.description == status + + @m.context("When getting a run by IDs") + @m.it("Gives us one or no runs") + def test_run_by_flowcell_batch(self, tracking_session): + assert ( + get_run_by_batch_and_flowcell( + session=tracking_session, batch_id=None, flowcell_id=None + ) + is None + ) + + run = get_run_by_batch_and_flowcell( + session=tracking_session, + batch_id="O44 batch 24798 pool 1", + flowcell_id="430591", + ) + assert run.id_run == 51533 From 7eeb16f1284647efae06aeb7e3433c271e65f930 Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Fri, 12 Dec 2025 17:01:36 +0000 Subject: [PATCH 3/5] Update changelog in anticipation of a release. --- CHANGELOG | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ec5dc4b..b9e6c3f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.2.0] - 2025-12-12 + +### Added + +* get_run_by_batch_and_flowcell function added to retrieval module + +### Fixed + +* run.batch_id is now a varchar + ## [0.1.1] - 2025-12-11 ### Fixed From 9a5c00ba98fe2b1def974cb1bc1bad2146a6a403 Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Mon, 15 Dec 2025 15:29:01 +0000 Subject: [PATCH 4/5] Add a docstring --- src/npgtracking/db/retrieval.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/npgtracking/db/retrieval.py b/src/npgtracking/db/retrieval.py index 4058195..5d5f7ad 100644 --- a/src/npgtracking/db/retrieval.py +++ b/src/npgtracking/db/retrieval.py @@ -72,6 +72,21 @@ def get_runs_by_currentstatus( def get_run_by_batch_and_flowcell( session: Session, batch_id: str, flowcell_id: str ) -> Run | None: + """ + Get a Run by its batch ID and flowcell ID. + + Args: + session : + Database session + batch_id : + The batch ID from DNA pipelines + flowcell_id : + The ID of the flowcell used in the run + + Returns: + ------- + npgtracking.db.schema.Run or None + """ result = session.execute( select(Run) .where(Run.batch_id == batch_id) From 5c862e753753841b8362e9a7ab8407df69611b0a Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Tue, 16 Dec 2025 17:07:12 +0000 Subject: [PATCH 5/5] Allow fetching by id_run as well --- CHANGELOG | 2 +- src/npgtracking/db/retrieval.py | 34 +++++++++++++++++++++++---------- tests/test_schema.py | 23 +++++++++++++--------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b9e6c3f..dbfe136 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -* get_run_by_batch_and_flowcell function added to retrieval module +* get_run_by_id function added to retrieval module ### Fixed diff --git a/src/npgtracking/db/retrieval.py b/src/npgtracking/db/retrieval.py index 5d5f7ad..e424122 100644 --- a/src/npgtracking/db/retrieval.py +++ b/src/npgtracking/db/retrieval.py @@ -69,27 +69,41 @@ def get_runs_by_currentstatus( return query.all() -def get_run_by_batch_and_flowcell( - session: Session, batch_id: str, flowcell_id: str +def get_run_by_id( + session: Session, + batch_id: str | None = None, + flowcell_id: str | None = None, + id_run: int | None = None, ) -> Run | None: """ - Get a Run by its batch ID and flowcell ID. + Get a Run by its IDs. Either id_run alone, or batch_id and flowcell_id together Args: session : Database session + --- batch_id : - The batch ID from DNA pipelines + The batch ID from DNA pipelines - must be combined with flowcell_id below flowcell_id : - The ID of the flowcell used in the run + The ID of the flowcell used in the run - must be combined with batch_id + --- + id_run : + NPG Tracking run ID - sufficient on its own Returns: ------- npgtracking.db.schema.Run or None """ - result = session.execute( - select(Run) - .where(Run.batch_id == batch_id) - .where(Run.flowcell_id == flowcell_id) - ).scalar_one_or_none() + + statement = select(Run) + + if id_run: + statement = statement.where(Run.id_run == id_run) + elif batch_id and flowcell_id: + statement = statement.where(Run.batch_id == batch_id).where( + Run.flowcell_id == flowcell_id + ) + else: + raise ValueError("Can't get one run without an argument") + result = session.execute(statement).scalar_one_or_none() return result diff --git a/tests/test_schema.py b/tests/test_schema.py index cacfc42..3ed450e 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -16,9 +16,10 @@ # along with this program. If not, see . from pytest import mark as m +from pytest import raises from npgtracking.db.retrieval import ( - get_run_by_batch_and_flowcell, + get_run_by_id, get_runs_by_currentstatus, ) @@ -83,17 +84,21 @@ def test_schema_multiple_runs(self, tracking_session): @m.context("When getting a run by IDs") @m.it("Gives us one or no runs") - def test_run_by_flowcell_batch(self, tracking_session): - assert ( - get_run_by_batch_and_flowcell( - session=tracking_session, batch_id=None, flowcell_id=None - ) - is None - ) + def test_run_by_id(self, tracking_session): + with raises(ValueError, match="Can't get one run without an argument"): + get_run_by_id(session=tracking_session, batch_id=None, flowcell_id=None) - run = get_run_by_batch_and_flowcell( + run = get_run_by_id( session=tracking_session, batch_id="O44 batch 24798 pool 1", flowcell_id="430591", ) + assert run + assert run.id_run == 51533 + + run = get_run_by_id(session=tracking_session, id_run=51533) + assert run assert run.id_run == 51533 + + run = get_run_by_id(session=tracking_session, id_run=12345) + assert run is None