diff --git a/CHANGELOG b/CHANGELOG
index ec5dc4b..dbfe136 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_id function added to retrieval module
+
+### Fixed
+
+* run.batch_id is now a varchar
+
## [0.1.1] - 2025-12-11
### Fixed
diff --git a/src/npgtracking/db/retrieval.py b/src/npgtracking/db/retrieval.py
index 71a4c80..e424122 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,43 @@ def get_runs_by_currentstatus(
Manufacturer.name == manufacturer_name,
)
return query.all()
+
+
+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 IDs. Either id_run alone, or batch_id and flowcell_id together
+
+ Args:
+ session :
+ Database session
+ ---
+ batch_id :
+ The batch ID from DNA pipelines - must be combined with flowcell_id below
+ flowcell_id :
+ 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
+ """
+
+ 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/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))
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..3ed450e 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -16,8 +16,12 @@
# along with this program. If not, see .
from pytest import mark as m
+from pytest import raises
-from npgtracking.db.retrieval import get_runs_by_currentstatus
+from npgtracking.db.retrieval import (
+ get_run_by_id,
+ get_runs_by_currentstatus,
+)
@m.describe("SchemaModel")
@@ -28,7 +32,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 +45,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 +69,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 +81,24 @@ 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_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_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