Copyright © 2025-2026, Empa.
Functions for getting and setting data in the database.
Note that the database does not contain the time-series data and analysed results.
These data are stored in the file system, use the data_parse module to access.
add_data_to_db(sample_id, file_stem, start_uts, end_uts, job_id=None)
Register a time-series data file in the database.
Warning: does not actually move a file. This function just updates the jobs and dataframes tables.
Parameters:
| Name |
Type |
Description |
Default |
sample_id
|
str
|
Sample ID that the data is associated with
|
required
|
file_stem
|
str
|
Filename of the file uploaded without snapshot. or extension
|
required
|
start_uts
|
float
|
Data start unix time stamp
|
required
|
end_uts
|
float
|
|
required
|
job_id
|
str | None
|
Job ID that the data is associated with
|
None
|
Returns:
| Name | Type |
Description |
str |
str
|
|
Source code in aurora_cycler_manager/database_funcs.py
| def add_data_to_db(sample_id: str, file_stem: str, start_uts: float, end_uts: float, job_id: str | None = None) -> str:
"""Register a time-series data file in the database.
Warning: does not actually move a file. This function just updates the jobs and dataframes tables.
Args:
sample_id: Sample ID that the data is associated with
file_stem: Filename of the file uploaded without snapshot. or extension
start_uts: Data start unix time stamp
end_uts: Data end unix time stamp
job_id: Job ID that the data is associated with
Returns:
str: Job ID
"""
data_start = datetime.fromtimestamp(start_uts, tz=timezone.utc).isoformat()
data_end = datetime.fromtimestamp(end_uts, tz=timezone.utc).isoformat()
if job_id:
return _add_data_to_db_with_job(sample_id, file_stem, data_start, data_end, job_id)
return _add_data_to_db_without_job(sample_id, file_stem, data_start, data_end)
|
add_or_update_job(job_id, row)
Add or update job in database.
Source code in aurora_cycler_manager/database_funcs.py
| def add_or_update_job(job_id: str, row: dict[str, str | float | None]) -> None:
"""Add or update job in database."""
with engine.begin() as conn:
conn.execute(
insert(jobs_table)
.values(stamp_sync({"Job ID": job_id, **row}, op="insert"))
.on_conflict_do_update(
index_elements=["Job ID"],
set_=stamp_sync(row, op="update"),
)
)
|
add_or_update_pipeline(pipeline, row)
Add or update pipeline in database. Remove job if ready == True.
Source code in aurora_cycler_manager/database_funcs.py
| def add_or_update_pipeline(pipeline: str, row: dict[str, str | float | None]) -> None:
"""Add or update pipeline in database. Remove job if ready == True."""
# If ready is one, job gets removed
if row.get("Ready") == 1:
row["Job ID"] = None
row["Job ID on server"] = None
# If there is no Job ID, but there is a Job ID on the server, try to match it and add
elif (
isinstance(job_id_on_server := row.get("Job ID on server"), str)
and isinstance(job_id := row.get("Server label"), str)
and not row.get("Job ID")
):
with suppress(ValueError):
row["Job ID"] = get_job_id_from_server(job_id, job_id_on_server)
# Insert or update the row
uts = time()
with engine.begin() as conn:
conn.execute(
insert(pipelines_table)
.values(Pipeline=pipeline, **stamp_sync(row, uts=uts, op="insert"))
.on_conflict_do_update(
index_elements=["Pipeline"],
set_=stamp_sync(row, uts=uts, op="update"),
)
)
|
add_protocol_to_job(job_id, protocol, capacity=None)
Attach a protocol to a job in the database.
Source code in aurora_cycler_manager/database_funcs.py
| def add_protocol_to_job(job_id: str, protocol: dict | str, capacity: float | None = None) -> None:
"""Attach a protocol to a job in the database."""
if isinstance(protocol, dict):
protocol = json.dumps(protocol)
with engine.begin() as conn:
conn.execute(
update(jobs_table)
.values(
stamp_sync(
{
"Unicycler protocol": protocol,
"Capacity (mAh)": capacity,
}
)
)
.where(jobs_table.c["Job ID"] == job_id)
)
|
add_samples_from_file(json_file, overwrite=False)
Add samples to database from a JSON file.
Source code in aurora_cycler_manager/database_funcs.py
| def add_samples_from_file(json_file: str | Path, overwrite: bool = False) -> None:
"""Add samples to database from a JSON file."""
json_file = Path(json_file)
_pre_check_sample_file(json_file)
df = pd.read_json(json_file, orient="records")
sample_df_to_db(df, overwrite)
|
add_samples_from_object(samples, overwrite=False)
Add a samples to database from a list of dicts.
Source code in aurora_cycler_manager/database_funcs.py
| def add_samples_from_object(samples: list[dict], overwrite: bool = False) -> None:
"""Add a samples to database from a list of dicts."""
df = pd.DataFrame(samples)
sample_df_to_db(df, overwrite)
|
bulk_add_or_update_pipeline(rows)
Add multiple rows to pipelines. Remove job if ready == True.
Source code in aurora_cycler_manager/database_funcs.py
| def bulk_add_or_update_pipeline(rows: list[dict[str, str | float | None]]) -> None:
"""Add multiple rows to pipelines. Remove job if ready == True."""
processed_rows = [
{
**row,
**({"Job ID": None, "Job ID on server": None} if row.get("Ready") else {}),
}
for row in rows
]
uts = time()
with engine.begin() as conn:
for row in processed_rows:
conn.execute(
insert(pipelines_table)
.values(stamp_sync(row, uts=uts, op="insert"))
.on_conflict_do_update(
index_elements=["Pipeline"],
set_=stamp_sync(row, uts=uts),
)
)
|
check_job_running(job_id)
Check if a job is currently on a pipeline.
Source code in aurora_cycler_manager/database_funcs.py
| def check_job_running(job_id: str) -> bool:
"""Check if a job is currently on a pipeline."""
with engine.connect() as conn:
result = conn.execute(
select(pipelines_table.c["Pipeline"]).where(pipelines_table.c["Job ID"] == job_id).limit(1)
)
return result.fetchone() is not None
|
delete_samples(sample_ids)
Remove sample(s) from the database.
Parameters:
| Name |
Type |
Description |
Default |
sample_ids
|
str | list
|
str or list
The sample ID or list of sample IDs to remove from the database
|
required
|
Source code in aurora_cycler_manager/database_funcs.py
| def delete_samples(sample_ids: str | list) -> None:
"""Remove sample(s) from the database.
Args:
sample_ids: str or list
The sample ID or list of sample IDs to remove from the database
"""
if not isinstance(sample_ids, list):
sample_ids = [sample_ids]
with engine.begin() as conn:
conn.execute(
update(samples_table)
.where(samples_table.c["Sample ID"].in_(sample_ids))
.values(stamp_sync({}, op="delete"))
)
|
fill_pipelines_missing_job_ids()
Try to fill missing Job ID in pipelines if only Job ID on server is present.
Source code in aurora_cycler_manager/database_funcs.py
| def fill_pipelines_missing_job_ids() -> None:
"""Try to fill missing Job ID in pipelines if only Job ID on server is present."""
job_id_subquery = (
select(jobs_table.c["Job ID"])
.where(jobs_table.c["Job ID on server"] == pipelines_table.c["Job ID on server"])
.where(jobs_table.c["Server label"] == pipelines_table.c["Server label"])
.scalar_subquery()
)
with engine.begin() as conn:
conn.execute(
update(pipelines_table)
.where(pipelines_table.c["Job ID"].is_(None))
.where(pipelines_table.c["Job ID on server"].isnot(None))
.values(stamp_sync({"Job ID": job_id_subquery}, op="update"))
)
|
find_new_data(mode)
Find jobs that have new data.
Source code in aurora_cycler_manager/database_funcs.py
| def find_new_data(mode: str) -> list[str]:
"""Find jobs that have new data."""
with engine.connect() as conn:
if mode == "new_data":
rows = conn.execute(
select(
results_table.c["Sample ID"],
results_table.c["Last snapshot"],
results_table.c["Last analysis"],
)
).fetchall()
return [
r[0] for r in rows if r[0] and (not r[1] or not r[2] or parse_datetime(r[1]) > parse_datetime(r[2]))
]
if mode == "if_not_exists":
rows = conn.execute(
select(results_table.c["Sample ID"]).where(results_table.c["Last analysis"].is_(None))
).fetchall()
return [r[0] for r in rows]
return []
|
get_all_run_ids()
Get all valid run IDs.
Source code in aurora_cycler_manager/database_funcs.py
| def get_all_run_ids() -> set[str]:
"""Get all valid run IDs."""
with engine.connect() as conn:
result = conn.execute(select(samples_table.c["Run ID"]).distinct()).fetchall()
return {row[0] for row in result}
|
get_all_sampleids()
Get a list of all sample IDs in the database.
Source code in aurora_cycler_manager/database_funcs.py
| def get_all_sampleids() -> list[str]:
"""Get a list of all sample IDs in the database."""
with engine.connect() as conn:
result = conn.execute(select(samples_table.c["Sample ID"]).where(samples_table.c["sync_op"] != "delete"))
return [row[0] for row in result.fetchall()]
|
get_batch_details()
Get all batch names, descriptions and samples from the database.
Source code in aurora_cycler_manager/database_funcs.py
| def get_batch_details() -> dict[str, dict]:
"""Get all batch names, descriptions and samples from the database."""
with engine.connect() as conn:
result = conn.execute(
select(batches_table.c.label, batches_table.c.description, batch_samples_table.c.sample_id)
.join(batches_table, batch_samples_table.c.batch_id == batches_table.c.id)
.order_by(batches_table.c.label)
)
batches: dict[str, dict] = {}
for batch, description, sample in result.fetchall():
if batch not in batches:
batches[batch] = {"description": description, "samples": []}
batches[batch]["samples"].append(sample)
return dict(sorted(batches.items()))
|
get_batches_from_sample(sample_id)
Get the batch names that a sample belongs to.
Source code in aurora_cycler_manager/database_funcs.py
| def get_batches_from_sample(sample_id: str) -> list[str]:
"""Get the batch names that a sample belongs to."""
with engine.connect() as conn:
result = conn.execute(
select(batches_table.c.label)
.where(batch_samples_table.c.sample_id == sample_id)
.join(batches_table, batch_samples_table.c.batch_id == batches_table.c.id)
)
return [r[0] for r in result.fetchall()]
|
get_column_def(table, column_names)
Get AG grid definitions from a table and columns.
Source code in aurora_cycler_manager/database_funcs.py
| def get_column_def(table: Table, column_names: list[str]) -> list[dict]:
"""Get AG grid definitions from a table and columns."""
def map_type(col_type: Any) -> tuple[str, str]: # noqa: ANN401
"""Get AG grid type and filter from sqlalchemy column type."""
if isinstance(col_type, (Integer, Float, Numeric)):
return "number", "agNumberColumnFilter"
if isinstance(col_type, Boolean):
return "boolean", "agTextColumnFilter"
if isinstance(col_type, DateTime):
return "date", "agDateColumnFilter"
return "text", "agTextColumnFilter"
columns = [table.columns[c] for c in column_names]
col_types = [map_type(c.type) for c in columns]
return [
{
"field": col.name,
"cellDataType": col_type[0],
"tooltipField": col.name,
"filter": col_type[1],
# Custom sorting for pipelines
**(
{"comparator": {"function": "pipelineComparatorCustom"}, "sort": "asc"}
if col.name == "Pipeline"
else {}
),
}
for col, col_type in zip(columns, col_types, strict=True)
]
|
get_database(columns=None)
Get all data from the database.
Formatted for passing to Dash AG Grid rowData.
Source code in aurora_cycler_manager/database_funcs.py
| def get_database(columns: dict[str, list] | None = None) -> dict[str, Any]:
"""Get all data from the database.
Formatted for passing to Dash AG Grid rowData.
"""
if columns is None:
columns = {
"samples": list(samples_table.columns.keys()),
"pipelines": list(pipelines_table.columns.keys()),
"jobs": list(jobs_table.columns.keys()),
"results": list(results_table.columns.keys()),
}
with engine.connect() as conn:
results = {
"samples": conn.execute(
select(*[samples_table.c[col] for col in columns["samples"]])
.where(samples_table.c["sync_op"] != "delete")
.order_by(samples_table.c["Sample ID"])
),
"pipelines": conn.execute( # Uses custom sort
select(*[pipelines_table.c[col] for col in columns["pipelines"]]).where(
pipelines_table.c["sync_op"] != "delete"
)
),
"jobs": conn.execute(
select(*[jobs_table.c[col] for col in columns["jobs"]])
.where(jobs_table.c["sync_op"] != "delete")
.order_by(jobs_table.c["Sample ID"])
),
"results": conn.execute(
select(*[results_table.c[col] for col in columns["results"]])
.where(results_table.c["sync_op"] != "delete")
.order_by(results_table.c["Sample ID"])
),
}
return {k: {"add": [dict(m) for m in v.mappings().all()]} for k, v in results.items()}
|
get_database_updates(last_sync=0, columns=None)
Get all new data from the database.
Formatted for viewing in Dash AG Grid.
Source code in aurora_cycler_manager/database_funcs.py
| def get_database_updates(last_sync: float = 0, columns: dict[str, list] | None = None) -> dict[str, Any]:
"""Get all new data from the database.
Formatted for viewing in Dash AG Grid.
"""
if columns is None:
columns = {
"samples": list(samples_table.columns.keys()),
"pipelines": list(pipelines_table.columns.keys()),
"jobs": list(jobs_table.columns.keys()),
"results": list(results_table.columns.keys()),
}
else:
# Must collect these columns for basic functionality, even if they are not displayed
pipelines_required = {"Pipeline", "Sample ID", "Job ID", "Server label"}
columns["pipelines"] = list(set(columns["pipelines"]) | pipelines_required)
if "Server label" not in columns["jobs"]:
columns["jobs"].append("Server label")
with engine.connect() as conn:
results = {
"samples": conn.execute(
select(*[samples_table.c[col] for col in columns["samples"]])
.where(samples_table.c["sync_modified"] > last_sync)
.where(samples_table.c["sync_op"] != "delete")
.order_by(samples_table.c["Sample ID"])
),
"pipelines": conn.execute(
select(*[pipelines_table.c[col] for col in columns["pipelines"]])
.where(pipelines_table.c["sync_modified"] > last_sync)
.where(pipelines_table.c["sync_op"] != "delete")
),
"jobs": conn.execute(
select(*[jobs_table.c[col] for col in columns["jobs"]])
.where(jobs_table.c["sync_modified"] > last_sync)
.where(jobs_table.c["sync_op"] != "delete")
.order_by(jobs_table.c["Sample ID"])
),
"results": conn.execute(
select(*[results_table.c[col] for col in columns["results"]])
.where(results_table.c["sync_modified"] > last_sync)
.where(results_table.c["sync_op"] != "delete")
.order_by(results_table.c["Sample ID"])
),
"del_samples": conn.execute(
select(samples_table.c["Sample ID"])
.where(samples_table.c["sync_modified"] > last_sync)
.where(samples_table.c["sync_op"] == "delete")
),
"del_pipelines": conn.execute(
select(pipelines_table.c["Pipeline"])
.where(pipelines_table.c["sync_modified"] > last_sync)
.where(pipelines_table.c["sync_op"] == "delete")
),
"del_jobs": conn.execute(
select(jobs_table.c["Job ID"])
.where(jobs_table.c["sync_modified"] > last_sync)
.where(jobs_table.c["sync_op"] == "delete")
),
"del_results": conn.execute(
select(results_table.c["Sample ID"])
.where(results_table.c["sync_modified"] > last_sync)
.where(results_table.c["sync_op"] == "delete")
),
}
return {
table: {
"upsert": [dict(m) for m in results[table].mappings().all()],
"remove": [dict(m) for m in results[f"del_{table}"].mappings().all()],
}
for table in ["samples", "pipelines", "jobs", "results"]
}
|
get_db_last_update()
Get the last update time of the database.
Returns 0.0 if never updated.
Source code in aurora_cycler_manager/database_funcs.py
| def get_db_last_update() -> float:
"""Get the last update time of the database.
Returns 0.0 if never updated.
"""
with engine.connect() as conn:
return conn.execute(select(func.max(pipelines_table.c["sync_modified"]))).scalar() or 0.0
|
get_job_data(job_id)
Get all data about a job from the database.
Source code in aurora_cycler_manager/database_funcs.py
| def get_job_data(job_id: str) -> dict:
"""Get all data about a job from the database."""
with engine.connect() as conn:
result = conn.execute(select(*job_cols).where(jobs_table.c["Job ID"] == job_id)).mappings().fetchone()
if not result:
msg = f"Job ID '{job_id}' not found in the database"
raise ValueError(msg)
job_data = dict(result)
# Convert json strings to python objects
payload = job_data.get("Payload")
if payload and isinstance(payload, str) and payload.startswith(("[", "{")):
job_data["Payload"] = json.loads(payload)
unicycler = job_data.get("Unicycler protocol")
if unicycler and isinstance(unicycler, str) and unicycler.startswith("{"):
job_data["Unicycler protocol"] = json.loads(unicycler)
return job_data
|
get_job_from_pipeline(pipeline)
Get Job ID from a pipeline.
Source code in aurora_cycler_manager/database_funcs.py
| def get_job_from_pipeline(pipeline: str) -> str | None:
"""Get Job ID from a pipeline."""
with engine.connect() as conn:
return conn.execute(
select(pipelines_table.c["Job ID"]).where(pipelines_table.c["Pipeline"] == pipeline)
).scalar()
|
get_job_id_from_server(server_label, job_id_on_server)
Get the job ID from server label and job ID on server.
Source code in aurora_cycler_manager/database_funcs.py
| def get_job_id_from_server(server_label: str, job_id_on_server: str) -> str:
"""Get the job ID from server label and job ID on server."""
with engine.connect() as conn:
result = conn.execute(
select(jobs_table.c["Job ID"])
.where(jobs_table.c["Job ID on server"] == job_id_on_server)
.where(jobs_table.c["Server label"] == server_label)
).fetchone()
if result:
return result[0]
msg = f"No Job ID found for server {server_label}: {job_id_on_server}"
raise ValueError(msg)
|
get_jobs_from_sample(sample_id)
List all Job IDs associated with a sample.
Source code in aurora_cycler_manager/database_funcs.py
| def get_jobs_from_sample(sample_id: str) -> list[str]:
"""List all Job IDs associated with a sample."""
with engine.connect() as conn:
result = conn.execute(select(jobs_table.c["Job ID"]).where(jobs_table.c["Sample ID"] == sample_id)).fetchall()
return [r[0] for r in result]
|
get_last_harvest(server, folder)
Get unix time stamp of last harvest.
Source code in aurora_cycler_manager/database_funcs.py
| def get_last_harvest(server: dict, folder: str) -> float:
"""Get unix time stamp of last harvest."""
with engine.connect() as conn:
result = conn.execute(
select(harvester_table.c["Last snapshot"])
.where(harvester_table.c["Server label"] == server["label"])
.where(harvester_table.c["Server hostname"] == server["hostname"])
.where(harvester_table.c["Folder"] == folder)
).fetchone()
if result:
return parse_datetime(result[0]).timestamp()
return 0.0
|
get_neware_pipelines()
Get only running Neware pipelines.
Source code in aurora_cycler_manager/database_funcs.py
| def get_neware_pipelines() -> tuple[list[str], list[str]]:
"""Get only running Neware pipelines."""
with engine.connect() as conn:
rows = conn.execute(
select(pipelines_table.c["Pipeline"], pipelines_table.c["Server label"])
.where(pipelines_table.c["Sample ID"].isnot(None))
.where(pipelines_table.c["Ready"].is_(False))
.where(pipelines_table.c["Server type"] == "neware")
).all()
pipelines = [row[0] for row in rows]
server_labels = [row[1] for row in rows]
return pipelines, server_labels
|
get_one_batch(batch_name)
Get details of a batch from the batch name.
Source code in aurora_cycler_manager/database_funcs.py
| def get_one_batch(batch_name: str) -> dict:
"""Get details of a batch from the batch name."""
with engine.connect() as conn:
result = conn.execute(
select(batches_table.c.id, batches_table.c.description).where(batches_table.c.label == batch_name)
)
res = result.fetchone()
if not res:
msg = "Batch not in database"
raise ValueError(msg)
batch_id, description = res
result = conn.execute(
select(batch_samples_table.c.sample_id)
.where(batch_samples_table.c.batch_id == batch_id)
.order_by(batch_samples_table.c.sample_id)
)
samples = [s[0] for s in result.fetchall()]
return {"name": batch_name, "description": description, "samples": samples}
|
get_or_create_job_id_from_server(server_label, job_id_on_server)
Get the job ID from server label and job ID on server, create new Job ID if it doesn't exist.
Source code in aurora_cycler_manager/database_funcs.py
| def get_or_create_job_id_from_server(server_label: str, job_id_on_server: str) -> str:
"""Get the job ID from server label and job ID on server, create new Job ID if it doesn't exist."""
try:
job_id = get_job_id_from_server(server_label, job_id_on_server)
except ValueError:
job_id = str(uuid.uuid4())
with engine.begin() as conn:
conn.execute(
insert(jobs_table).values(
stamp_sync(
{
"Job ID": job_id,
"Job ID on server": job_id_on_server,
"Server label": server_label,
},
op="insert",
)
)
)
return job_id
|
get_pipeline(pipeline)
Get pipeline details.
Source code in aurora_cycler_manager/database_funcs.py
| def get_pipeline(pipeline: str) -> dict | None:
"""Get pipeline details."""
with engine.connect() as conn:
result = (
conn.execute(select(*pipeline_cols).where(pipelines_table.c["Pipeline"] == pipeline)).mappings().first()
)
return dict(result) if result else None
|
get_pipeline_from_sample(sample_id)
Get pipeline from a Sample ID.
Source code in aurora_cycler_manager/database_funcs.py
| def get_pipeline_from_sample(sample_id: str) -> dict | None:
"""Get pipeline from a Sample ID."""
with engine.connect() as conn:
result = (
conn.execute(select(*pipeline_cols).where(pipelines_table.c["Sample ID"] == sample_id)).mappings().first()
)
return dict(result) if result else None
|
get_results_from_sample(sample_id)
Get results summary from Sample ID.
Source code in aurora_cycler_manager/database_funcs.py
| def get_results_from_sample(sample_id: str) -> dict | None:
"""Get results summary from Sample ID."""
with engine.connect() as conn:
result = conn.execute(select(*result_cols).where(results_table.c["Sample ID"] == sample_id)).mappings().first()
return dict(result) if result else None
|
get_running_job(sample_id)
Get pipeline, job ID, and status of a job if a sample is running.
Source code in aurora_cycler_manager/database_funcs.py
| def get_running_job(sample_id: str) -> dict[str, str | None]:
"""Get pipeline, job ID, and status of a job if a sample is running."""
with engine.connect() as conn:
result = (
conn.execute(
select(
pipelines_table.c["Pipeline"],
pipelines_table.c["Job ID"],
jobs_table.c["Status"],
)
.outerjoin(jobs_table, pipelines_table.c["Job ID"] == jobs_table.c["Job ID"])
.where(pipelines_table.c["Sample ID"] == sample_id)
)
.mappings()
.fetchone()
)
if result:
return dict(result)
return {"Pipeline": None, "Job ID": None, "Status": None}
|
get_sample_data(sample_id)
Get all data about a sample from the database.
Source code in aurora_cycler_manager/database_funcs.py
| def get_sample_data(sample_id: str) -> dict:
"""Get all data about a sample from the database."""
with engine.connect() as conn:
result = (
conn.execute(
select(*sample_cols)
.where(samples_table.c["Sample ID"] == sample_id)
.where(samples_table.c["sync_op"] != "delete")
)
.mappings()
.fetchone()
)
if not result:
msg = f"Sample ID '{sample_id}' not found in the database"
raise ValueError(msg)
sample_data = dict(result)
# Convert json strings to python objects
history = sample_data.get("Assembly history")
if history and isinstance(history, str):
sample_data["Assembly history"] = json.loads(history)
return sample_data
|
get_sample_from_pipeline(pipeline)
Get Sample ID from a pipeline.
Source code in aurora_cycler_manager/database_funcs.py
| def get_sample_from_pipeline(pipeline: str) -> str | None:
"""Get Sample ID from a pipeline."""
with engine.connect() as conn:
return conn.execute(
select(pipelines_table.c["Sample ID"]).where(pipelines_table.c["Pipeline"] == pipeline)
).scalar()
|
get_unicycler_protocols(sample_id)
Return a list of unicycler protocols associated with the sample.
Source code in aurora_cycler_manager/database_funcs.py
| def get_unicycler_protocols(sample_id: str) -> list[dict]:
"""Return a list of unicycler protocols associated with the sample."""
with engine.connect() as conn:
j = jobs_table.c
d = dataframes_table.c
sort_timestamp = func.coalesce(
j["Submitted"],
select(func.min(d["Data start"])).where(d["Job ID"] == j["Job ID"]).scalar_subquery(),
literal(datetime(9999, 12, 31), type_=j["Submitted"].type), # noqa: DTZ001
).label("sort_timestamp")
result = conn.execute(
select(j["Job ID"], j["Unicycler protocol"], j["Capacity (mAh)"], sort_timestamp)
.where(j["Sample ID"] == sample_id)
.where(j["Unicycler protocol"].isnot(None))
.order_by(sort_timestamp)
)
return [dict(row) for row in result.mappings().all()]
|
is_sample(sample_id)
Check if sample_id exists in the database.
Source code in aurora_cycler_manager/database_funcs.py
| def is_sample(sample_id: str) -> bool:
"""Check if `sample_id` exists in the database."""
with engine.connect() as conn:
return bool(conn.execute(select(exists().where(samples_table.c["Sample ID"] == sample_id))).scalar())
|
patch_database(engine)
Add missing columns to database, in case users are coming from an older version.
Source code in aurora_cycler_manager/database_funcs.py
| def patch_database(engine: Engine) -> None:
"""Add missing columns to database, in case users are coming from an older version."""
if _db_schema_needs_update(engine):
try:
_update_db_schema(engine)
except ProgrammingError as e:
msg = (
"Database schema needs updating. "
"Failed to update. An admin must run 'aurora-app' or 'aurora-setup update' first."
)
raise PermissionError(msg) from e
|
remove_batch(batch_name)
Remove a batch from the database.
Source code in aurora_cycler_manager/database_funcs.py
| def remove_batch(batch_name: str) -> None:
"""Remove a batch from the database."""
with engine.begin() as conn:
batch_id = conn.execute(select(batches_table.c.id).where(batches_table.c.label == batch_name)).fetchone()[0]
conn.execute(delete(batches_table).where(batches_table.c.label == batch_name))
conn.execute(delete(batch_samples_table).where(batch_samples_table.c.batch_id == batch_id))
|
sample_df_to_db(df, overwrite=False)
Add samples to database from a pandas dataframe.
Source code in aurora_cycler_manager/database_funcs.py
| def sample_df_to_db(df: pd.DataFrame, overwrite: bool = False) -> None:
"""Add samples to database from a pandas dataframe."""
sample_ids = df["Sample ID"].tolist()
if any(not isinstance(sample_id, str) for sample_id in sample_ids):
msg = "File contains non-string 'Sample ID' keys"
raise TypeError(msg)
for sample_id in sample_ids:
check_illegal_text(sample_id)
if len(sample_ids) != len(set(sample_ids)):
msg = "File contains duplicate 'Sample ID' keys"
raise ValueError(msg)
# Check if any sample already exists
existing_sample_ids = get_all_sampleids()
if not overwrite and any(sample_id in existing_sample_ids for sample_id in sample_ids):
msg = "Sample IDs already exist in the database"
raise ValueError(msg)
# Recalculate some values
df = _recalculate_sample_data(df)
# Insert into database
valid_columns = {c.name for c in samples_table.columns}
df_columns = set(df.columns)
missing_in_db = df_columns - valid_columns
if missing_in_db:
logger.warning(
"Adding samples to database: after automatic calculations, "
"column(s) %s do not exist in the database, skipping",
", ".join("'" + col + "'" for col in missing_in_db),
)
df = df.drop(columns=missing_in_db)
with engine.begin() as conn:
for _, raw_row in df.iterrows():
# Remove empty columns from the row
row = raw_row.dropna()
# Insert or update the row
row_dict = row.to_dict()
conn.execute(
insert(samples_table)
.values(stamp_sync(row_dict, op="insert"))
.on_conflict_do_update(
index_elements=["Sample ID"],
set_=stamp_sync(row_dict),
)
)
|
save_or_overwrite_batch(batch_name, batch_description, sample_ids, overwrite=False)
Save a batch to the database, overwriting it if the name already exists.
Source code in aurora_cycler_manager/database_funcs.py
| def save_or_overwrite_batch(batch_name: str, batch_description: str, sample_ids: list, overwrite: bool = False) -> None:
"""Save a batch to the database, overwriting it if the name already exists."""
with engine.begin() as conn:
result = conn.execute(select(batches_table.c.id).where(batches_table.c.label == batch_name)).fetchone()
if result:
if not overwrite:
msg = f"Batch {batch_name} already exists. Set overwrite=True to overwrite."
raise ValueError(msg)
batch_id = result[0]
conn.execute(
update(batches_table).where(batches_table.c.id == batch_id).values(description=batch_description)
)
conn.execute(delete(batch_samples_table).where(batch_samples_table.c.batch_id == batch_id))
else:
batch_id = conn.execute(
insert(batches_table).values(label=batch_name, description=batch_description)
).inserted_primary_key[0]
conn.execute(
insert(batch_samples_table),
[{"batch_id": batch_id, "sample_id": sample_id} for sample_id in sample_ids],
)
|
stamp_sync(row, uts=None, op='update')
Update modified time and mode.
Source code in aurora_cycler_manager/database_funcs.py
| def stamp_sync(
row: dict,
uts: float | None = None,
op: Literal["insert", "update", "delete"] = "update",
) -> dict:
"""Update modified time and mode."""
if uts is None:
uts = time()
return {**row, "sync_modified": uts, "sync_op": op}
|
update_flags()
Update the flags in the pipelines table from the results table.
Source code in aurora_cycler_manager/database_funcs.py
| def update_flags() -> None:
"""Update the flags in the pipelines table from the results table."""
with engine.begin() as conn:
# Reset all flags
conn.execute(update(pipelines_table).values(Flag=None))
# Get Sample IDs that exist in pipelines
sample_ids = (
conn.execute(
select(pipelines_table.c["Sample ID"]).distinct().where(pipelines_table.c["Sample ID"].isnot(None))
)
.scalars()
.all()
)
if not sample_ids:
return
# Get all results
rows = (
conn.execute(
select(results_table.c["Sample ID"], results_table.c["Flag"]).where(
results_table.c["Sample ID"].in_(sample_ids)
)
)
.mappings()
.all()
)
# Bulk update
if rows:
uts = time()
conn.execute(
update(pipelines_table).where(
pipelines_table.c["Sample ID"] == bindparam("b_Sample ID") # match on Sample ID
),
[stamp_sync({"b_Sample ID": row["Sample ID"], "Flag": row["Flag"]}, uts=uts) for row in rows],
)
|
update_harvester(server, folder, copy_datetime)
Update last copy time in harvester table.
Source code in aurora_cycler_manager/database_funcs.py
| def update_harvester(server: dict, folder: str, copy_datetime: datetime) -> None:
"""Update last copy time in harvester table."""
with engine.begin() as conn:
conn.execute(
insert(harvester_table)
.values(
**{
"Server label": server["label"],
"Server hostname": server["hostname"],
"Folder": folder,
}
)
.on_conflict_do_nothing()
)
conn.execute(
update(harvester_table)
.values(
**{
"Last snapshot": copy_datetime.isoformat(timespec="seconds"),
}
)
.where(harvester_table.c["Server label"] == server["label"])
.where(harvester_table.c["Server hostname"] == server["hostname"])
.where(harvester_table.c["Folder"] == folder)
)
|
update_results(sample_id, row)
Add or update results for a sample.
Source code in aurora_cycler_manager/database_funcs.py
| def update_results(sample_id: str, row: dict[str, str | float | None]) -> None:
"""Add or update results for a sample."""
with engine.begin() as conn:
conn.execute(
insert(results_table)
.values(stamp_sync({"Sample ID": sample_id, **row}, op="insert"))
.on_conflict_do_update(
index_elements=["Sample ID"],
set_=stamp_sync(row, op="update"),
)
)
|
update_sample_label(sample_ids, label)
Update the label of sample(s) in the database.
Parameters:
| Name |
Type |
Description |
Default |
sample_ids
|
str | list[str]
|
str or list
The sample ID or list of sample IDs to remove from the database
|
required
|
label
|
str | None
|
str or None
The label to attach to the sample. Overwrites any existing label.
|
required
|
Source code in aurora_cycler_manager/database_funcs.py
| def update_sample_label(sample_ids: str | list[str], label: str | None) -> None:
"""Update the label of sample(s) in the database.
Args:
sample_ids: str or list
The sample ID or list of sample IDs to remove from the database
label: str or None
The label to attach to the sample. Overwrites any existing label.
"""
if isinstance(sample_ids, str):
sample_ids = [sample_ids]
with engine.begin() as conn:
for sample_id in sample_ids:
conn.execute(
update(samples_table)
.where(samples_table.c["Sample ID"] == sample_id)
.values(stamp_sync({"Label": label}))
)
|