tol-sdk 1.7.5b3__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,15 @@ 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
- data_object_factory: DataObjectFactory
105
+ data_object_factory: DataObjectFactory,
106
+ config: Config,
107
+ **kwargs
101
108
  ):
102
109
  super().__init__(data_object_factory)
103
110
 
@@ -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
 
@@ -55,3 +55,4 @@ from .tolqc_species_to_elastic_species_converter import TolqcSpeciesToElasticSpe
55
55
  from .sts_sample_to_casm_benchling_converter import StsSampleToCasmBenchlingConverterFactory # noqa F401
56
56
  from .treeofsex_species_to_treeofsexwh_species_converter import TreeofsexSpeciesToTreeofsexwhSpeciesConverter # noqa F401
57
57
  from .treeofsex_upload_to_treeofsex_attribute_converter import TreeofsexUploadToTreeofsexAttributeConverter # noqa F401
58
+ from .skip_null_fields_converter import SkipNullFieldsConverter # noqa F401
@@ -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)
@@ -0,0 +1,42 @@
1
+ # SPDX-FileCopyrightText: 2025 Genome Research Ltd.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Iterable
6
+
7
+ from tol.core import DataObject, DataObjectToDataObjectOrUpdateConverter
8
+
9
+
10
+ class SkipNullFieldsConverter(DataObjectToDataObjectOrUpdateConverter):
11
+
12
+ @dataclass(slots=True, frozen=True, kw_only=True)
13
+ class Config:
14
+ field_names: list[str]
15
+
16
+ __slots__ = ['__config']
17
+ __config: Config
18
+
19
+ def __init__(self, data_object_factory, config: Config) -> None:
20
+ super().__init__(data_object_factory)
21
+ self.__config = config
22
+ self._data_object_factory = data_object_factory
23
+
24
+ def convert(self, data_object: DataObject) -> Iterable[DataObject]:
25
+ """
26
+ removing null fields from the DataObject
27
+ """
28
+
29
+ passes = True
30
+ for field in self.__config.field_names:
31
+ value = data_object.get_field_by_name(field)
32
+ if value is None:
33
+ passes = False
34
+ break
35
+
36
+ if passes:
37
+ ret = self._data_object_factory(
38
+ data_object.type,
39
+ data_object.id,
40
+ attributes=data_object.attributes
41
+ )
42
+ yield ret
@@ -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.5b3
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=zIGcin3jcoswBfsuUe74C2nxpyzvhHkP4_ZwKZWxh_Y,3765
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,13 +142,13 @@ 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
149
149
  tol/flows/secrets.py,sha256=1mlbsxaahzYRfVAx3XdztHOmUCtDMSJDzHysdbaCtj0,352
150
150
  tol/flows/sequencing_submissions.py,sha256=ukz_y5be-BCBN2y3JPQ2EK6b3jwOCh-187j-jnw3EUY,11027
151
- tol/flows/converters/__init__.py,sha256=Ee1yMFWwsRoG71ZFCsqtNwF45AR8farmV9h3ERrK994,6154
151
+ tol/flows/converters/__init__.py,sha256=ZSF9i-32mVsV4-rNYjA6TrIEPrgku5svCF8lz6HEqf8,6231
152
152
  tol/flows/converters/benchling_entity_to_benchling_worklist_item_converter_factory.py,sha256=PN27fcvN4JLBnLrtPPAot1cWjAwPQHVcIDoMfPDeKzU,1210
153
153
  tol/flows/converters/benchling_extraction_to_elastic_extraction_converter.py,sha256=S8pbmIeKlcXrLPRJHYBUGP0-Q7jTOV2QQk2TeA2naWo,1966
154
154
  tol/flows/converters/benchling_extraction_to_elastic_sequencing_request_converter.py,sha256=2RiyRvGRSWzpUwEI4p-s0afshJpFUUxPqv2z-nyDSVg,1992
@@ -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
@@ -188,6 +188,7 @@ tol/flows/converters/labwhere_location_to_sts_tray_converter.py,sha256=dSBP5Hfdv
188
188
  tol/flows/converters/mlwh_extraction_to_elastic_extraction_converter.py,sha256=047eU_3Zb3ZlJ8utrk3o_VW7jifv5VO0WxgmFg-QkaE,1195
189
189
  tol/flows/converters/mlwh_run_data_to_elastic_run_data_converter.py,sha256=f0I_N8DH_9Kom4Fpl_O7hKTUPPiURWeyhxWN3XFLmLk,1313
190
190
  tol/flows/converters/mlwh_sequencing_request_to_elastic_sequencing_request_converter.py,sha256=zXYkq37qsvHEY7O1zh2vOoMBdt8hE7T1SuFIOR2DQrM,1209
191
+ tol/flows/converters/skip_null_fields_converter.py,sha256=IlLrX31XWpQ58indW-hk9eApMunu2HGFhMConYDmxtU,1226
191
192
  tol/flows/converters/sts_banked_sample_to_elastic_sample_converter.py,sha256=HK_3Q60E6CKCT8YSWbZXu0Wqs2mmBLYhcD0OEwXM4Qs,688
