tol-sdk 1.8.4__py3-none-any.whl → 1.8.6__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/dummy/parser.py ADDED
@@ -0,0 +1,70 @@
1
+ # SPDX-FileCopyrightText: 2024 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ import typing
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any, Iterable
10
+
11
+ from dateutil.parser import parse as dateutil_parse
12
+
13
+ from ..core import DataObject
14
+
15
+ if typing.TYPE_CHECKING:
16
+ from ..core import DataSource
17
+
18
+
19
+ DummyResource = dict[str, Any]
20
+ DummyDoc = dict[str, list[DummyResource]]
21
+
22
+
23
+ class Parser(ABC):
24
+
25
+ def parse_iterable(
26
+ self,
27
+ transfers: Iterable[DummyResource]
28
+ ) -> Iterable[DataObject]:
29
+ """
30
+ Parses an `Iterable` of Dummy transfer resources
31
+ """
32
+
33
+ return (
34
+ self.parse(t) for t in transfers
35
+ )
36
+
37
+ @abstractmethod
38
+ def parse(self, transfer: DummyResource) -> DataObject:
39
+ """
40
+ Parses an individual Dummy transfer resource to a
41
+ `DataObject` instance
42
+ """
43
+
44
+
45
+ class DefaultParser(Parser):
46
+
47
+ def __init__(self, data_source_dict: dict[str, DataSource]) -> None:
48
+ self.__dict = data_source_dict
49
+
50
+ def parse(self, transfer: DummyResource) -> DataObject:
51
+ ds = self.__dict[transfer.get('type')]
52
+ return ds.data_object_factory(
53
+ transfer.get('type'),
54
+ id_=transfer.get('id'),
55
+ attributes={
56
+ k: (
57
+ dateutil_parse(v)
58
+ if k in ['date'] and v is not None
59
+ else v
60
+ )
61
+ for k, v in transfer.items()
62
+ if k not in ['id', 'type', 'category']
63
+ },
64
+ to_one={
65
+ 'category': ds.data_object_factory(
66
+ 'category',
67
+ transfer.get('category')
68
+ ) if 'category' in transfer else None
69
+ }
70
+ )
@@ -56,4 +56,8 @@ from .sts_sample_to_casm_benchling_converter import StsSampleToCasmBenchlingConv
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
58
  from .skip_null_fields_converter import SkipNullFieldsConverter # noqa F401
59
+ from .default_field_value_if_missing_converter import DefaultFieldValueIfMissingConverter # noqa F401
60
+ from .prefix_field_converter import PrefixFieldConverter # noqa F401
61
+ from .combine_fields_converter import CombineFieldsConverter # noqa F401
59
62
  from .auto_detect_manifest_type_converter import AutoDetectManifestTypeConverter # noqa F401
