tol-sdk 1.8.4__py3-none-any.whl → 1.8.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tol/api_client/view.py CHANGED
@@ -197,7 +197,7 @@ class DefaultView(View):
197
197
  sub_tree = tree.get_sub_tree(rel)
198
198
  if sub_tree and rel in data_object._to_many_objects:
199
199
  many_obj = data_object._to_many_objects.get(rel)
200
- to_many[rel] = [self.__dump_stub(x, rel) for x in many_obj]
200
+ to_many[rel] = {'data': [self.__dump_stub(x, rel) for x in many_obj]}
201
201
  for obj in many_obj:
202
202
  included.add_dump(self.__dump_object(obj, included, sub_tree))
203
203
  elif quoted_id:
@@ -0,0 +1,35 @@
1
+ # SPDX-FileCopyrightText: 2025 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ import re
6
+ from datetime import time
7
+
8
+ from tol.core import DataObject
9
+
10
+
11
+ class Converter:
12
+ def convert(self, obj):
13
+ raise NotImplementedError()
14
+
15
+
16
+ class TimeStringToTimeConverter(Converter):
17
+ """
18
+ Converts string fields representing time in HH:MM (24-hour) format to Python time objects.
19
+ If the string is not in HH:MM, tries to append ':00' and parse as HH:MM:SS.
20
+ """
21
+ def __init__(self, field: str):
22
+ self.field = field
23
+
24
+ def convert(self, obj: DataObject) -> DataObject:
25
+ value = obj.attributes.get(self.field)
26
+ if isinstance(value, str):
27
+ match = re.match(r'^(\d{1,2}):(\d{2})(?::(\d{2}))?$', value)
28
+ if match:
29
+ h, m = int(match.group(1)), int(match.group(2))
30
+ s = int(match.group(3)) if match.group(3) else 0
31
+ try:
32
+ obj.attributes[self.field] = time(h, m, s)
33
+ except ValueError:
34
+ pass
35
+ return obj
@@ -12,6 +12,11 @@ from tol.sources.ena import ena
12
12
 
13
13
 
14
14
  class EnaChecklistValidator(Validator):
15
+ ENA_TO_INTERNAL_FIELD_MAP = {
16
+ 'geographic location (longitude)': 'DECIMAL_LONGITUDE',
17
+ 'geographic location (latitude)': 'DECIMAL_LATITUDE',
18
+ }
19
+
15
20
  """
16
21
  validates the ENA_CHECKLIST for each samples
17
22
  """
@@ -37,16 +42,18 @@ class EnaChecklistValidator(Validator):
37
42
  field_name = key
38
43
  if 'field' in validation:
39
44
  field_name = validation['field']
45
+ internal_field = self.ENA_TO_INTERNAL_FIELD_MAP.get(field_name, field_name)
46
+
40
47
  if 'mandatory' in validation and key not in obj.attributes:
41
- self.add_error(object_id=obj.id, detail='Must be given', field=[field_name])
48
+ self.add_error(object_id=obj.id, detail='Must be given', field=[internal_field])
42
49
  continue
43
50
  if 'mandatory' in validation and obj.attributes[key] is None:
44
- self.add_error(object_id=obj.id, detail='Must be given', field=[field_name])
51
+ self.add_error(object_id=obj.id, detail='Must be given', field=[internal_field])
45
52
  continue
46
53
  if 'mandatory' in validation and obj.attributes.get(key) == '':
47
54
  self.add_error(
48
55
  object_id=obj.id,
49
- detail='Must not be empty', field=[field_name]
56
+ detail='Must not be empty', field=[internal_field]
50
57
  )
51
58
 
52
59
  if 'restricted text' in validation and key in obj.attributes:
@@ -55,12 +62,25 @@ class EnaChecklistValidator(Validator):
55
62
  regex = condition
56
63
  compiled_re = re.compile(regex)
57
64
  if not compiled_re.search(obj.attributes.get(key)):
