tol-sdk 1.7.5b4__py3-none-any.whl → 1.8.0__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.
@@ -3,6 +3,7 @@
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
5
  from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass
6
7
  from typing import Iterable
7
8
 
8
9
  from more_itertools import flatten
@@ -95,9 +96,14 @@ class DefaultDataObjectToDataObjectConverter(DataObjectToDataObjectOrUpdateConve
95
96
 
96
97
  class SanitisingConverter(DataObjectToDataObjectOrUpdateConverter):
97
98
 
99
+ @dataclass(slots=True, frozen=True, kw_only=True)
100
+ class Config:
101
+ pass
102
+
98
103
  def __init__(
99
104
  self,
100
105
  data_object_factory: DataObjectFactory,
106
+ config: Config,
101
107
  **kwargs
102
108
  ):
103
109
  super().__init__(data_object_factory)
@@ -65,7 +65,7 @@ class ExcelDataSource(
65
65
  ) -> Iterable[DataObject]:
66
66
 
67
67
  return (
68
- self.__marshal_row(row_index + 1, row)
68
+ self.__marshal_row(row_index + 2, row) # Add 1 for header, 1 for 1-based ID
69
69
  for row_index, row
70
70
  in self.__df.iterrows()
71
71
  )
@@ -115,6 +115,10 @@ class ExcelDataSource(
115
115
  __v: Any,
116
116
  ) -> Any:
117
117
 
118
+ # Convert pandas Timestamp to Python datetime
119
+ if isinstance(__v, pd.Timestamp):
120
+ __v = datetime.fromtimestamp(__v.timestamp())
121
+
118
122
  if __k not in self.__mappings:
119
123
  return __v
120
124
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  import re
5
5
  from dataclasses import dataclass
6
+ from datetime import datetime
6
7
  from typing import Iterable
7
8
 
8
9
  from tol.core import DataObject, DataObjectToDataObjectOrUpdateConverter
@@ -30,8 +31,10 @@ class IncomingSampleToEnaSampleConverter(DataObjectToDataObjectOrUpdateConverter
30
31
  s = data_object
31
32
  attributes = {
32
33
  'ENA-CHECKLIST': self.__config.ena_checklist_id,
33
- 'organism part': self.__replace_underscores(
34
- s.attributes.get('ORGANISM_PART')),
34
+ 'organism part': self.__join_list([
35
+ self.__replace_underscores(v)
36
+ for v in s.attributes.get('ORGANISM_PART', [])
37
+ ]),
35
38
  'lifestage': (
36
39
  'spore-bearing structure'
37
40
  if s.attributes.get('LIFESTAGE') == 'SPORE_BEARING_STRUCTURE'
@@ -40,35 +43,38 @@ class IncomingSampleToEnaSampleConverter(DataObjectToDataObjectOrUpdateConverter
40
43
  ),
41
44
  'project name':
42
45
  self.__config.project_name,
43
- 'collected by':
44
- self.__replace_underscores(
45
- s.attributes.get('COLLECTED_BY')),
46
+ 'collected_by': self.__join_list([
47
+ self.__replace_underscores(v)
48
+ for v in s.attributes.get('COLLECTED_BY', [])
49
+ ]),
46
50
  'collection date':
47
- self.__replace_underscores(
48
- s.attributes.get('DATE_OF_COLLECTION')).lower(),
51
+ self.__format_date(
52
+ s.attributes.get('DATE_OF_COLLECTION')),
49
53
  'geographic location (country and/or sea)':
50
54
  self.__collection_country(s).replace('_', ' '),
51
55
  'geographic location (latitude)':
52
56
  self.__replace_underscores(
53
- s.attributes.get('DECIMAL_LATITUDE')).lower(),
57
+ str(s.attributes.get('DECIMAL_LATITUDE'))).lower(),
54
58
  'geographic location (latitude) units':
55
59
  'DD',
56
60
  'geographic location (longitude)':
57
61
  self.__replace_underscores(
58
- s.attributes.get('DECIMAL_LONGITUDE')).lower(),
62
+ str(s.attributes.get('DECIMAL_LONGITUDE'))).lower(),
59
63
  'geographic location (longitude) units':
60
64
  'DD',
61
65
  'geographic location (region and locality)':
62
66
  self.__collection_region(s).replace('_', ' '),
63
- 'identified_by':
64
- self.__replace_underscores(
65
- s.attributes.get('IDENTIFIED_BY')),
67
+ 'identified_by': self.__join_list([
68
+ self.__replace_underscores(v)
69
+ for v in s.attributes.get('IDENTIFIED_BY', [])
70
+ ]),
66
71
  'habitat':
67
72
  self.__replace_underscores(
68
73
  s.attributes.get('HABITAT')),
69
- 'identifier_affiliation':
70
- self.__replace_underscores(
71
- s.attributes.get('IDENTIFIER_AFFILIATION')),
74
+ 'identifier_affiliation': self.__join_list([
75
+ self.__replace_underscores(v)
76
+ for v in s.attributes.get('IDENTIFIER_AFFILIATION', [])
77
+ ]),
72
78
  'sex':
73
79
  self.__replace_underscores(
74
80
  s.attributes.get('SEX')),
@@ -77,9 +83,10 @@ class IncomingSampleToEnaSampleConverter(DataObjectToDataObjectOrUpdateConverter
77
83
  s.attributes.get('RELATIONSHIP')),
78
84
  'SYMBIONT':
79
85
  'Y' if s.attributes.get('SYMBIONT') == 'SYMBIONT' else 'N',
80
- 'collecting institution':
81
- self.__replace_underscores(
82
- s.attributes.get('COLLECTOR_AFFILIATION'))
86
+ 'collecting institution': self.__join_list([
87
+ self.__replace_underscores(v)
88
+ for v in s.attributes.get('COLLECTOR_AFFILIATION', [])
89
+ ]),
83
90
  }
84
91
  if self.__sanitise(s.attributes.get('DEPTH')) != '':
85
92
  attributes['geographic location (depth)'] = s.attributes.get('DEPTH')
@@ -88,9 +95,11 @@ class IncomingSampleToEnaSampleConverter(DataObjectToDataObjectOrUpdateConverter
88
95
  attributes['geographic location (elevation)'] = s.attributes.get('ELEVATION')
89
96
  attributes['geographic location (elevation) units'] = 'm'
90
97
  if self.__sanitise(s.attributes.get('ORIGINAL_COLLECTION_DATE')) != '':
91
- attributes['original collection date'] = s.attributes.get('ORIGINAL_COLLECTION_DATE')
98
+ attributes['original collection date'] = \
99
+ self.__format_date(s.attributes.get('ORIGINAL_COLLECTION_DATE'))
92
100
  if self.__sanitise(s.attributes.get('ORIGINAL_GEOGRAPHIC_LOCATION')) != '':
93
- attributes['original geographic location'] = self.__replace_underscores(s.attributes.get('ORIGINAL_GEOGRAPHIC_LOCATION')) # noqa
101
+ attributes['original geographic location'] = \
102
+ self.__replace_underscores(s.attributes.get('ORIGINAL_GEOGRAPHIC_LOCATION'))
94
103
  if s.attributes.get('GAL') is not None:
95
104
  attributes['GAL'] = s.attributes.get('GAL')
96
105
  if s.attributes.get('VOUCHER_ID') is not None:
@@ -103,7 +112,7 @@ class IncomingSampleToEnaSampleConverter(DataObjectToDataObjectOrUpdateConverter
103
112
  attributes['culture_or_strain_id'] = s.attributes.get('CULTURE_OR_STRAIN_ID')
104
113
 
105
114
  ret = self._data_object_factory(
106
- 'sample',
115
+ data_object.type,
107
116
  s.id,
108
117
  attributes=attributes,
109
118
  )
@@ -128,3 +137,19 @@ class IncomingSampleToEnaSampleConverter(DataObjectToDataObjectOrUpdateConverter
128
137
  if value is None:
129
138
  return default_value
130
139
  return value
140
+
141
+ def __join_list(self, value_list):
142
+ if value_list is None:
143
+ return ''
144
+ if not isinstance(value_list, list):
145
+ return str(value_list)
146
+ return ' | '.join(str(v) for v in value_list)
147
+
148
+ def __format_date(self, value):
149
+ """Format date to YYYY-mm-dd format"""
150
+ if value is None:
151
+ return ''
152
+ if isinstance(value, datetime):
153
+ return value.strftime('%Y-%m-%d')
154
+
155
+ return str(value)
@@ -15,6 +15,7 @@ from .regex_by_value import RegexByValueValidator # noqa
15
15
  from .specimens_have_same_taxon import SpecimensHaveSameTaxonValidator # noqa
16
16
  from .sts_fields import StsFieldsValidator # noqa
17
17
  from .tolid import TolidValidator # noqa
18
+ from .types import TypesValidator # noqa
18
19
  from .unique_values import UniqueValuesValidator # noqa
19
20
  from .unique_whole_organisms import UniqueWholeOrganismsValidator # noqa
20
21
  from .interfaces import Condition # noqa
@@ -19,12 +19,12 @@ class ConverterAndValidateValidator(Validator):
19
19
  "converters": [{
20
20
  "module": "<path.to.module>",
21
21
  "class_name": "<path.to.ConverterClass>",
22
- "config": { ... }
22
+ "config_details": { ... }
23
23
  }],
24
24
  "validators": [{
25
25
  "module": "<path.to.module>",
26
26
  "class_name": "<path.to.ValidatorClass>",
27
- "config": { ... }
27
+ "config_details": { ... }
28
28
  }]
29
29
  }
30
30
 
@@ -54,7 +54,7 @@ class ConverterAndValidateValidator(Validator):
54
54
  converter_class = getattr(__module, conv.get('class_name'))
55
55
 
56
56
  converter_conf = converter_class.Config(
57
- **conv.get('config')
57
+ **conv.get('config_details')
58
58
  )
59
59
  self.__converters.append(converter_class(
60
60
  data_object_factory=data_object_factory,
@@ -65,7 +65,7 @@ class ConverterAndValidateValidator(Validator):
65
65
  validator_class = getattr(__module, val.get('class_name'))
66
66
 
67
67
  validator_conf = validator_class.Config(
68
- **val.get('config')
68
+ **val.get('config_details')
69
69
  )
70
70
  self.__validators.append(validator_class(
71
71
  data_object_factory=data_object_factory,
@@ -27,31 +27,31 @@ class EnaChecklistValidator(Validator):
27
27
  super().__init__()
28
28
  self.__config = config
29
29
  self._datasource = datasource
30
+ self.__ena_checklist = datasource.get_one(
31
+ 'checklist',
32
+ self.__config.ena_checklist_id
33
+ ).checklist
30
34
 
31
35
  def _validate_data_object(self, obj: DataObject) -> None:
32
- ena_datasource = self._datasource
33
- ena_checklist = ena_datasource.get_one('checklist', self.__config.ena_checklist_id)
34
-
35
- validations = ena_checklist.attributes['checklist']
36
- for key in validations:
36
+ for key, validation in self.__ena_checklist.items():
37
37
  field_name = key
38
- if 'field' in validations[key]:
39
- field_name = validations[key]['field']
40
- if 'mandatory' in validations[key] and key not in obj.attributes:
38
+ if 'field' in validation:
39
+ field_name = validation['field']
40
+ if 'mandatory' in validation and key not in obj.attributes:
41
41
  self.add_error(object_id=obj.id, detail='Must be given', field=[field_name])
42
42
  continue
43
- if 'mandatory' in validations[key] and obj.attributes[key] is None:
43
+ if 'mandatory' in validation and obj.attributes[key] is None:
44
44
  self.add_error(object_id=obj.id, detail='Must be given', field=[field_name])
45
45
  continue
46
- if 'mandatory' in validations[key] and obj.attributes.get(key) == '':
46
+ if 'mandatory' in validation and obj.attributes.get(key) == '':
47
47
  self.add_error(
48
48
  object_id=obj.id,
49
49
  detail='Must not be empty', field=[field_name]
50
50
  )
51
51
 
52
- if 'restricted text' in validations[key] and key in obj.attributes:
53
- for condition in validations[key]:
54
- if type(condition) == str and '(' in condition:
52
+ if 'restricted text' in validation and key in obj.attributes:
53
+ for condition in validation:
54
+ if isinstance(condition, str) and '(' in condition:
55
55
  regex = condition
56
56
  compiled_re = re.compile(regex)
57
57
  if not compiled_re.search(obj.attributes.get(key)):
@@ -61,9 +61,9 @@ class EnaChecklistValidator(Validator):
61
61
  )
62
62
 
63
63
  # Check against allowed values
64
- if 'text choice' in validations[key] and key in obj.attributes:
65
- for condition in validations[key]:
66
- if type(condition) == list:
64
+ if 'text choice' in validation and key in obj.attributes:
65
+ for condition in validation:
66
+ if isinstance(condition, list):
67
67
  allowed_values = condition
68
68
  if obj.attributes.get(key).lower() not in \
69
69
  [x.lower() for x in allowed_values]:
tol/validators/regex.py CHANGED
@@ -26,9 +26,9 @@ class Regex:
26
26
 
27
27
  def is_allowed(self, __v: Any) -> bool:
28
28
  # Check regex
29
- return bool(re.search(
29
+ return __v is None or __v == '' or bool(re.search(
30
30
  self.regex,
31
- str(__v) if __v is not None else ''
31
+ str(__v)
32
32
  ))
33
33
 
34
34
 
@@ -90,7 +90,6 @@ class RegexValidator(Validator):
90
90
  obj: DataObject,
91
91
  c: Regex,
92
92
  ) -> None:
93
-
94
93
  if c.is_error:
95
94
  self.add_error(
96
95
  object_id=obj.id,
@@ -53,36 +53,42 @@ class StsFieldsValidator(Validator):
53
53
  for field in self.__fields.values():
54
54
  # Get the value from the data object
55
55
  field_value = obj.get_field_by_name(field.get('data_input_key'))
56
- if field.get('mandatory_input') and (field_value is None or field_value == ''):
56
+ if field.get('mandatory_validation') and (field_value is None or field_value == ''):
57
57
  self.add_error(
58
58
  object_id=obj.id,
59
59
  detail=f'Field {field.get("data_input_key")} is required '
60
60
  f'for project {self.__config.project_code}',
61
61
  field=field.get('data_input_key'),
62
62
  )
63
- elif field.get('allowed_values') and field_value not in field.get('allowed_values'):
63
+ elif field.get('allowed_values'):
64
+ allowed_values = [
65
+ value.get('value') for value in field.get('allowed_values', [])
66
+ ]
67
+ if field_value not in allowed_values:
68
+ self.add_error(
69
+ object_id=obj.id,
70
+ detail=f'Field {field.get("data_input_key")} value '
71
+ f'"{field_value}" not found in allowed values '
72
+ f'{allowed_values} for project '
73
+ f'{self.__config.project_code}',
74
+ field=field.get('data_input_key'),
75
+ )
76
+ elif field.get('min') and field.get('type') == 'String' \
77
+ and field_value is not None and len(field_value) < field.get('min'):
64
78
  self.add_error(
65
79
  object_id=obj.id,
66
80
  detail=f'Field {field.get("data_input_key")} value '
67
- f'"{field_value}" not found in allowed values '
68
- f'{field.get("allowed_values")} for project '
69
- f'{self.__config.project_code}',
70
- field=field.get('data_input_key'),
71
- )
72
- elif field.get('min') and field_value < field.get('min'):
73
- self.add_error(
74
- object_id=obj.id,
75
- detail=f'Field {field.get("data_input_key")} value '
76
- f'"{field_value}" is less than minimum value '
81
+ f'"{field_value}" is shorter than minimum length '
77
82
  f'"{field.get("min")}" for project '
78
83
  f'{self.__config.project_code}',
79
84
  field=field.get('data_input_key'),
80
85
  )
81
- elif field.get('max') and field_value > field.get('max'):
86
+ elif field.get('max') and field.get('type') == 'String' \
87
+ and field_value is not None and len(field_value) > field.get('max'):
82
88
  self.add_error(
83
89
  object_id=obj.id,
84
90
  detail=f'Field {field.get("data_input_key")} value '
85
- f'"{field_value}" is greater than maximum value '
91
+ f'"{field_value}" is longer than maximum length '
86
92
  f'"{field.get("max")}" for project '
87
93
  f'{self.__config.project_code}',
88
94
  field=field.get('data_input_key'),
tol/validators/tolid.py CHANGED
@@ -103,7 +103,7 @@ class TolidValidator(Validator):
103
103
 
104
104
  if str(obj.get_field_by_name(self.__config.species_id_field)) not in taxons:
105
105
  self.add_error(
106
- object_id=obj.id,
106
+ object_id=obj.id + 1,
107
107
  detail=f'Specimen ID {specimen_id} does not match Taxon ID '
108
108
  f'{obj.get_field_by_name(self.__config.species_id_field)}'
109
109
  'in TolID source',
@@ -0,0 +1,87 @@
1
+ # SPDX-FileCopyrightText: 2025 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime, time
7
+
8
+ from tol.core import DataObject
9
+ from tol.core.validate import Validator
10
+
11
+
12
+ class TypesValidator(Validator):
13
+ """
14
+ Validates an incoming stream of `DataObject` instances,
15
+ ensuring that they only have attributes of the given
16
+ allowed keys.
17
+ """
18
+ @dataclass(slots=True, frozen=True, kw_only=True)
19
+ class Config:
20
+ allowed_types: dict[str, str]
21
+ is_error: bool = True
22
+ detail: str = 'Value is of incorrect type'
23
+
24
+ __slots__ = ['__config']
25
+ __config: Config
26
+
27
+ def __init__(
28
+ self,
29
+ config: Config,
30
+ **kwargs
31
+ ) -> None:
32
+
33
+ super().__init__()
34
+ self.__config = config
35
+
36
+ def _validate_data_object(
37
+ self,
38
+ obj: DataObject
39
+ ) -> None:
40
+
41
+ type_map = {
42
+ 'str': str,
43
+ 'int': int,
44
+ 'float': float,
45
+ 'bool': bool,
46
+ 'list': list,
47
+ 'dict': dict,
48
+ 'datetime': datetime,
49
+ 'time': time
50
+ }
51
+ for key, expected_type in self.__config.allowed_types.items():
52
+ if key in obj.attributes:
53
+ actual_value = obj.get_field_by_name(key)
54
+ if actual_value is None:
55
+ continue
56
+ type_class = type_map.get(expected_type)
57
+ if type_class and not isinstance(actual_value, type_class):
58
+ self.__add_result(
59
+ obj,
60
+ key,
61
+ )
62
+ if type_class and isinstance(actual_value, type_class):
63
+ # Special case for bool since isinstance(True, int) is True
64
+ if expected_type == 'int' and isinstance(actual_value, bool):
65
+ self.__add_result(
66
+ obj,
67
+ key,
68
+ )
69
+
70
+ def __add_result(
71
+ self,
72
+ obj: DataObject,
73
+ key: str,
74
+ ) -> None:
75
+
76
+ if self.__config.is_error:
77
+ self.add_error(
78
+ object_id=obj.id,
79
+ detail=self.__config.detail,
80
+ field=key,
81
+ )
82
+ else:
83
+ self.add_warning(
84
+ object_id=obj.id,
85
+ detail=self.__config.detail,
86
+ field=key,
87
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tol-sdk
3
- Version: 1.7.5b4
3
+ Version: 1.8.0
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
@@ -50,6 +50,8 @@ Requires-Dist: openpyxl>=3.0.10; extra == "sheets"
50
50
  Requires-Dist: XlsxWriter==3.1.9; extra == "sheets"
51
51
  Requires-Dist: xlrd==2.0.1; extra == "sheets"
52
52
  Requires-Dist: gspread>=5.12.0; extra == "sheets"
53
+ Provides-Extra: s3
54
+ Requires-Dist: minio==7.2.15; extra == "s3"
53
55
  Provides-Extra: all
54
56
  Requires-Dist: tol-sdk[api-base]; extra == "all"
55
57
  Requires-Dist: tol-sdk[benchling]; extra == "all"
@@ -89,7 +89,7 @@ tol/core/attribute_metadata.py,sha256=wYD3NXDdStrpkUZoyTUiEpp7c14f7MLIcyooT1G4GE
89
89
  tol/core/core_converter.py,sha256=Gn4J507BtqDjnOWV2MFRYGz8YElJAKQItmnCrD72s7k,4504
90
90
  tol/core/data_loader.py,sha256=k-ET1nIohIz6PcADbEn9Y7k9TupoiBYxKDkcAl_9pGY,14710
91
91
  tol/core/data_object.py,sha256=GxG04JMcICaiHU1rufkhoD8jb9YQLhE0QWlFU2ZkQsM,4241
92
- tol/core/data_object_converter.py,sha256=FUNUXGi5FIdIe34B0g32hhRf8GGzWTHW7vLhb1GXG6E,3783
92
+ tol/core/data_object_converter.py,sha256=GESpLvwrAEwmCfBwy3GxcSCuHz0xt7ECCBPE2stxBdI,3927
93
93
  tol/core/data_source_attribute_metadata.py,sha256=NHvJ_Gmw7-Oej1MoFCohvq4f6emDJ2HF483UmW2Qd_c,4407
94
94
  tol/core/data_source_dict.py,sha256=d-hSmoWTwG6IOc0cQTLap1EBslsxYIWGUd3ScSoeH_Q,1705
95
95
  tol/core/datasource.py,sha256=e9GaeDPfO_Gs7cgQhmNxCiSDlRNf64reegzFebcMNkA,6303
@@ -142,7 +142,7 @@ tol/ena/filter.py,sha256=UzOx5ivXvA0TY2QuNzFmS-zDPVNnaAx07DMVkAwVsAE,3370
142
142
  tol/ena/parser.py,sha256=Z4YmUnpfLKng4QwmZkLEj1hUfwYb_bqr-DWgF1Gw-EY,3253
143
143
  tol/excel/__init__.py,sha256=M0xL9w9Au8kYOLWzFGuijJ7WoZENOMkZ1XV1ephhlDY,229
144
144
  tol/excel/excel.py,sha256=rcA-wfXY9R14OfNKS-NX2sn__9gmQ_G8LoUgWseF1Gk,2124
145
- tol/excel/excel_datasource.py,sha256=nIMvkCZ1edx8djqsVsPTRi6yCfyKc_dIokTpLy16rwY,3091
145
+ tol/excel/excel_datasource.py,sha256=9dRvpfIaolYbXrXUb7EHOWkROIBlAsYYlsNQuGel_gU,3284
146
146
  tol/excel/s3_factory.py,sha256=4lGyKrSvarPXWndyvm7K-tel0FoM0My8wnz-Mzwt0yQ,1245
147
147
  tol/flows/__init__.py,sha256=M7iSvnBJs6fJ8M38cW0bYQa9WW0TN8FHAMjIHPDNAJ4,166
148
148
  tol/flows/logger.py,sha256=rWXbaknGcPEZRFvC1CiB1qkhFRZsQk435w7VyJ3cpyw,170
@@ -180,7 +180,7 @@ tol/flows/converters/gap_assembly_to_elastic_assembly_converter.py,sha256=XK-es-
180
180
  tol/flows/converters/genome_notes_genome_note_to_elastic_genome_note_converter.py,sha256=AaUWbVTaWU-NXnUQPaPwI41TE7a-nC4zlg-jrWpPT2s,1166
181
181
  tol/flows/converters/goat_taxon_to_elastic_species_converter.py,sha256=1NGs9427OdXGsBaMB467nOF7aTlJsUKYCuoSoABw9L4,1074
182
182
  tol/flows/converters/grit_issue_to_elastic_curation_converter.py,sha256=XpRpoRn589MxTqEk6zPWGn6tamJiqY9Ctxk8v0q-dvA,3953
183
- tol/flows/converters/incoming_sample_to_ena_sample_converter.py,sha256=HmGsg-VCE4W9Dl3lAlcNhWfkVYp1d22DZlFoTaFzeqA,5560
183
+ tol/flows/converters/incoming_sample_to_ena_sample_converter.py,sha256=SAVYWENG3GS7B1rM6rYwxfLQH75nZl7mEzphH5CBxRw,6353
184
184
  tol/flows/converters/incoming_sample_to_incoming_sample_with_lists_converter.py,sha256=5Fp1_ojsYqvRcKTgXJbyWqetPisi_vtWFcWr6RtGZoA,1504
185
185
  tol/flows/converters/informatics_tolid_to_elastic_tolid_converter.py,sha256=VrvtsDTPlc5Xa3K4rcAMHwV4n71zOH7q5EfALLLQ1tI,587
186
186
  tol/flows/converters/labwhere_location_to_elastic_sample_update_converter.py,sha256=NJNmG9sCc2WXc-2J5XfCKXhb2sDH82nZUBekd16PHcw,656
@@ -321,28 +321,29 @@ tol/treeval/treeval_datasource.py,sha256=GzY6JwH67b5QdV-UVdCFJfgGAIuZ96J2nl53YxZ
321
321
  tol/utils/__init__.py,sha256=764-Na1OaNGUDWpMIu51ZtXG7n_nB5MccUFK6LmkWRI,138
322
322
  tol/utils/csv.py,sha256=mihww25fSn72c4h-RFeqD_pFIG6KHZP4v1_C0rx81ws,421
323
323
  tol/utils/s3.py,sha256=aoYCwJ-qcMqFrpxmViFqPa0O1jgp0phtztO3-0CSNjw,491
324
- tol/validators/__init__.py,sha256=mJDlsI_W2y5jxazwOlyf-COl_Vlj1Xk1yC5xASouGH8,1134
324
+ tol/validators/__init__.py,sha256=QI5ykFzsTLsIQXcL4vF_aaVGdSr2l0X0Qkssbnxumss,1176
325
325
  tol/validators/allowed_keys.py,sha256=RJcHBiguL84B8hjSRaXLNES21yZqaKFwJNp2Tz9zvh0,1506
326
326
  tol/validators/allowed_values.py,sha256=-Yy3Sqo1WYacGKlot_dn3M2o7Oj5MXOioJrJmrWCCxs,1536
327
327
  tol/validators/allowed_values_from_datasource.py,sha256=ICFO6FcYXDN7M2Cv1OwpyN38CdhmY7oU-njzIatA3-w,3185
328
328
  tol/validators/assert_on_condition.py,sha256=eBGgSVfIQ6e45SheM-ZDg7daXJjyZxRVS5L8AWvbXag,2027
329
- tol/validators/converter_and_validate.py,sha256=YjhLsh0qMcyZEnHK2GJFotJfZssOtr8qU4uszcPQmrg,2960
330
- tol/validators/ena_checklist.py,sha256=VGJeDrHH-XzueforuyyCEgEi6y9NurhvuOSL-gSDoOE,2885
329
+ tol/validators/converter_and_validate.py,sha256=O1uYdrU4YDZ8eZjb7Koots4-8fMVOkJFXESg-LVw2o8,2992
330
+ tol/validators/ena_checklist.py,sha256=M10VAFGpaxnm7rWO4jmFhTWkYRlCmU0Ox2IUEDFGKbo,2812
331
331
  tol/validators/ena_submittable.py,sha256=CujF9t4mA4N3Wm_5rA5MRp401aW19kbioOZpfWVXg6I,1965
332
332
  tol/validators/min_one_valid_value.py,sha256=gZUHtfRA-Lvpw0d1FJoAA31cRJpLbbxAJCC9DCt5lCY,1442
333
333
  tol/validators/mutually_exclusive.py,sha256=6blZK-2IY4Eq79fHKKrm-pxsQ6B5DNH5ldtxOFVCPhU,4492
334
- tol/validators/regex.py,sha256=YdFHPcvEo6jNbXxDPTnpAQeOv3kSX4OUZUKfWmFFWl0,2602
334
+ tol/validators/regex.py,sha256=dLAi_vQt9_DsT6wQZmbYC7X5-Wp15l0leUE6XkPaItg,2602
335
335
  tol/validators/regex_by_value.py,sha256=XM5EnT4vgD17rfpR3bUE9I56IemSw26BI9MZtMakd4E,2582
336
336
  tol/validators/specimens_have_same_taxon.py,sha256=m2LLRIZMdhPj1fzyioDJOraI6UHXgy1l963xhezgk7E,2177
337
- tol/validators/sts_fields.py,sha256=A_NkQFn2TMNFv2yU_ercs7CXlh-oib33ZmDZtc6SuKQ,3459
338
- tol/validators/tolid.py,sha256=kgo-OWW3at6jK4DQtdgVFjO06sDvqG4QulGkI-jjVRU,3893
337
+ tol/validators/sts_fields.py,sha256=IUWeqqNpSLZJVfhGewLHuClZMnxoaIOzg9bGnprxXzo,3805
338
+ tol/validators/tolid.py,sha256=yODebLYbKtlem3IpVcv8XImvq90r-AK68asH9JEawqo,3897
339
+ tol/validators/types.py,sha256=hZ8Iz47kKvmqv-UuOz5oj6D8RjjPDfpMEKw0m5t5BaI,2436
339
340
  tol/validators/unique_values.py,sha256=o5IrfUNLEmlEp8kpInTtFnTq-FqiHSC9TItKdf-LI1o,3114
340
341
  tol/validators/unique_whole_organisms.py,sha256=RdqA1GzIf3LTdrmNGGdxv0aW2udDY2P9EaqZb40hhik,5735
341
342
  tol/validators/interfaces/__init__.py,sha256=jtOxnwnwqV_29xjmmMcS_kvlt-pQiWwQYJn2YRP07_w,172
342
343
  tol/validators/interfaces/condition_evaluator.py,sha256=nj8Cb8hi47OBy6OVNfeLhF-Pjwtr8MiOSymYL6hfVes,3766
343
- tol_sdk-1.7.5b4.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
344
- tol_sdk-1.7.5b4.dist-info/METADATA,sha256=ZR7pMG-jGHoasPgY1zqvDMJMc4rm4rbqRi0hiC5pyN4,3081
345
- tol_sdk-1.7.5b4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
346
- tol_sdk-1.7.5b4.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
347
- tol_sdk-1.7.5b4.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
348
- tol_sdk-1.7.5b4.dist-info/RECORD,,
344
+ tol_sdk-1.8.0.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
345
+ tol_sdk-1.8.0.dist-info/METADATA,sha256=Sz6ELtecqM1ndqoLPi2pa7JN6urXSDCv4mFS_RK5Gz4,3142
346
+ tol_sdk-1.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
347
+ tol_sdk-1.8.0.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
348
+ tol_sdk-1.8.0.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
349
+ tol_sdk-1.8.0.dist-info/RECORD,,