Skip to content

Commit e335957

Browse files
authored
Merge pull request #1147 from BioGeek/fix-datapoint-id-collision
Give generated datapoint IDs sub-second resolution
2 parents 55bb970 + 6784609 commit e335957

4 files changed

Lines changed: 65 additions & 6 deletions

File tree

proteobench/datapoint/denovo_datapoint.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,12 @@ def generate_id(self) -> None:
197197
"""
198198
Generate a unique ID for the benchmark run by combining the software name and a timestamp.
199199
200-
This ID is used to uniquely identify each run of the benchmark.
200+
This ID is used to uniquely identify each run of the benchmark. The timestamp carries
201+
microseconds because datapoints are also generated in batches -- the resubmission
202+
script regenerates many in a loop -- where whole-second resolution hands several runs
203+
the same ID.
201204
"""
202-
time_stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
205+
time_stamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
203206
self.id = "_".join([self.software_name, str(time_stamp)])
204207
logging.info(f"Assigned the following ID to this run: {self.id}")
205208

proteobench/datapoint/entrapment_datapoint.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,12 @@ def generate_id(self) -> None:
8989
"""
9090
Generate a unique ID for the benchmark run by combining the software name and a timestamp.
9191
92-
This ID is used to uniquely identify each run of the benchmark.
92+
This ID is used to uniquely identify each run of the benchmark. The timestamp carries
93+
microseconds because datapoints are also generated in batches -- the resubmission
94+
script regenerates many in a loop -- where whole-second resolution hands several runs
95+
the same ID.
9396
"""
94-
time_stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
97+
time_stamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
9598
self.id = "_".join([self.software_name, str(time_stamp)])
9699
logging.info(f"Assigned the following ID to this run: {self.id}")
97100

proteobench/datapoint/quant_datapoint.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,12 @@ def generate_id(self) -> None:
327327
"""
328328
Generate a unique ID for the benchmark run by combining the software name and a timestamp.
329329
330-
This ID is used to uniquely identify each run of the benchmark.
330+
This ID is used to uniquely identify each run of the benchmark. The timestamp carries
331+
microseconds because datapoints are also generated in batches -- the resubmission
332+
script regenerates many in a loop -- where whole-second resolution hands several runs
333+
the same ID.
331334
"""
332-
time_stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
335+
time_stamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
333336
self.id = "_".join([self.software_name, str(time_stamp)])
334337
logging.info(f"Assigned the following ID to this run: {self.id}")
335338

test/test_datapoint_id.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Tests that generated datapoint IDs stay unique when datapoints are created in a batch."""
2+
3+
import re
4+
5+
import pytest
6+
7+
from proteobench.datapoint.denovo_datapoint import DenovoDatapoint
8+
from proteobench.datapoint.entrapment_datapoint import EntrapmentDatapoint
9+
from proteobench.datapoint.quant_datapoint import QuantDatapointHYE
10+
11+
DATAPOINT_CLASSES = [QuantDatapointHYE, DenovoDatapoint, EntrapmentDatapoint]
12+
13+
# software name, then date_time_microseconds
14+
ID_PATTERN = re.compile(r"^(?P<software>.+)_\d{8}_\d{6}_\d{6}$")
15+
16+
17+
@pytest.mark.parametrize("datapoint_class", DATAPOINT_CLASSES)
18+
def test_generated_id_starts_with_the_software_name(datapoint_class):
19+
datapoint = datapoint_class(software_name="TestTool")
20+
datapoint.generate_id()
21+
assert datapoint.id.startswith("TestTool_")
22+
23+
24+
@pytest.mark.parametrize("datapoint_class", DATAPOINT_CLASSES)
25+
def test_generated_id_carries_sub_second_resolution(datapoint_class):
26+
datapoint = datapoint_class(software_name="TestTool")
27+
datapoint.generate_id()
28+
match = ID_PATTERN.match(datapoint.id)
29+
assert match is not None, f"unexpected ID format: {datapoint.id}"
30+
assert match.group("software") == "TestTool"
31+
32+
33+
@pytest.mark.parametrize("datapoint_class", DATAPOINT_CLASSES)
34+
def test_ids_generated_in_a_tight_loop_are_unique(datapoint_class):
35+
"""A batch (e.g. the resubmission script) must not hand several runs the same ID."""
36+
ids = []
37+
for _ in range(50):
38+
datapoint = datapoint_class(software_name="TestTool")
39+
datapoint.generate_id()
40+
ids.append(datapoint.id)
41+
assert len(set(ids)) == len(ids)
42+
43+
44+
def test_ids_stay_distinct_across_datapoint_types():
45+
ids = []
46+
for datapoint_class in DATAPOINT_CLASSES:
47+
datapoint = datapoint_class(software_name="TestTool")
48+
datapoint.generate_id()
49+
ids.append(datapoint.id)
50+
assert len(set(ids)) == len(ids)

0 commit comments

Comments
 (0)