tol-sdk 1.8.3__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/client.py CHANGED
@@ -206,7 +206,7 @@ class JsonApiClient(HttpClient):
206
206
  )
207
207
 
208
208
  headers = self._merge_headers()
209
- session = self._get_session()
209
+ session = self.get_session()
210
210
  r = session.post(
211
211
  url,
212
212
  headers=headers,
@@ -228,7 +228,7 @@ class JsonApiClient(HttpClient):
228
228
 
229
229
  url = self.__insert_url(object_type)
230
230
  headers = self._merge_headers()
231
- session = self._get_session()
231
+ session = self.get_session()
232
232
  r = session.post(url, headers=headers, json=transfer)
233
233
  self.__assert_no_error(r)
234
234
  return r.json()
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:
tol/core/http_client.py CHANGED
@@ -55,7 +55,7 @@ class HttpClient:
55
55
  **__empty_if_none(self.__token)
56
56
  }
57
57
 
58
- def _get_session(self) -> requests.Session:
58
+ def get_session(self) -> requests.Session:
59
59
 
60
60
  cert_path = os.path.join(
61
61
  os.path.dirname(__file__),
@@ -74,7 +74,7 @@ class HttpClient:
74
74
  """
75
75
  Attempts a call to the endpoint 5 times, with a delay of 1 second
76
76
  """
77
- session = self._get_session()
77
+ session = self.get_session()
78
78
 
79
79
  retry_strategy = Retry(
80
80
  total=self.__retries,
@@ -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
tol/sql/auth/blueprint.py CHANGED
@@ -22,6 +22,7 @@ from ...api_base.auth import (
22
22
  )
23
23
  from ...api_base.auth.abc import AuthorisationManager
24
24
  from ...api_base.misc import AuthContext
25
+ from ...core import HttpClient
25
26
 
26
27
 
27
28
  class DbAuthManager(AuthManager):
@@ -194,7 +195,12 @@ class DbAuthManager(AuthManager):
194
195
  Raises:
195
196
  requests.HTTPError: If the revocation request fails
196
197
  """
197
- r = requests.post(
198
+
199
+ client = HttpClient()
200
+
201
+ session = client.get_session()
202
+
203
+ r = session.post(
198
204
  self.__config.revoke_url,
199
205
  data={
200
206
  'token': token,
@@ -21,4 +21,6 @@ from .unique_whole_organisms import UniqueWholeOrganismsValidator # noqa
21
21
  from .interfaces import Condition # noqa
22
22
  from .min_one_valid_value import MinOneValidValueValidator # noqa
23
23
  from .value_check import ValueCheckValidator # noqa
24
+ from .branching import BranchingValidator # noqa
24
25
  from .unique_value_check import UniqueValueCheckValidator # noqa
26
+ from .date_sorting import DateSortingValidator # noqa
@@ -0,0 +1,160 @@
1
+ # SPDX-FileCopyrightText: 2026 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ import importlib
6
+ from dataclasses import dataclass
7
+ from typing import Dict, List, cast
8
+
9
+ from tol.core import DataObject
10
+ from tol.core.validate import ValidationResult, Validator
11
+
12
+ from .interfaces import Condition, ConditionDict, ConditionEvaluator
13
+
14
+
15
+ Subvalidation = Dict[str, ConditionDict | str | Dict]
16
+
17
+
18
+ class BranchingValidator(Validator, ConditionEvaluator):
19
+ """
20
+ This validator is configured with a list of conditions.
21
+ If a condition passes, the corresponding sub-validator will be run.
22
+ """
23
+ @dataclass(slots=True, frozen=True, kw_only=True)
24
+ class Config:
25
+ """
26
+ ```
27
+ validations=[
28
+ {
29
+ 'condition': {
30
+ 'field': 'column_name',
31
+ 'operator': '==',
32
+ 'value': 'expected_value',
33
+ },
34
+ 'module': '<path.to.module>',
35
+ 'class_name': '<path.to.ValidatorClass>',
36
+ 'config_details': { ... },
37
+ },
38
+ {
39
+ ...
40
+ }
41
+ ]
42
+ ```
43
+ """
44
+ validations: List[Subvalidation]
45
+
46
+ __slots__ = ['__config', '_cached_validators']
47
+ __config: Config
48
+ _cached_validators: Dict[int, Validator]
49
+ """
50
+ Stores all sub-validators that have already been seen so that they can be used again.
51
+ Their keys are their indexes in the `validations` list in the validator config
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ config: Config,
57
+ **kwargs
58
+ ) -> None:
59
+ super().__init__()
60
+
61
+ del kwargs
62
+ self.__config = config
63
+ self._cached_validators = {}
64
+
65
+ def _validate_data_object(
66
+ self,
67
+ obj: DataObject
68
+ ) -> None:
69
+ for subvalidator_index, subvalidation in enumerate(self.__config.validations):
70
+ # Do not run this sub-validation if its condition does not pass
71
+ condition_dict = cast(ConditionDict, subvalidation['condition'])
72
+ if not self._does_condition_pass(Condition.from_dict(condition_dict), obj):
73
+ continue
74
+
75
+ # Obtain validator, either from cache or anew
76
+ validator: Validator
77
+ if subvalidator_index in self._cached_validators:
78
+ # Use existing validator to perform subvalidation
79
+ validator = self._cached_validators[subvalidator_index]
80
+ else:
81
+ # Create a new validator and use that for the subvalidation
82
+ validator = self.__instantiate_validator(subvalidation)
83
+
84
+ # Add the new validator to the store of cached validators
85
+ self._cached_validators[subvalidator_index] = validator
86
+
87
+ # Validate data object
88
+ validator._validate_data_object(obj)
89
+
90
+ # At this point, I initially added `break` so that once a condition passed, the rest
91
+ # did not need to be checked. However, I decided against it because it is more flexible
92
+ # to have the opportunity to allow multiple conditions to pass, and thus execute
93
+ # multiple subvalidations, on the same `DataObject` in the same iteration
94
+
95
+ def __instantiate_validator(self, subvalidation: Subvalidation) -> Validator:
96
+ # Before attempting to extract items from the subvalidation, ensure all of the required
97
+ # keys are there and that they contain values of the correct types.
98
+ # This allows the dictionary to be safely queried thereafter
99
+ try:
100
+ # Accessing with square brackets will also check whether the key exists or not
101
+ if not isinstance(subvalidation['module'], str):
102
+ raise TypeError('module')
103
+ elif not isinstance(subvalidation['class_name'], str):
104
+ raise TypeError('class_name')
105
+ except (KeyError, TypeError) as e:
106
+ # Make errors more specific to validators
107
+ if isinstance(e, KeyError):
108
+ raise KeyError(
109
+ f'Invalid config in BranchingValidator: `{e.args[0]}` not found'
110
+ )
111
+ else:
112
+ raise TypeError(
113
+ f'Invalid config in BranchingValidator: '
114
+ f'`{e.args[0]}` contains an erroneous value type'
115
+ )
116
+
117
+ # Dynamically retrieve the validator class
118
+ validator_module = importlib.import_module(subvalidation['module'])
119
+ validator_class = getattr(validator_module, subvalidation['class_name'])
120
+
121
+ # Construct and return the new validator
122
+ validator_config = validator_class.Config(
123
+ subvalidation['config_details']
124
+ )
125
+ return validator_class(
126
+ config=validator_config,
127
+ )
128
+
129
+ @property
130
+ def results(self) -> List[ValidationResult]:
131
+ """
132
+ Fetches results from all sub-validators, collated into a single list
133
+ """
134
+ return [
135
+ result
136
+ for validator in self._cached_validators.values()
137
+ for result in validator.results
138
+ ]
139
+
140
+ @property
141
+ def warnings(self) -> List[ValidationResult]:
142
+ """
143
+ Fetches warnings from all sub-validators, collated into a single list
144
+ """
145
+ return [
146
+ warning
147
+ for validator in self._cached_validators.values()
148
+ for warning in validator.warnings
149
+ ]
150
+
151
+ @property
152
+ def errors(self) -> List[ValidationResult]:
153
+ """
154
+ Fetches errors from all sub-validators, collated into a single list
155
+ """
156
+ return [
157
+ error
158
+ for validator in self._cached_validators.values()
159
+ for error in validator.errors
160
+ ]
@@ -0,0 +1,58 @@
1
+ # SPDX-FileCopyrightText: 2026 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import date, datetime
7
+
8
+ from tol.core import Validator
9
+ from tol.core.data_object import DataObject
10
+
11
+
12
+ class DateSortingValidator(Validator):
13
+ """
14
+ Validates an incoming stream of `DataObject` instances.
15
+ For each data object (sample) check the collection date
16
+ is not preceding the plating date
17
+ """
18
+ @dataclass(slots=True, frozen=True, kw_only=True)
19
+ class Config:
20
+ dates: list[str]
21
+
22
+ __slots__ = ['__config']
23
+ __config: Config
24
+
25
+ def __init__(self, config: Config, **kwargs) -> None:
26
+ super().__init__()
27
+ self.__config = config
28
+
29
+ def _validate_data_object(self, obj: DataObject) -> None:
30
+ # This function is used to check if the dates obtained
31
+ # are in the standard format and the date of collection
32
+ # is not preceding the date of plating
33
+
34
+ previous_date = None
35
+
36
+ for date_field in self.__config.dates:
37
+ date_value = obj.get_field_by_name(date_field)
38
+
39
+ # Validate that the value is a date or datetime object
40
+ if not isinstance(date_value, (date, datetime)):
41
+ self.add_error(
42
+ object_id=obj.id,
43
+ detail=f'{date_field} of {date_value} is not in the right date format',
44
+ field=self.__config.dates,
45
+ )
46
+ return
47
+
48
+ # Check if dates are in ascending order
49
+ if previous_date is not None and date_value < previous_date:
50
+ self.add_error(
51
+ object_id=obj.id,
52
+ detail=f'Date {date_field} ({date_value})'
53
+ f'is before previous date ({previous_date})',
54
+ field=self.__config.dates,
55
+ )
56
+ return
57
+
58
+ previous_date = date_value
@@ -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.3
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
@@ -32,14 +32,14 @@ tol/api_base/misc/relation_url.py,sha256=qfo-okp8Gv9-PEDghMfGZ2pHdYbHRhohvA9v3Go
32
32
  tol/api_base/misc/stats_parameters.py,sha256=IVpHqUeGQyjuih59jwqT-fIQMCBeESi2T9b4r9i4J28,1721
33
33
  tol/api_client/__init__.py,sha256=58SAywuMrIUCBAY9us_d_RLTMnaUTYWWts0LRQC5wLo,187
34
34
  tol/api_client/api_datasource.py,sha256=9CPNsujgnp__EwkZGwu9ZTmOxSwOldfLraOEQ7GBLng,14490
35
- tol/api_client/client.py,sha256=gcnX4iCZtjnCC6qylizXxLe3l6xLhME6LEJH0UeW7V4,13979
35
+ tol/api_client/client.py,sha256=hNqoyGPSiKSiT2DeHi13agrDBlXIWm7wtNjrMRCUuQg,13977
36
36
  tol/api_client/converter.py,sha256=g32fjqga4mC923n95HmQImPuawMfeb9rQcl3ZxUWP2s,4463
37
37
  tol/api_client/exception.py,sha256=MkvJaIyRVCzQ2rKOYnCOcT747mpOeQwGJJl3Kkb1BsQ,3999
38
38
  tol/api_client/factory.py,sha256=WGHA5wio4XS8OHqG07DLSVjehOeAsVoVCc5phAIq4H8,3737
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
@@ -98,7 +98,7 @@ tol/core/datasource_error.py,sha256=TqfqaPANG0gishadhA7myCmTO1Fg9u7hVZOvsY6BdAo,
98
98
  tol/core/datasource_filter.py,sha256=RY2S9kTx0XwdrFRSE2n2GohB9__fKGzFVsZrkN5hzQk,726
99
99
  tol/core/datasource_utils.py,sha256=18mwvFmtJL73_mxtFb56rKXZCGCtZFoEb8sWFKn3Yf0,6232
100
100
  tol/core/factory.py,sha256=pLLu8l-yK8QaLTt52izMhKZ2VlFHqRQlUHwMaLL6DI4,9156
101
- tol/core/http_client.py,sha256=05Wa_ySmpm_lC8OkxqXkq6jer0t693GexpaKNQBHufI,2244
101
+ tol/core/http_client.py,sha256=QyZarplEHVYIrqEfrySeHbawfbnBU4nN62TLt41x4tY,2242
102
102
  tol/core/relationship.py,sha256=etdyCjLbfi2tgkaqzE6cntpNtTzgT_jOPGeNKmPu5yc,4624
103
103
  tol/core/requested_fields.py,sha256=QFNky8QmT1Cmfn42EYPciwbCueJ0DVQNKkP0Vz-_7Jk,6715
104
104
  tol/core/session.py,sha256=6AohamIEfB8oV3Z414ishKqmlTgVfUaGYWzvxLgZgM4,3803
@@ -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
@@ -306,7 +307,7 @@ tol/sql/sql_datasource.py,sha256=jLPQSolGtKRKezjVnMrlj8YO6bLepyiFARMbDYEXuN8,162
306
307
  tol/sql/action/__init__.py,sha256=T1zAsCza_lvsNtXF1ecSLt9OFGup8tGnIs68YylBmXI,142
307
308
  tol/sql/action/factory.py,sha256=HkareJp_57ud0_Bdd9Kwz3_Rnq2l211sGJgftohFAHg,3589
308
309
  tol/sql/auth/__init__.py,sha256=e3JuwugXmXobklqZ1Mt1w03qPgb1WdUaJVM7oblzHyk,202
309
- tol/sql/auth/blueprint.py,sha256=u0vT_TC9IMKrg08QFa9W29_83mT0y0jzLj3DvXy1BBw,25906
310
+ tol/sql/auth/blueprint.py,sha256=3M55fYi_H1PgFaFxYSW8pgOwgFwdl1lN6VRbEt1r8N8,26008
310
311
  tol/sql/auth/models.py,sha256=U4CsKMMyzGMg6hj4tp_iRenr3_Q--64WJmHWvxQ2--Q,12297
311
312
  tol/sql/pipeline_step/__init__.py,sha256=O7u4RrLfuoB0mwLcPxFoUrdTBZGB_4bE1vWCn5ho-qw,147
312
313
  tol/sql/pipeline_step/factory.py,sha256=FaO61WLST4GQdAWuCIGqAvpVBvzkfBOgZWgHEZy2OXo,5483
@@ -323,31 +324,33 @@ tol/treeval/treeval_datasource.py,sha256=GzY6JwH67b5QdV-UVdCFJfgGAIuZ96J2nl53YxZ
323
324
  tol/utils/__init__.py,sha256=764-Na1OaNGUDWpMIu51ZtXG7n_nB5MccUFK6LmkWRI,138
324
325
  tol/utils/csv.py,sha256=mihww25fSn72c4h-RFeqD_pFIG6KHZP4v1_C0rx81ws,421
325
326
  tol/utils/s3.py,sha256=aoYCwJ-qcMqFrpxmViFqPa0O1jgp0phtztO3-0CSNjw,491
326
- tol/validators/__init__.py,sha256=vRuNMVhUlRPinTZQ-TBtmfIAW40vcE4dez6RI89sgoI,1295
327
+ tol/validators/__init__.py,sha256=_ETv6oGQ2bTH_6-foYFy9T5wP5OG3cl96zEjvrIS7zk,1399
327
328
  tol/validators/allowed_keys.py,sha256=RJcHBiguL84B8hjSRaXLNES21yZqaKFwJNp2Tz9zvh0,1506
328
329
  tol/validators/allowed_values.py,sha256=-Yy3Sqo1WYacGKlot_dn3M2o7Oj5MXOioJrJmrWCCxs,1536
329
330
  tol/validators/allowed_values_from_datasource.py,sha256=ICFO6FcYXDN7M2Cv1OwpyN38CdhmY7oU-njzIatA3-w,3185
330
331
  tol/validators/assert_on_condition.py,sha256=eBGgSVfIQ6e45SheM-ZDg7daXJjyZxRVS5L8AWvbXag,2027
332
+ tol/validators/branching.py,sha256=7YFjHNjrrTmy4hZ3E7JKDT6MEsBMhrc3P3p3ykv4wKI,5720
331
333
  tol/validators/converter_and_validate.py,sha256=O1uYdrU4YDZ8eZjb7Koots4-8fMVOkJFXESg-LVw2o8,2992
332
- tol/validators/ena_checklist.py,sha256=M10VAFGpaxnm7rWO4jmFhTWkYRlCmU0Ox2IUEDFGKbo,2812
334
+ tol/validators/date_sorting.py,sha256=NzYsBfhgeG4NYlYjVUYgcGGwEHns5hESqeaPvXUxjUI,1918
335
+ tol/validators/ena_checklist.py,sha256=2IfgzdgpumTPPwzoycIpfFFnkTSRlEjpZ8RQYQpBoXY,3771
333
336
  tol/validators/ena_submittable.py,sha256=CujF9t4mA4N3Wm_5rA5MRp401aW19kbioOZpfWVXg6I,1965
334
337
  tol/validators/min_one_valid_value.py,sha256=gZUHtfRA-Lvpw0d1FJoAA31cRJpLbbxAJCC9DCt5lCY,1442
335
338
  tol/validators/mutually_exclusive.py,sha256=6blZK-2IY4Eq79fHKKrm-pxsQ6B5DNH5ldtxOFVCPhU,4492
336
339
  tol/validators/regex.py,sha256=dLAi_vQt9_DsT6wQZmbYC7X5-Wp15l0leUE6XkPaItg,2602
337
340
  tol/validators/regex_by_value.py,sha256=XM5EnT4vgD17rfpR3bUE9I56IemSw26BI9MZtMakd4E,2582
338
- tol/validators/specimens_have_same_taxon.py,sha256=m2LLRIZMdhPj1fzyioDJOraI6UHXgy1l963xhezgk7E,2177
341
+ tol/validators/specimens_have_same_taxon.py,sha256=BaJcZ38ZprPcuGTIorSxxC9uGN0_lj6HS6B54EObcuY,2183
339
342
  tol/validators/sts_fields.py,sha256=aYbzy15btEg4-ocDT1qrspe7-atoWRrOJ_KmuPU6J14,8936
340
343
  tol/validators/tolid.py,sha256=yODebLYbKtlem3IpVcv8XImvq90r-AK68asH9JEawqo,3897
341
- tol/validators/types.py,sha256=KDBNqx5isJG5XI1l2V9Wmi9135ZwDace3MU6Qij3J6E,2612
344
+ tol/validators/types.py,sha256=jMVpckRp8RS93f7usf58YH_K-5rKWgZIYs7bO9dHhQc,2914
342
345
  tol/validators/unique_value_check.py,sha256=sFvDooYkKeORvULGEOTsgIcxlbe0AXDWxY3Gbr3j0KI,1282
343
346
  tol/validators/unique_values.py,sha256=o5IrfUNLEmlEp8kpInTtFnTq-FqiHSC9TItKdf-LI1o,3114
344
347
  tol/validators/unique_whole_organisms.py,sha256=RdqA1GzIf3LTdrmNGGdxv0aW2udDY2P9EaqZb40hhik,5735
345
- tol/validators/value_check.py,sha256=lxfhfL8BCIs_B838CQ5znJ6KFD7ms_fSVCS9QuVearE,1052
348
+ tol/validators/value_check.py,sha256=DdNx_B1gns01zgBg5N6Bwia46Aukw6MAteM-M37Kv1k,1122
346
349
  tol/validators/interfaces/__init__.py,sha256=jtOxnwnwqV_29xjmmMcS_kvlt-pQiWwQYJn2YRP07_w,172
347
350
  tol/validators/interfaces/condition_evaluator.py,sha256=nj8Cb8hi47OBy6OVNfeLhF-Pjwtr8MiOSymYL6hfVes,3766
348
- tol_sdk-1.8.3.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
349
- tol_sdk-1.8.3.dist-info/METADATA,sha256=xD9AOIVwrZlC5Vgb3rPRM9eT5HdJ0FmCsJDRmxfdvw8,3142
350
- tol_sdk-1.8.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
351
- tol_sdk-1.8.3.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
352
- tol_sdk-1.8.3.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
353
- tol_sdk-1.8.3.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,,