192
193
  tol/flows/converters/sts_manifest_to_elastic_manifest_converter.py,sha256=Ko3ulOFXXYaZB1YUiwu_HPlP5oPZeI65hViAZ05cioQ,1806
193
194
  tol/flows/converters/sts_project_to_elastic_sample_update_converter.py,sha256=b-hdbusSgbWDg7KwPXOVsaxzznZg0kGJcYpvvs4LDHU,623
@@ -320,28 +321,29 @@ tol/treeval/treeval_datasource.py,sha256=GzY6JwH67b5QdV-UVdCFJfgGAIuZ96J2nl53YxZ
320
321
  tol/utils/__init__.py,sha256=764-Na1OaNGUDWpMIu51ZtXG7n_nB5MccUFK6LmkWRI,138
321
322
  tol/utils/csv.py,sha256=mihww25fSn72c4h-RFeqD_pFIG6KHZP4v1_C0rx81ws,421
322
323
  tol/utils/s3.py,sha256=aoYCwJ-qcMqFrpxmViFqPa0O1jgp0phtztO3-0CSNjw,491
323
- tol/validators/__init__.py,sha256=mJDlsI_W2y5jxazwOlyf-COl_Vlj1Xk1yC5xASouGH8,1134
324
+ tol/validators/__init__.py,sha256=QI5ykFzsTLsIQXcL4vF_aaVGdSr2l0X0Qkssbnxumss,1176
324
325
  tol/validators/allowed_keys.py,sha256=RJcHBiguL84B8hjSRaXLNES21yZqaKFwJNp2Tz9zvh0,1506
325
326
  tol/validators/allowed_values.py,sha256=-Yy3Sqo1WYacGKlot_dn3M2o7Oj5MXOioJrJmrWCCxs,1536
326
327
  tol/validators/allowed_values_from_datasource.py,sha256=ICFO6FcYXDN7M2Cv1OwpyN38CdhmY7oU-njzIatA3-w,3185
327
328
  tol/validators/assert_on_condition.py,sha256=eBGgSVfIQ6e45SheM-ZDg7daXJjyZxRVS5L8AWvbXag,2027
328
- tol/validators/converter_and_validate.py,sha256=YjhLsh0qMcyZEnHK2GJFotJfZssOtr8qU4uszcPQmrg,2960
329
- 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
330
331
  tol/validators/ena_submittable.py,sha256=CujF9t4mA4N3Wm_5rA5MRp401aW19kbioOZpfWVXg6I,1965
331
332
  tol/validators/min_one_valid_value.py,sha256=gZUHtfRA-Lvpw0d1FJoAA31cRJpLbbxAJCC9DCt5lCY,1442
332
333
  tol/validators/mutually_exclusive.py,sha256=6blZK-2IY4Eq79fHKKrm-pxsQ6B5DNH5ldtxOFVCPhU,4492
333
- tol/validators/regex.py,sha256=YdFHPcvEo6jNbXxDPTnpAQeOv3kSX4OUZUKfWmFFWl0,2602
334
+ tol/validators/regex.py,sha256=dLAi_vQt9_DsT6wQZmbYC7X5-Wp15l0leUE6XkPaItg,2602
334
335
  tol/validators/regex_by_value.py,sha256=XM5EnT4vgD17rfpR3bUE9I56IemSw26BI9MZtMakd4E,2582
335
336
  tol/validators/specimens_have_same_taxon.py,sha256=m2LLRIZMdhPj1fzyioDJOraI6UHXgy1l963xhezgk7E,2177
336
- tol/validators/sts_fields.py,sha256=A_NkQFn2TMNFv2yU_ercs7CXlh-oib33ZmDZtc6SuKQ,3459
337
- 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
338
340
  tol/validators/unique_values.py,sha256=o5IrfUNLEmlEp8kpInTtFnTq-FqiHSC9TItKdf-LI1o,3114
339
341
  tol/validators/unique_whole_organisms.py,sha256=RdqA1GzIf3LTdrmNGGdxv0aW2udDY2P9EaqZb40hhik,5735
340
342
  tol/validators/interfaces/__init__.py,sha256=jtOxnwnwqV_29xjmmMcS_kvlt-pQiWwQYJn2YRP07_w,172
341
343
  tol/validators/interfaces/condition_evaluator.py,sha256=nj8Cb8hi47OBy6OVNfeLhF-Pjwtr8MiOSymYL6hfVes,3766
342
- tol_sdk-1.7.5b3.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
343
- tol_sdk-1.7.5b3.dist-info/METADATA,sha256=EJao_K6Vaa6nFKvIM8iw1vx73RocSQXiK4KkuFfQRVM,3081
344
- tol_sdk-1.7.5b3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
345
- tol_sdk-1.7.5b3.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
346
- tol_sdk-1.7.5b3.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
347
- tol_sdk-1.7.5b3.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,,