63
+ from .time_string_to_time import TimeStringToTimeConverter # noqa F401
@@ -0,0 +1,45 @@
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 CombineFieldsConverter(DataObjectToDataObjectOrUpdateConverter):
11
+
12
+ @dataclass(slots=True, frozen=True, kw_only=True)
13
+ class Config:
14
+ field1: str
15
+ field2: str
16
+ dest_field: str
17
+ lowercase_field1: bool
18
+
19
+ __slots__ = ('__config',)
20
+ __config: Config
21
+
22
+ def __init__(self, data_object_factory, config: Config) -> None:
23
+ super().__init__(data_object_factory)
24
+ self.__config = config
25
+
26
+ def convert(self, data_object: DataObject) -> Iterable[DataObject]:
27
+ """
28
+ Concatenates the values of two fields and stores the result in a new field.
29
+ The first field's value may be lowercased if specified in the configuration.
30
+ The destination field name is given by the configuration.
31
+ """
32
+
33
+ val1 = data_object.get_field_by_name(self.__config.field1)
34
+ val2 = data_object.get_field_by_name(self.__config.field2)
35
+ attributes = dict(data_object.attributes)
36
+
37
+ if val1 is not None and val2 is not None:
38
+ part1 = str(val1).lower() if self.__config.lowercase_field1 else str(val1)
39
+ attributes[self.__config.dest_field] = f'{part1}{val2}'
40
+
41
+ yield self._data_object_factory(
42
+ data_object.type,
43
+ data_object.id,
44
+ attributes=attributes,
45
+ )
@@ -0,0 +1,43 @@
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 DefaultFieldValueIfMissingConverter(DataObjectToDataObjectOrUpdateConverter):
11
+
12
+ @dataclass(slots=True, frozen=True, kw_only=True)
13
+ class Config:
14
+ field_name: str
15
+ default_value: str
16
+
17
+ __slots__ = ['__config']
18
+ __config: Config
19
+
20
+ def __init__(self, data_object_factory, config: Config) -> None:
21
+ super().__init__(data_object_factory)
22
+ self.__config = config
23
+
24
+ def convert(self, data_object: DataObject) -> Iterable[DataObject]:
25
+ """
26
+ Adds a default value for a configured field if missing, empty, or None
27
+ """
28
+
29
+ attributes_obj = data_object.attributes
30
+ if hasattr(attributes_obj, 'get_field_by_name'):
31
+ current_value = attributes_obj.get_field_by_name(self.__config.field_name)
32
+ else:
33
+ current_value = attributes_obj.get(self.__config.field_name)
34
+ attributes = dict(attributes_obj)
35
+ if not current_value:
36
+ attributes[self.__config.field_name] = self.__config.default_value
37
+
38
+ ret = self._data_object_factory(
39
+ data_object.type,
40
+ data_object.id,
41
+ attributes=attributes
42
+ )
43
+ yield ret
@@ -0,0 +1,49 @@
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 PrefixFieldConverter(DataObjectToDataObjectOrUpdateConverter):
11
+
12
+ @dataclass(slots=True, frozen=True, kw_only=True)
13
+ class Config:
14
+ field_name: str
15
+ prefix: str
16
+
17
+ __slots__ = ['__config']
18
+ __config: Config
19
+
20
+ def __init__(self, data_object_factory, config: Config) -> None:
21
+ super().__init__(data_object_factory)
22
+ self.__config = config
23
+ self._data_object_factory = data_object_factory
24
+
25
+ def convert(self, data_object: DataObject) -> Iterable[DataObject]:
26
+ """
27
+ Ensures the configured field value
28
+ starts with the configured prefix. If the field is None, it is
29
+ left as-is.
30
+ """
31
+
32
+ value = data_object.get_field_by_name(
33
+ self.__config.field_name
34
+ )
35
+
36
+ if value is not None:
37
+ value_str = str(value)
38
+ if not value_str.startswith(self.__config.prefix):
39
+ value = f'{self.__config.prefix}{value_str}'
40
+
41
+ ret = self._data_object_factory(
42
+ data_object.type,
43
+ data_object.id,
44
+ attributes={
45
+ **data_object.attributes,
46
+ self.__config.field_name: value
47
+ }
48
+ )
49
+ yield ret
@@ -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/sources/dummy.py ADDED
@@ -0,0 +1,17 @@
1
+ # SPDX-FileCopyrightText: 2024 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from ..core import (
6
+ core_data_object
7
+ )
8
+ from ..dummy import (
9
+ DummyDataSource,
10
+ create_dummy_datasource
11
+ )
12
+
13
+
14
+ def dummy(**kwargs) -> DummyDataSource:
15
+ dummy = create_dummy_datasource()
16
+ core_data_object(dummy)
17
+ return dummy
tol/sql/auth/blueprint.py CHANGED
@@ -8,7 +8,6 @@ from datetime import datetime, timedelta
8
8
  from typing import Any, Callable, Optional
9
9
  from urllib.parse import urlencode
10
10
 
11
- import requests
12
11
  from requests.auth import HTTPBasicAuth
13
12
 
14
13
  from .models import ModelClass, ModelTuple, create_models
@@ -228,9 +227,14 @@ class DbAuthManager(AuthManager):
228
227
  Raises:
229
228
  requests.HTTPError: If the user info request fails
230
229
  """
230
+
231
+ client = HttpClient()
232
+
233
+ session = client.get_session()
234
+
231
235
  headers = {'Authorization': f'Bearer {token}'}
232
236
 
233
- r = requests.get(self.__config.user_info_url, headers=headers)
237
+ r = session.get(self.__config.user_info_url, headers=headers)
234
238
  r.raise_for_status()
235
239
 
236
240
  json_return = r.json()
@@ -401,13 +405,16 @@ class DbAuthManager(AuthManager):
401
405
  Raises:
402
406
  requests.HTTPError: If the token request fails
403
407
  """