58
- self.add_error(
59
- object_id=obj.id,
60
- detail='Must match specific pattern', field=[field_name]
61
- )
65
+ if internal_field in ('DECIMAL_LONGITUDE', 'DECIMAL_LATITUDE'):
66
+ self.add_error(
67
+ object_id=obj.id,
68
+ detail=(
69
+ f'Value for {internal_field} must be a valid decimal in the'
70
+ f' format required by ENA (e.g. 12.3456). '
71
+ f'See ENA checklist for accepted format.',
72
+ ),
73
+ field=[internal_field]
74
+ )
75
+ else:
76
+ self.add_error(
77
+ object_id=obj.id,
78
+ detail=(
79
+ f'Value for {internal_field} must match the required pattern.'
80
+ ),
81
+ field=[internal_field]
82
+ )
62
83
 
63
- # Check against allowed values
64
84
  if 'text choice' in validation and key in obj.attributes:
65
85
  for condition in validation:
66
86
  if isinstance(condition, list):
@@ -69,5 +89,5 @@ class EnaChecklistValidator(Validator):
69
89
  [x.lower() for x in allowed_values]:
70
90
  self.add_error(
71
91
  object_id=obj.id,
72
- detail='Must be in allowed values', field=[field_name]
92
+ detail='Must be in allowed values', field=[internal_field]
73
93
  )
@@ -53,7 +53,7 @@ class SpecimensHaveSameTaxonValidator(Validator):
53
53
  if specimen_id in self.__seen and taxon_id != self.__seen[specimen_id]:
54
54
  self.add_error(
55
55
  object_id=obj.id,
56
- detail='A non-symbiont must have a matching Specimen ID and Taxon ID',
56
+ detail='All samples from the same specimen_ID must have the same TAXON_ID.',
57
57
  field=self.__config.specimen_id_field,
58
58
  )
59
59
  if specimen_id not in self.__seen:
tol/validators/types.py CHANGED
@@ -55,12 +55,19 @@ class TypesValidator(Validator):
55
55
  continue
56
56
  type_class = type_map.get(expected_type)
57
57
  if type_class and not isinstance(actual_value, type_class):
58
- self.__add_result(
59
- obj,
60
- key,
61
- detail=f'Field {key} value "{actual_value}" is not of type '
62
- f'"{expected_type}"',
63
- )
58
+ if expected_type == 'time':
59
+ self.__add_result(
60
+ obj,
61
+ key,
62
+ detail='Only HH:MM format is accepted.',
63
+ )
64
+ else:
65
+ self.__add_result(
66
+ obj,
67
+ key,
68
+ detail=f'Field {key} value "{actual_value}" is not of type '
69
+ f'"{expected_type}"',
70
+ )
64
71
  if type_class and isinstance(actual_value, type_class):
65
72
  # Special case for bool since isinstance(True, int) is True
66
73
  if expected_type == 'int' and isinstance(actual_value, bool):
@@ -26,11 +26,12 @@ class ValueCheckValidator(Validator):
26
26
  self.__config = config
27
27
 
28
28
  def _validate_data_object(self, obj: DataObject) -> None:
29
- # This function is used to check if the data object is SYMBIONT or not
30
-
31
- if obj.attributes.get(self.__config.field) == self.__config.value:
29
+ if obj.attributes.get(self.__config.field) != self.__config.value:
32
30
  self.add_error(
33
31
  object_id=obj.id,
34
- detail=f'{self.__config.value} is detected',
32
+ detail=(
33
+ f"Expected '{self.__config.value}' for field "
34
+ f"'{self.__config.field}', but got '{obj.attributes.get(self.__config.field)}'"
35
+ ),
35
36
  field=self.__config.field,
36
37
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tol-sdk
3
- Version: 1.8.4
3
+ Version: 1.8.5
4
4
  Summary: SDK for interaction with ToL, Sanger and external services
5
5
  Author-email: ToL Platforms Team <tol-platforms@sanger.ac.uk>
6
6
  License: MIT
@@ -39,7 +39,7 @@ tol/api_client/factory.py,sha256=WGHA5wio4XS8OHqG07DLSVjehOeAsVoVCc5phAIq4H8,373
39
39
  tol/api_client/filter.py,sha256=D49RIai5Yj4CiQvIkgaEIXdw_oSU7CD5cn9SdXWRYXU,1474
40
40
  tol/api_client/parser.py,sha256=88Q2RlPwn9JJsSIukC2_yxfaM2AaXjsGuDOESqY7se4,8351
41
41
  tol/api_client/validate.py,sha256=9wFZotTJ4fI21BdO5AuMZok0rDQ4s8zM_WojLdVvA0A,3071
42
- tol/api_client/view.py,sha256=DvhRTA957Fn5vVPByV7e8ngvJaD9DSa5SaJ48ONWQlU,8753
42
+ tol/api_client/view.py,sha256=LGojaC0vCPkjemV8A83b3T_uDVQ002nQYgAZN7JapIU,8763
43
43
  tol/barcodes/__init__.py,sha256=k9KoBO0xJyxg1kanLrN0E3HuVSbbpfSInGEsWX-8UsY,214
44
44
  tol/barcodes/main.py,sha256=QxueF2AdrclIaQuu7W4lb4eF5tU_l-p3UMsDFIYgCfo,6213
45
45
  tol/benchling/__init__.py,sha256=9VIL6PBxvZRaBrN1j2Di75MdEY261kEHs_D-lUutIlg,229
@@ -198,6 +198,7 @@ tol/flows/converters/sts_sample_project_to_elastic_sample_converter.py,sha256=YE
198
198
  tol/flows/converters/sts_sample_to_casm_benchling_converter.py,sha256=Zo577u2v5_Fela2uQVtZsGZmHq4bLecTCC4Ewvq61Xo,39414
199
199
  tol/flows/converters/sts_sampleset_to_elastic_sampleset_converter.py,sha256=PUP0Qjy9wTmqp5GHNEd9fukqtWdoMvDfs4rJisfLzcc,3197
200
200
  tol/flows/converters/sts_species_to_elastic_species_converter.py,sha256=ELZ_ML8vPlLkfXrx0B_wxUWiPyxkI8UXgSPQZCKknXU,1164
201
+ tol/flows/converters/time_string_to_time.py,sha256=myo9K9pRMT6GtT5vXf3c0UDK5n8bMVbvW7ewhSPkecU,1062
201
202
  tol/flows/converters/tolid_specimen_to_elastic_tolid_converter.py,sha256=4Ird6ATYjsjSFZ1AGsxpuWdQ6QtQTvKJ-rF-_TCC_rg,1072
202
203
  tol/flows/converters/tolqc_data_to_elastic_run_data_converter.py,sha256=f9PYnsswikskvXpnSlrfYN7wXfyn8iXQpg996ZsQHbw,3799
203
204
  tol/flows/converters/tolqc_sample_to_elastic_sequencing_request_converter.py,sha256=MzPcO75Z_3-6nsWC8X0kmmmxJTurV_HOZhseC-tWdFo,1397
@@ -331,25 +332,25 @@ tol/validators/assert_on_condition.py,sha256=eBGgSVfIQ6e45SheM-ZDg7daXJjyZxRVS5L
331
332
  tol/validators/branching.py,sha256=7YFjHNjrrTmy4hZ3E7JKDT6MEsBMhrc3P3p3ykv4wKI,5720
332
333
  tol/validators/converter_and_validate.py,sha256=O1uYdrU4YDZ8eZjb7Koots4-8fMVOkJFXESg-LVw2o8,2992
333
334
  tol/validators/date_sorting.py,sha256=NzYsBfhgeG4NYlYjVUYgcGGwEHns5hESqeaPvXUxjUI,1918
334
- tol/validators/ena_checklist.py,sha256=M10VAFGpaxnm7rWO4jmFhTWkYRlCmU0Ox2IUEDFGKbo,2812
335
+ tol/validators/ena_checklist.py,sha256=2IfgzdgpumTPPwzoycIpfFFnkTSRlEjpZ8RQYQpBoXY,3771
335
336
  tol/validators/ena_submittable.py,sha256=CujF9t4mA4N3Wm_5rA5MRp401aW19kbioOZpfWVXg6I,1965
336
337
  tol/validators/min_one_valid_value.py,sha256=gZUHtfRA-Lvpw0d1FJoAA31cRJpLbbxAJCC9DCt5lCY,1442
337
338
  tol/validators/mutually_exclusive.py,sha256=6blZK-2IY4Eq79fHKKrm-pxsQ6B5DNH5ldtxOFVCPhU,4492
338
339
  tol/validators/regex.py,sha256=dLAi_vQt9_DsT6wQZmbYC7X5-Wp15l0leUE6XkPaItg,2602
339
340
  tol/validators/regex_by_value.py,sha256=XM5EnT4vgD17rfpR3bUE9I56IemSw26BI9MZtMakd4E,2582
340
- tol/validators/specimens_have_same_taxon.py,sha256=m2LLRIZMdhPj1fzyioDJOraI6UHXgy1l963xhezgk7E,2177
341
+ tol/validators/specimens_have_same_taxon.py,sha256=BaJcZ38ZprPcuGTIorSxxC9uGN0_lj6HS6B54EObcuY,2183
341
342
  tol/validators/sts_fields.py,sha256=aYbzy15btEg4-ocDT1qrspe7-atoWRrOJ_KmuPU6J14,8936
342
343
  tol/validators/tolid.py,sha256=yODebLYbKtlem3IpVcv8XImvq90r-AK68asH9JEawqo,3897
343
- tol/validators/types.py,sha256=KDBNqx5isJG5XI1l2V9Wmi9135ZwDace3MU6Qij3J6E,2612
344
+ tol/validators/types.py,sha256=jMVpckRp8RS93f7usf58YH_K-5rKWgZIYs7bO9dHhQc,2914
344
345
  tol/validators/unique_value_check.py,sha256=sFvDooYkKeORvULGEOTsgIcxlbe0AXDWxY3Gbr3j0KI,1282
345
346
  tol/validators/unique_values.py,sha256=o5IrfUNLEmlEp8kpInTtFnTq-FqiHSC9TItKdf-LI1o,3114
346
347
  tol/validators/unique_whole_organisms.py,sha256=RdqA1GzIf3LTdrmNGGdxv0aW2udDY2P9EaqZb40hhik,5735
347
- tol/validators/value_check.py,sha256=lxfhfL8BCIs_B838CQ5znJ6KFD7ms_fSVCS9QuVearE,1052
348
+ tol/validators/value_check.py,sha256=DdNx_B1gns01zgBg5N6Bwia46Aukw6MAteM-M37Kv1k,1122
348
349
  tol/validators/interfaces/__init__.py,sha256=jtOxnwnwqV_29xjmmMcS_kvlt-pQiWwQYJn2YRP07_w,172
349
350
  tol/validators/interfaces/condition_evaluator.py,sha256=nj8Cb8hi47OBy6OVNfeLhF-Pjwtr8MiOSymYL6hfVes,3766
350
- tol_sdk-1.8.4.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
351
- tol_sdk-1.8.4.dist-info/METADATA,sha256=g4uV0VKLkExU3Zc1waAU3ukQbtb6wRhzkaZgX-BoT5Y,3142
352
- tol_sdk-1.8.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
353
- tol_sdk-1.8.4.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
354
- tol_sdk-1.8.4.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
355
- tol_sdk-1.8.4.dist-info/RECORD,,
351
+ tol_sdk-1.8.5.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
352
+ tol_sdk-1.8.5.dist-info/METADATA,sha256=Fgyn-UcaEg4JQ7Bge0_hGHUB3gMk3g6hIbBLO3o35PQ,3142
353
+ tol_sdk-1.8.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
354
+ tol_sdk-1.8.5.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
355
+ tol_sdk-1.8.5.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
356
+ tol_sdk-1.8.5.dist-info/RECORD,,