404
- r = requests.post(
408
+ client = HttpClient()
409
+
410
+ session = client.get_session()
411
+ r = session.post(
405
412
  self.__config.token_url,
406
- auth=self.__basic_auth(),
407
413
  data=self.__token_post_data(code),
414
+ auth=self.__basic_auth()
408
415
  )
409
- r.raise_for_status()
410
416
 
417
+ r.raise_for_status()
411
418
  return r.json()
412
419
 
413
420
  def __basic_auth(self) -> HTTPBasicAuth:
tol/sql/sql_datasource.py CHANGED
@@ -42,6 +42,7 @@ from ..core.operator import (
42
42
  Upserter,
43
43
  )
44
44
  from ..core.relationship import RelationshipConfig
45
+ from ..core.requested_fields import requested_fields_to_tree
45
46
 
46
47
  if typing.TYPE_CHECKING:
47
48
  from ..core.session import OperableSession
@@ -56,26 +57,6 @@ FilterFactory = Callable[[DataSourceFilter], DatabaseFilter]
56
57
  SorterFactory = Callable[[Optional[str]], DatabaseSorter]
57
58
 
58
59
 
59
- def requested_fields_to_tree(func):
60
- """
61
- Allows `requested_fields` keyword arguments to be supplied to methods if a
62
- `requested_tree` object has not been given.
63
- """
64
-
65
- def wrapper(self, tablename, *args, **kwargs):
66
- if 'requested_fields' in kwargs:
67
- if 'requested_tree' in kwargs:
68
- msg = 'Both requested_fields and requested_tree arguments given'
69
- raise TypeError(msg)
70
- flds = kwargs.pop('requested_fields')
71
- kwargs['requested_tree'] = ReqFieldsTree(
72
- tablename, self, requested_fields=flds
73
- )
74
- return func(self, tablename, *args, **kwargs)
75
-
76
- return wrapper
77
-
78
-
79
60
  class SqlDataSource(
80
61
  Counter,
81
62
  Cursor,
@@ -485,6 +485,8 @@ def create_standard_models(
485
485
  foreign_keys=[data_source_config_id]
486
486
  )
487
487
 
488
+ source_order: Mapped[list[str]] = mapped_column(JSONB, nullable=True)
489
+
488
490
  class DataSourceConfigSummary(base_model_class):
489
491
  __tablename__ = 'data_source_config_summary'
490
492
 
@@ -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.6
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
@@ -10,7 +10,7 @@ tol/api_base/blueprint.py,sha256=AHCvBUwvXbXtKuRv_MmQNtMrXnVEtdNVZMh-_CMsYY0,193
10
10
  tol/api_base/controller.py,sha256=5Fp2FobjXXPlongu1j7adGJKlSVufW0_jpk_wZIMRa4,26818
11
11
  tol/api_base/data_upload.py,sha256=z8Kp4BWQ6q06vT9ZwS8cgedWuGxBwZLkT6xpHk1K008,2939
12
12
  tol/api_base/pipeline_steps.py,sha256=Y3rcqQh1w4F0tCxMLGI3H8W3pbl54OWLrUYbBR44Y4A,5850
13
- tol/api_base/system.py,sha256=c47sbcNaCd3teiJLO-Zwg1cUNvfCvRO4GmKzAMNxZrw,1219
13
+ tol/api_base/system.py,sha256=NLvG1eQnIgOa5H_NYufVn87NZtpazpmITPFJqm5KMZI,1822
14
14
  tol/api_base/auth/__init__.py,sha256=LNbVtQDlzKWzLoCmxAPSIAcM6mlVMnn0Aj9HFUudseo,460
15
15
  tol/api_base/auth/asserts.py,sha256=3kHP2yW6HZxevIgeO8Rl3iKovi8WHZcguVbPSFUjawQ,2765
16
16
  tol/api_base/auth/blueprint.py,sha256=-Bv5FNnyr73NpdRlqjkq3h4J5-29BrM01bxfBPPHUV8,2209
@@ -31,15 +31,15 @@ tol/api_base/misc/list_get_parameters.py,sha256=6DtUgfKPTok3utKPOxceJdwV_ZnJ5nis
31
31
  tol/api_base/misc/relation_url.py,sha256=qfo-okp8Gv9-PEDghMfGZ2pHdYbHRhohvA9v3Govtlo,1127
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
- tol/api_client/api_datasource.py,sha256=9CPNsujgnp__EwkZGwu9ZTmOxSwOldfLraOEQ7GBLng,14490
35
- tol/api_client/client.py,sha256=hNqoyGPSiKSiT2DeHi13agrDBlXIWm7wtNjrMRCUuQg,13977
34
+ tol/api_client/api_datasource.py,sha256=AYvaOqsrTuBu8CTRSmm9Fo-VHReJAQjpZUSZc63dLXQ,15161
35
+ tol/api_client/client.py,sha256=H4X5_Jj-JmnaeAN85ghCWGtQUhYprOZqJXNmCJq_Vz0,14216
36
36
  tol/api_client/converter.py,sha256=g32fjqga4mC923n95HmQImPuawMfeb9rQcl3ZxUWP2s,4463
37
37
  tol/api_client/exception.py,sha256=MkvJaIyRVCzQ2rKOYnCOcT747mpOeQwGJJl3Kkb1BsQ,3999
38
- tol/api_client/factory.py,sha256=WGHA5wio4XS8OHqG07DLSVjehOeAsVoVCc5phAIq4H8,3737
38
+ tol/api_client/factory.py,sha256=PHRMpCJwBcgaEU_9x_9hNM__RI0udTN7Q7xzAxfVcNk,3492
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
@@ -100,7 +100,7 @@ tol/core/datasource_utils.py,sha256=18mwvFmtJL73_mxtFb56rKXZCGCtZFoEb8sWFKn3Yf0,
100
100
  tol/core/factory.py,sha256=pLLu8l-yK8QaLTt52izMhKZ2VlFHqRQlUHwMaLL6DI4,9156
101
101
  tol/core/http_client.py,sha256=QyZarplEHVYIrqEfrySeHbawfbnBU4nN62TLt41x4tY,2242
102
102
  tol/core/relationship.py,sha256=etdyCjLbfi2tgkaqzE6cntpNtTzgT_jOPGeNKmPu5yc,4624
103
- tol/core/requested_fields.py,sha256=QFNky8QmT1Cmfn42EYPciwbCueJ0DVQNKkP0Vz-_7Jk,6715
103
+ tol/core/requested_fields.py,sha256=-UXBnalzK1Fd-6zaYbZxlvlQOqIndOW0Zjvr4Z5UgaQ,7397
104
104
  tol/core/session.py,sha256=6AohamIEfB8oV3Z414ishKqmlTgVfUaGYWzvxLgZgM4,3803
105
105
  tol/core/validate.py,sha256=kFRPhgYyeZisGbSXR7S1pmD8jvz_aZ7RX40bT-gD_iA,4083
106
106
  tol/core/operator/__init__.py,sha256=ircyLdj45IlaL5k0cDzWHjEr-_66BAx7YRbuEvWF30k,1826
@@ -124,6 +124,12 @@ tol/core/operator/statter.py,sha256=qCyJ6AOeyvDy5t0tQEJr6-p-5Rw6vVwlQ-PXTmHXn8c,
124
124
  tol/core/operator/summariser.py,sha256=l7kmcvI_s9TkLGQf9XModrMziqvxKH6jdyo-mROUUxU,5823
125
125
  tol/core/operator/updater.py,sha256=uLx02Mw77c6sHCw76pdJIArlGbk6N7HDv4FUf0C542w,1055
126
126
  tol/core/operator/upserter.py,sha256=0sAx13I-_YiXnzGC7t8N23BZFiil9FxT9S0WJ8Z6EKk,2603
127
+ tol/dummy/__init__.py,sha256=A7mBVh-j6rmSZ_Chk3FodUCSb42vreeP4pG0GKz0ZCM,193
128
+ tol/dummy/client.py,sha256=tU5Oj-XWuvo2G6JYUyHvC6Oc5TZ--tbWsMf8-DA0pss,2516
129
+ tol/dummy/converter.py,sha256=WU4y3VyYDhJLERqBqROKmEK0glwWlGPeF9XoDYtQY50,1068
130
+ tol/dummy/dummy_datasource.py,sha256=wgbO2qycdApAARL3y_9WsiUftyMCxoVuQktNzBAjAp8,2862
131
+ tol/dummy/factory.py,sha256=Jf5Oo5D_FlQMthIMLEF2kROxpJIoXiB7qG8DsCJdGn8,2105
132
+ tol/dummy/parser.py,sha256=xQRTLRdMdwolkQ1KXkS5RxTDiKwrLWwLd3lpXIAGCew,1793
127
133
  tol/elastic/__init__.py,sha256=5zOZ83jkup26AHrTEhLw4Pve548uN3gnzC6fe_mtm-Q,166
128
134
  tol/elastic/elastic_datasource.py,sha256=XPMr5sn64qPMe9H6Rf6xd-6TgMagVJzaZ2J1wZfRvwM,42041
129
135
  tol/elastic/runtime_fields.py,sha256=08N8JnbF1U2znSK9mpuCoyQjxiCShuxsggljptfCVoE,4479
@@ -149,7 +155,7 @@ tol/flows/__init__.py,sha256=M7iSvnBJs6fJ8M38cW0bYQa9WW0TN8FHAMjIHPDNAJ4,166
149
155
  tol/flows/logger.py,sha256=rWXbaknGcPEZRFvC1CiB1qkhFRZsQk435w7VyJ3cpyw,170
150
156
  tol/flows/secrets.py,sha256=1mlbsxaahzYRfVAx3XdztHOmUCtDMSJDzHysdbaCtj0,352
151
157
  tol/flows/sequencing_submissions.py,sha256=ukz_y5be-BCBN2y3JPQ2EK6b3jwOCh-187j-jnw3EUY,11027
152
- tol/flows/converters/__init__.py,sha256=QgbwWIbMKI8LWZBwYVrHARKi03ISLDS3yzpHNPRLXdk,6324
158
+ tol/flows/converters/__init__.py,sha256=m2q04EXBdG5SZ3ieMsm_ork9eUuj-g8z6ulMOlVHs4U,6641
153
159
  tol/flows/converters/auto_detect_manifest_type_converter.py,sha256=uHakTmVHMbm2kFQOWaAv8ynD9ueh7p-kq6wmfEGnmEw,1361
154
160
  tol/flows/converters/benchling_entity_to_benchling_worklist_item_converter_factory.py,sha256=PN27fcvN4JLBnLrtPPAot1cWjAwPQHVcIDoMfPDeKzU,1210
155
161
  tol/flows/converters/benchling_extraction_to_elastic_extraction_converter.py,sha256=S8pbmIeKlcXrLPRJHYBUGP0-Q7jTOV2QQk2TeA2naWo,1966
@@ -165,6 +171,8 @@ tol/flows/converters/bioscan_qc_specimen_to_elastic_sample_update_converter.py,s
165
171
  tol/flows/converters/bioscan_qc_uksi_entry_to_elastic_sample_update_converter.py,sha256=F_81d9XweccUWPppIzuq1rHVEaIctwoFBmWf9g9jWtw,650
166
172
  tol/flows/converters/bold_bin_to_elastic_sample_update_converter.py,sha256=viQNm-3PGCTf8zUHax3H1PpZSfW2P9HCocVL8h9O74o,579
167
173
  tol/flows/converters/bold_sample_to_elastic_sample_update_converter.py,sha256=eehTzW37x4IINKtMdXDrgSk-Iptx1k8_IR1WDNX1JtA,585
174
+ tol/flows/converters/combine_fields_converter.py,sha256=S_Bm4dix18kgmfPF6TEUTp05Wv53hJPR2MjtGjn8BrI,1528
175
+ tol/flows/converters/default_field_value_if_missing_converter.py,sha256=81xKskti_cpAEIs7A1arG9nlaIvrl1TkQj6URvFI3o0,1397
168
176
  tol/flows/converters/elastic_object_to_portaldb_object_converter.py,sha256=YVUTYMvsLwDfq33WQbyIDl2d3D5jEcMxhqnGhx3HtQ4,1600
169
177
  tol/flows/converters/elastic_sample_to_benchling_tissue_converter.py,sha256=zsybAg8ZyRI_ibTpkvSy7Zn2lNzUYmhc9SNMQQFj1HE,1103
170
178
  tol/flows/converters/elastic_sample_to_benchling_tissue_update_converter.py,sha256=2Y_Kn8Z5_Sksgah9p4Q3Gy4Em3KOAG-rnzmy4_jtod0,3726
@@ -190,6 +198,7 @@ tol/flows/converters/labwhere_location_to_sts_tray_converter.py,sha256=dSBP5Hfdv
190
198
  tol/flows/converters/mlwh_extraction_to_elastic_extraction_converter.py,sha256=047eU_3Zb3ZlJ8utrk3o_VW7jifv5VO0WxgmFg-QkaE,1195
191
199
  tol/flows/converters/mlwh_run_data_to_elastic_run_data_converter.py,sha256=f0I_N8DH_9Kom4Fpl_O7hKTUPPiURWeyhxWN3XFLmLk,1313
192
200
  tol/flows/converters/mlwh_sequencing_request_to_elastic_sequencing_request_converter.py,sha256=zXYkq37qsvHEY7O1zh2vOoMBdt8hE7T1SuFIOR2DQrM,1209
201
+ tol/flows/converters/prefix_field_converter.py,sha256=MHYvreOygcEOZdqF067MrDmWQWiLcp_c0w5-juK4oY4,1435
193
202
  tol/flows/converters/skip_null_fields_converter.py,sha256=IlLrX31XWpQ58indW-hk9eApMunu2HGFhMConYDmxtU,1226
194
203
  tol/flows/converters/sts_banked_sample_to_elastic_sample_converter.py,sha256=HK_3Q60E6CKCT8YSWbZXu0Wqs2mmBLYhcD0OEwXM4Qs,688
195
204
  tol/flows/converters/sts_manifest_to_elastic_manifest_converter.py,sha256=Ko3ulOFXXYaZB1YUiwu_HPlP5oPZeI65hViAZ05cioQ,1806
@@ -198,6 +207,7 @@ tol/flows/converters/sts_sample_project_to_elastic_sample_converter.py,sha256=YE
198
207
  tol/flows/converters/sts_sample_to_casm_benchling_converter.py,sha256=Zo577u2v5_Fela2uQVtZsGZmHq4bLecTCC4Ewvq61Xo,39414
199
208
  tol/flows/converters/sts_sampleset_to_elastic_sampleset_converter.py,sha256=PUP0Qjy9wTmqp5GHNEd9fukqtWdoMvDfs4rJisfLzcc,3197
200
209
  tol/flows/converters/sts_species_to_elastic_species_converter.py,sha256=ELZ_ML8vPlLkfXrx0B_wxUWiPyxkI8UXgSPQZCKknXU,1164
210
+ tol/flows/converters/time_string_to_time.py,sha256=myo9K9pRMT6GtT5vXf3c0UDK5n8bMVbvW7ewhSPkecU,1062
201
211
  tol/flows/converters/tolid_specimen_to_elastic_tolid_converter.py,sha256=4Ird6ATYjsjSFZ1AGsxpuWdQ6QtQTvKJ-rF-_TCC_rg,1072
202
212
  tol/flows/converters/tolqc_data_to_elastic_run_data_converter.py,sha256=f9PYnsswikskvXpnSlrfYN7wXfyn8iXQpg996ZsQHbw,3799
203
213
  tol/flows/converters/tolqc_sample_to_elastic_sequencing_request_converter.py,sha256=MzPcO75Z_3-6nsWC8X0kmmmxJTurV_HOZhseC-tWdFo,1397
@@ -267,6 +277,7 @@ tol/sources/bioscan_qc.py,sha256=XpU7gufFOcjtUEgW65nNGATrpt6utWhON-kCogDhEaM,171
267
277
  tol/sources/bold.py,sha256=vbJGRaO-xJj7-mNcaUEPC_YTNTlb1_442YP3sGvCbu4,674
268
278
  tol/sources/copo.py,sha256=L03fFT3x1xWe3IpbijoVnKDGlncVQPmKhuGcAjdAuYg,485
269
279
  tol/sources/defaults.py,sha256=wnxLSqZzxgQYwSJCshzfjJ0gzw7Au7Ag75aC3C9IzeA,1463
280
+ tol/sources/dummy.py,sha256=p2eZWpsKd2bv_uZW0sBGCgpr0Sye_35g1VEeWOlNZlc,328
270
281
  tol/sources/elastic.py,sha256=LNl0MKT8jZta2aCB9Jjl4lJkC9b8Qd4TjgmyqLgQxWw,1058
271
282
  tol/sources/ena.py,sha256=BnCq_tGAOQ3Ij0afcArjFaMjYG1MqGRJUrGZhnpvxYY,615
272
283
  tol/sources/gap.py,sha256=h3dwi-Y19kT6vRsU-GHtZBgD-piaHR4r4wqU7qBIGww,1544
@@ -302,16 +313,16 @@ tol/sql/relationship.py,sha256=EClMgVx_If5nZV9MV99TQk7Wr7uACWetwQdWAliM5XI,2891
302
313
  tol/sql/session.py,sha256=VmqTegr4L2X2zvaOJCpwSrkVRx8fc1RVL0drkL2MXu8,806
303
314
  tol/sql/sort.py,sha256=ENrjHGgj4fZtXKmkdlkv1HRi1X14SVlcl-tp8Pu7G0k,2553
304
315
  tol/sql/sql_converter.py,sha256=taD5FRwadvw2bBaUGrCIiUs0-ATAbBnRYI1M7xe3yEc,4618
305
- tol/sql/sql_datasource.py,sha256=jLPQSolGtKRKezjVnMrlj8YO6bLepyiFARMbDYEXuN8,16219
316
+ tol/sql/sql_datasource.py,sha256=RnPkfg4TSHaxfJG77eBrNgBAKexJaypc-fvfFzssdac,15598
306
317
  tol/sql/action/__init__.py,sha256=T1zAsCza_lvsNtXF1ecSLt9OFGup8tGnIs68YylBmXI,142
307
318
  tol/sql/action/factory.py,sha256=HkareJp_57ud0_Bdd9Kwz3_Rnq2l211sGJgftohFAHg,3589
308
319
  tol/sql/auth/__init__.py,sha256=e3JuwugXmXobklqZ1Mt1w03qPgb1WdUaJVM7oblzHyk,202
309
- tol/sql/auth/blueprint.py,sha256=3M55fYi_H1PgFaFxYSW8pgOwgFwdl1lN6VRbEt1r8N8,26008
320
+ tol/sql/auth/blueprint.py,sha256=1B4RqYKxOfoBhrNY6qrsPO1kHSfaUfKyoAzjPSCZGs4,26131
310
321
  tol/sql/auth/models.py,sha256=U4CsKMMyzGMg6hj4tp_iRenr3_Q--64WJmHWvxQ2--Q,12297
311
322
  tol/sql/pipeline_step/__init__.py,sha256=O7u4RrLfuoB0mwLcPxFoUrdTBZGB_4bE1vWCn5ho-qw,147
312
323
  tol/sql/pipeline_step/factory.py,sha256=FaO61WLST4GQdAWuCIGqAvpVBvzkfBOgZWgHEZy2OXo,5483
313
324
  tol/sql/standard/__init__.py,sha256=2NbLXFk0rneGZosZ2ESIRcT0WMK0KncmPWaLPqvX-i4,142
314
- tol/sql/standard/factory.py,sha256=yY8iWmZRMvUqphwnzBeOIQtKGgxsU6AcA7YTz53UYvc,20010
325
+ tol/sql/standard/factory.py,sha256=zCCbPBpPwoTNKacqSjWXFY8ROGzHx8IaL7TsrGUeelM,20089
315
326
  tol/status/__init__.py,sha256=sBo-j1wCmberl89uryVCBEJk8ohbfsYhaNpIp_brR9Y,146
316
327
  tol/status/status_datasource.py,sha256=UYU2vB561XRWY8y2dY96qHiWXy15xaHxsbGCVCIUnqs,1916
317
328
  tol/sts/__init__.py,sha256=1nb_lBWDwxJo3hutxSid2rqMIpfZ4GHxDS6cfj-FKv4,187
@@ -331,25 +342,25 @@ tol/validators/assert_on_condition.py,sha256=eBGgSVfIQ6e45SheM-ZDg7daXJjyZxRVS5L
331
342
  tol/validators/branching.py,sha256=7YFjHNjrrTmy4hZ3E7JKDT6MEsBMhrc3P3p3ykv4wKI,5720
332
343
  tol/validators/converter_and_validate.py,sha256=O1uYdrU4YDZ8eZjb7Koots4-8fMVOkJFXESg-LVw2o8,2992
333
344
  tol/validators/date_sorting.py,sha256=NzYsBfhgeG4NYlYjVUYgcGGwEHns5hESqeaPvXUxjUI,1918
334
- tol/validators/ena_checklist.py,sha256=M10VAFGpaxnm7rWO4jmFhTWkYRlCmU0Ox2IUEDFGKbo,2812
345
+ tol/validators/ena_checklist.py,sha256=2IfgzdgpumTPPwzoycIpfFFnkTSRlEjpZ8RQYQpBoXY,3771
335
346
  tol/validators/ena_submittable.py,sha256=CujF9t4mA4N3Wm_5rA5MRp401aW19kbioOZpfWVXg6I,1965
336
347
  tol/validators/min_one_valid_value.py,sha256=gZUHtfRA-Lvpw0d1FJoAA31cRJpLbbxAJCC9DCt5lCY,1442
337
348
  tol/validators/mutually_exclusive.py,sha256=6blZK-2IY4Eq79fHKKrm-pxsQ6B5DNH5ldtxOFVCPhU,4492
338
349
  tol/validators/regex.py,sha256=dLAi_vQt9_DsT6wQZmbYC7X5-Wp15l0leUE6XkPaItg,2602
339
350
  tol/validators/regex_by_value.py,sha256=XM5EnT4vgD17rfpR3bUE9I56IemSw26BI9MZtMakd4E,2582
340
- tol/validators/specimens_have_same_taxon.py,sha256=m2LLRIZMdhPj1fzyioDJOraI6UHXgy1l963xhezgk7E,2177
351
+ tol/validators/specimens_have_same_taxon.py,sha256=BaJcZ38ZprPcuGTIorSxxC9uGN0_lj6HS6B54EObcuY,2183
341
352
  tol/validators/sts_fields.py,sha256=aYbzy15btEg4-ocDT1qrspe7-atoWRrOJ_KmuPU6J14,8936
342
353
  tol/validators/tolid.py,sha256=yODebLYbKtlem3IpVcv8XImvq90r-AK68asH9JEawqo,3897
343
- tol/validators/types.py,sha256=KDBNqx5isJG5XI1l2V9Wmi9135ZwDace3MU6Qij3J6E,2612
354
+ tol/validators/types.py,sha256=jMVpckRp8RS93f7usf58YH_K-5rKWgZIYs7bO9dHhQc,2914
344
355
  tol/validators/unique_value_check.py,sha256=sFvDooYkKeORvULGEOTsgIcxlbe0AXDWxY3Gbr3j0KI,1282
345
356
  tol/validators/unique_values.py,sha256=o5IrfUNLEmlEp8kpInTtFnTq-FqiHSC9TItKdf-LI1o,3114
346
357
  tol/validators/unique_whole_organisms.py,sha256=RdqA1GzIf3LTdrmNGGdxv0aW2udDY2P9EaqZb40hhik,5735
347
- tol/validators/value_check.py,sha256=lxfhfL8BCIs_B838CQ5znJ6KFD7ms_fSVCS9QuVearE,1052
358
+ tol/validators/value_check.py,sha256=DdNx_B1gns01zgBg5N6Bwia46Aukw6MAteM-M37Kv1k,1122
348
359
  tol/validators/interfaces/__init__.py,sha256=jtOxnwnwqV_29xjmmMcS_kvlt-pQiWwQYJn2YRP07_w,172
349
360
  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,,
361
+ tol_sdk-1.8.6.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
362
+ tol_sdk-1.8.6.dist-info/METADATA,sha256=drjwvfHHt_dIUvdywaSjXVuTa0wNCbdMHsLPuONx92o,3142
363
+ tol_sdk-1.8.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
364
+ tol_sdk-1.8.6.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
365
+ tol_sdk-1.8.6.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
366
+ tol_sdk-1.8.6.dist-info/RECORD,,