tol-sdk 1.6.36__py3-none-any.whl → 1.7.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.
Files changed (47) hide show
  1. tol/{sql/loader → actions}/__init__.py +2 -2
  2. tol/actions/action.py +31 -0
  3. tol/actions/upsert_action.py +56 -0
  4. tol/api_base/action.py +70 -21
  5. tol/api_base/blueprint.py +29 -6
  6. tol/api_base/controller.py +14 -5
  7. tol/api_client/api_datasource.py +15 -7
  8. tol/api_client/client.py +12 -6
  9. tol/api_client/converter.py +22 -8
  10. tol/api_client/factory.py +5 -3
  11. tol/api_client/view.py +75 -205
  12. tol/cli/cli.py +1 -1
  13. tol/core/__init__.py +1 -0
  14. tol/core/http_client.py +4 -2
  15. tol/core/operator/cursor.py +5 -3
  16. tol/core/operator/detail_getter.py +7 -15
  17. tol/core/operator/list_getter.py +3 -1
  18. tol/core/operator/page_getter.py +3 -1
  19. tol/core/operator/relational.py +9 -4
  20. tol/core/requested_fields.py +189 -0
  21. tol/elastic/elastic_datasource.py +2 -1
  22. tol/flows/converters/benchling_extraction_to_elastic_extraction_converter.py +25 -6
  23. tol/flows/converters/benchling_extraction_to_elastic_sequencing_request_converter.py +28 -7
  24. tol/flows/converters/benchling_sequencing_request_to_elastic_sequencing_request_converter.py +30 -9
  25. tol/flows/converters/benchling_tissue_prep_to_elastic_tissue_prep_converter.py +14 -3
  26. tol/flows/converters/elastic_sample_to_benchling_tissue_update_converter.py +1 -1
  27. tol/flows/converters/elastic_sample_to_elastic_sequencing_request_update_converter.py +4 -1
  28. tol/flows/converters/elastic_tolid_to_elastic_genome_note_update_converter.py +4 -1
  29. tol/flows/converters/elastic_tolid_to_elastic_sample_update_converter.py +4 -1
  30. tol/sources/sts.py +6 -2
  31. tol/sql/database.py +80 -44
  32. tol/sql/factory.py +2 -2
  33. tol/sql/filter.py +22 -20
  34. tol/sql/model.py +43 -38
  35. tol/sql/relationship.py +1 -1
  36. tol/sql/sql_converter.py +49 -142
  37. tol/sql/sql_datasource.py +80 -172
  38. tol/sql/{board → standard}/__init__.py +1 -1
  39. tol/sql/standard/factory.py +549 -0
  40. {tol_sdk-1.6.36.dist-info → tol_sdk-1.7.0.dist-info}/METADATA +1 -1
  41. {tol_sdk-1.6.36.dist-info → tol_sdk-1.7.0.dist-info}/RECORD +45 -43
  42. tol/sql/board/factory.py +0 -341
  43. tol/sql/loader/factory.py +0 -246
  44. {tol_sdk-1.6.36.dist-info → tol_sdk-1.7.0.dist-info}/WHEEL +0 -0
  45. {tol_sdk-1.6.36.dist-info → tol_sdk-1.7.0.dist-info}/entry_points.txt +0 -0
  46. {tol_sdk-1.6.36.dist-info → tol_sdk-1.7.0.dist-info}/licenses/LICENSE +0 -0
  47. {tol_sdk-1.6.36.dist-info → tol_sdk-1.7.0.dist-info}/top_level.txt +0 -0
@@ -2,5 +2,5 @@
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
4
 
5
-
6
- from .factory import create_loader_models # noqa F401
5
+ from .action import Action # noqa
6
+ from .upsert_action import UpsertAction # noqa
tol/actions/action.py ADDED
@@ -0,0 +1,31 @@
1
+ # SPDX-FileCopyrightText: 2025 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any
9
+
10
+ from ..core import DataSource
11
+
12
+
13
+ class Action(ABC):
14
+ """
15
+ The central class for running local actions.
16
+ """
17
+
18
+ @abstractmethod
19
+ def run(
20
+ self,
21
+ datasource: DataSource,
22
+ ids: list[str],
23
+ object_type: str,
24
+ params: dict[str, Any] | None = None
25
+ ) -> tuple[dict[str, bool], int]:
26
+ """
27
+ Run the action on the given IDs and return status in
28
+ format ({'success': True}, 200).
29
+ """
30
+
31
+ pass
@@ -0,0 +1,56 @@
1
+ # SPDX-FileCopyrightText: 2025 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any
8
+
9
+ from .action import Action
10
+ from ..core import DataSource
11
+
12
+
13
+ class UpsertAction(Action):
14
+ """
15
+ The central class for running local actions.
16
+ """
17
+
18
+ def __init__(self):
19
+ super().__init__()
20
+
21
+ def run(
22
+ self,
23
+ datasource: DataSource,
24
+ ids: list[str],
25
+ object_type: str,
26
+ params: dict[str, Any] | None = None
27
+ ) -> tuple[dict[str, bool], int]:
28
+
29
+ data_objects = self.__convert_to_data_objects(
30
+ datasource=datasource,
31
+ ids=ids,
32
+ object_type=object_type,
33
+ params=params
34
+ )
35
+
36
+ try:
37
+ datasource.upsert_batch(object_type=object_type, objects=data_objects)
38
+ return {'success': True}, 200
39
+ except Exception as e:
40
+ return {'error': str(e)}, 500
41
+
42
+ def __convert_to_data_objects(
43
+ self,
44
+ datasource: DataSource,
45
+ ids: list[str],
46
+ object_type: str,
47
+ params: dict[str, Any] | None = None
48
+ ) -> Any:
49
+ CoreDataObject = datasource.data_object_factory # noqa N806
50
+
51
+ for id_ in ids:
52
+ yield CoreDataObject(
53
+ type_=object_type,
54
+ id_=id_,
55
+ attributes=params
56
+ )
tol/api_base/action.py CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import importlib
7
8
  import typing
8
9
  from datetime import datetime
9
10
  from typing import Any
@@ -151,31 +152,79 @@ def action_blueprint(
151
152
  else {}
152
153
  )
153
154
 
154
- flow_params = {
155
- 'extra_params': {
155
+ if action.flow_name:
156
+ flow_params = {
157
+ 'extra_params': {
158
+ **params,
159
+ **action_params,
160
+ },
161
+ 'user_id': user_id,
162
+ 'object_type': object_type,
163
+ 'ids': ids
164
+ }
165
+
166
+ flow_run_id, flow_run_name = __insert_flow_run(
167
+ action,
168
+ flow_params,
169
+ user_id
170
+ )
171
+
172
+ user_action_params = {
156
173
  **params,
157
174
  **action_params,
158
- },
159
- 'user_id': user_id,
160
- 'object_type': object_type,
161
- 'ids': ids
162
- }
163
-
164
- flow_run_id, flow_run_name = __insert_flow_run(
165
- action,
166
- flow_params,
167
- user_id
168
- )
175
+ 'ids': ids,
176
+ 'flow_run_id': flow_run_id,
177
+ 'flow_run_name': flow_run_name
178
+ }
169
179
 
170
- user = sql_ds.get_one('user', user_id)
180
+ elif action.class_name:
181
+ # Try to import the class from tol.actions first, then fall back to main.actions
182
+ action_class = None
183
+ try:
184
+ tol_actions_module = importlib.import_module('tol.actions')
185
+ if hasattr(tol_actions_module, action.class_name):
186
+ action_class = getattr(tol_actions_module, action.class_name)
187
+
188
+ if action_class is None:
189
+ main_actions_module = importlib.import_module('main.actions')
190
+ if hasattr(main_actions_module, action.class_name):
191
+ action_class = getattr(main_actions_module, action.class_name)
192
+
193
+ except ImportError:
194
+ raise DataSourceError(
195
+ 'Action Class Import Error',
196
+ 'Class not found in tol.actions or main.actions',
197
+ 500
198
+ )
199
+
200
+ if action_class is None:
201
+ raise DataSourceError(
202
+ 'Action Class Not Found',
203
+ f'Action class "{action.class_name}" not found in tol.actions or main.actions',
204
+ 404
205
+ )
206
+
207
+ class_params = {**action_params, **params}
208
+
209
+ action_instance = action_class()
210
+ status = action_instance.run(ids=ids, params=class_params,
211
+ object_type=object_type, datasource=sql_ds)
212
+
213
+ user_action_params = {
214
+ **params,
215
+ **action_params,
216
+ 'ids': ids,
217
+ 'status': status
218
+ }
171
219
 
172
- user_action_params = {
173
- **params,
174
- **action_params,
175
- 'ids': ids,
176
- 'flow_run_id': flow_run_id,
177
- 'flow_run_name': flow_run_name
178
- }
220
+ else:
221
+ raise DataSourceError(
222
+ 'Invalid Action',
223
+ 'No Actions are defined',
224
+ 400
225
+ )
226
+
227
+ user = sql_ds.get_one('user', user_id)
179
228
 
180
229
  user_action = sql_ds.data_object_factory(
181
230
  'user_action',
tol/api_base/blueprint.py CHANGED
@@ -35,6 +35,7 @@ from ..core import DataSource, DataSourceError, OperableDataSource
35
35
  from ..core.data_source_dict import DataSourceDict
36
36
  from ..core.operator import Relational
37
37
  from ..core.operator.operator_config import DefaultOperatorConfig, OperatorConfig
38
+ from ..core.requested_fields import ReqFieldsTree
38
39
 
39
40
 
40
41
  class DataBlueprint(Blueprint):
@@ -179,6 +180,7 @@ def _core_blueprint(
179
180
  data_source_dict: dict[str, DataSource],
180
181
  url_prefix: str,
181
182
  auth_inspector: Optional[AuthInspector] = None,
183
+ include_all_to_ones: bool = True,
182
184
  ) -> DataBlueprint:
183
185
  """
184
186
  Create the core blueprint responsible for managing DataSource endpoints.
@@ -193,6 +195,8 @@ def _core_blueprint(
193
195
  url_prefix (str): URL prefix for all data endpoints.
194
196
  auth_inspector (Optional[AuthInspector], optional): Authentication
195
197
  inspector for request authorisation.
198
+ include_all_to_ones (bool): Whether to fetch or store all to-one related objects
199
+ when fetching or serialising DataObjects.
196
200
 
197
201
  Returns:
198
202
  DataBlueprint: A configured blueprint with all data endpoints and error handlers.
@@ -231,18 +235,30 @@ def _core_blueprint(
231
235
  hop_limit = None if requested_fields else 1
232
236
 
233
237
  data_source = data_source_dict[object_type]
238
+
239
+ # Build a ReqFieldsTree template for the request
240
+ req_fields_tree = ReqFieldsTree(
241
+ object_type,
242
+ data_source,
243
+ requested_fields,
244
+ include_all_to_ones=include_all_to_ones,
245
+ )
246
+
234
247
  view = DefaultView(
248
+ requested_tree=req_fields_tree,
235
249
  prefix=url_prefix,
236
- include_all_to_ones=True,
237
250
  hop_limit=hop_limit,
238
- requested_fields=requested_fields,
239
251
  )
240
- return Controller(data_source, view, auth_inspector=auth_inspector)
252
+ return Controller(data_source, view, req_fields_tree, auth_inspector=auth_inspector)
241
253
 
242
254
  @data_handler.route('/<object_type>/<path:object_id>', methods=['GET']) # Allow slashes
243
255
  def get_detail(*, object_type: str, object_id: str):
244
256
  """Get details of a specific object by ID."""
245
- controller = __new_controller(object_type)
257
+ request_args = ListGetParameters(request.args)
258
+ controller = __new_controller(
259
+ object_type,
260
+ requested_fields=request_args.requested_fields,
261
+ )
246
262
  object_id_unencoded = urllib.parse.unquote(object_id)
247
263
  return controller.get_detail(object_type, object_id_unencoded)
248
264
 
@@ -325,8 +341,11 @@ def _core_blueprint(
325
341
  @data_handler.post('/<object_type>:cursor')
326
342
  def get_cursor_page(*, object_type: str):
327
343
  """Get a page of results using cursor-based pagination."""
328
- controller = __new_controller(object_type)
329
344
  request_args = ListGetParameters(request.args)
345
+ controller = __new_controller(
346
+ object_type,
347
+ requested_fields=request_args.requested_fields,
348
+ )
330
349
  search_after = request.json.get('search_after')
331
350
  return controller.get_cursor_page(object_type, request_args, search_after)
332
351
 
@@ -369,6 +388,7 @@ def data_blueprint(
369
388
  url_prefix: str = '/data',
370
389
  config_prefix: str = '/_config',
371
390
  auth_inspector: Optional[AuthInspector] = None,
391
+ include_all_to_ones: bool = True,
372
392
  ) -> DataBlueprint:
373
393
  """
374
394
  Create a complete data blueprint with both core and configuration endpoints.
@@ -405,7 +425,10 @@ def data_blueprint(
405
425
  config_prefix, data_sources, DefaultOperatorConfig(*data_sources)
406
426
  )
407
427
  core_bp = _core_blueprint(
408
- DataSourceDict(*data_sources), url_prefix, auth_inspector=auth_inspector
428
+ DataSourceDict(*data_sources),
429
+ url_prefix,
430
+ auth_inspector=auth_inspector,
431
+ include_all_to_ones=include_all_to_ones,
409
432
  )
410
433
  core_bp.register_blueprint(config_bp)
411
434
 
@@ -31,7 +31,7 @@ from ..api_client.exception import (
31
31
  UnsupportedOperationError,
32
32
  )
33
33
  from ..api_client.view import ResponseDict, View
34
- from ..core import DataObject, OperableDataSource
34
+ from ..core import DataObject, OperableDataSource, ReqFieldsTree
35
35
  from ..core.datasource_filter import AndFilter, DataSourceFilter
36
36
  from ..core.operator import (
37
37
  Aggregator,
@@ -160,6 +160,7 @@ class Controller:
160
160
  self,
161
161
  data_source: OperableDataSource,
162
162
  view: View,
163
+ requested_tree: ReqFieldsTree | None = None,
163
164
  auth_inspector: Optional[AuthInspector] = None,
164
165
  ) -> None:
165
166
  """
@@ -173,6 +174,7 @@ class Controller:
173
174
  """
174
175
  self.__data_source = data_source
175
176
  self.__view = view
177
+ self.__requested_tree = requested_tree
176
178
  self.__inspector = auth_inspector
177
179
 
178
180
  @property
@@ -263,13 +265,13 @@ class Controller:
263
265
  page_size=query_args.page_size,
264
266
  object_filters=self.__combine_filters(query_args.filter, ext_and),
265
267
  sort_by=query_args.sort_by,
266
- requested_fields=query_args.requested_fields,
268
+ requested_tree=self.__requested_tree,
267
269
  )
268
- document_meta = {
270
+ meta = {
269
271
  'total': total,
270
272
  'types': self.__data_source.get_attribute_types(object_type),
271
273
  }
272
- return self.__view.dump_bulk(data_objects, document_meta=document_meta)
274
+ return self.__view.dump_bulk(data_objects, document_meta=meta)
273
275
 
274
276
  @validate(Counter, 'get_count', OperatorMethod.COUNT)
275
277
  def get_count(
@@ -545,6 +547,7 @@ class Controller:
545
547
  query_args.page_size,
546
548
  self.__combine_filters(query_args.filter, ext_and),
547
549
  search_after,
550
+ requested_tree=self.__requested_tree,
548
551
  )
549
552
  meta = {'search_after': new_search_after}
550
553
 
@@ -657,7 +660,13 @@ class Controller:
657
660
  Raises:
658
661
  ObjectNotFoundByIdException: If no object with the given ID exists.
659
662
  """
660
- data_objects = list(self.__data_source.get_by_id(object_type, [object_id]))
663
+ data_objects = list(
664
+ self.__data_source.get_by_id(
665
+ object_type,
666
+ [object_id],
667
+ requested_tree=self.__requested_tree,
668
+ )
669
+ )
661
670
  if len(data_objects) == 0 or data_objects[0] is None:
662
671
  raise ObjectNotFoundByIdException(object_type, object_id)
663
672
  return data_objects[0]
@@ -144,7 +144,11 @@ class ApiDataSource(
144
144
 
145
145
  client = self.__client_factory()
146
146
  json_responses = (
147
- client.get_detail(object_type, id_)
147
+ client.get_detail(
148
+ object_type,
149
+ id_,
150
+ requested_fields=requested_fields,
151
+ )
148
152
  for id_ in object_ids
149
153
  )
150
154
  json_converter = self.__jc_factory()
@@ -184,16 +188,17 @@ class ApiDataSource(
184
188
  session: Optional[OperableSession] = None,
185
189
  requested_fields: list[str] | None = None,
186
190
  ) -> Iterable[DataObject]:
187
-
188
191
  if self.__can_cursor(object_type, object_filters):
189
192
  return self._get_list_by_cursor(
190
193
  object_type,
191
- object_filters
194
+ object_filters,
195
+ requested_fields=requested_fields,
192
196
  )
193
197
  else:
194
198
  return self.__get_list_regular(
195
199
  object_type,
196
- object_filters
200
+ object_filters,
201
+ requested_fields=requested_fields,
197
202
  )
198
203
 
199
204
  @validate('count')
@@ -266,7 +271,8 @@ class ApiDataSource(
266
271
  object_type,
267
272
  page_size,
268
273
  search_after,
269
- filter_string=filter_string
274
+ filter_string=filter_string,
275
+ requested_fields=requested_fields,
270
276
  )
271
277
  return self.__jc_factory().convert_cursor_page(transfer)
272
278
 
@@ -429,7 +435,8 @@ class ApiDataSource(
429
435
  def __get_list_regular(
430
436
  self,
431
437
  object_type: str,
432
- object_filters: Optional[DataSourceFilter]
438
+ object_filters: Optional[DataSourceFilter],
439
+ requested_fields: list[str] | None = None,
433
440
  ) -> Iterable[DataObject]:
434
441
 
435
442
  page = 1
@@ -443,7 +450,8 @@ class ApiDataSource(
443
450
  object_type,
444
451
  page,
445
452
  page_size,
446
- filter_string=filter_string
453
+ filter_string=filter_string,
454
+ requested_fields=requested_fields,
447
455
  )
448
456
  (results_page, _) = jc_converter.convert_list(transfer)
449
457
 
tol/api_client/client.py CHANGED
@@ -28,9 +28,13 @@ class JsonApiClient(HttpClient):
28
28
  config_prefix: str = '/_config',
29
29
  token_header: str = 'token',
30
30
  retries: int = 5,
31
+ status_forcelist: Optional[list[int]] = None,
31
32
  merge_collections: bool | None = None,
32
33
  ) -> None:
33
- super().__init__(token=token, token_header=token_header, retries=retries)
34
+ kwargs = {'token': token, 'token_header': token_header, 'retries': retries}
35
+ if status_forcelist is not None:
36
+ kwargs['status_forcelist'] = status_forcelist
37
+ super().__init__(**kwargs)
34
38
  self.__data_url = f'{api_url}{data_prefix}'
35
39
  self.__config_url = f'{self.__data_url}{config_prefix}'
36
40
  self.__merge_collections = merge_collections
@@ -77,7 +81,7 @@ class JsonApiClient(HttpClient):
77
81
  page_size=page_size,
78
82
  filter=filter_string,
79
83
  sort_by=sort_string,
80
- requested_field=requested_fields
84
+ requested_fields=requested_fields
81
85
  )
82
86
  headers = self._merge_headers()
83
87
  return self.__fetch_list(
@@ -455,7 +459,9 @@ class JsonApiClient(HttpClient):
455
459
  return f'{self.__config_url}/return_mode'
456
460
 
457
461
  def __no_none_value_dict(self, **kwargs) -> dict[str, Any]:
458
- return {
459
- k: v for k, v in kwargs.items()
460
- if v is not None
461
- }
462
+ str_params = {}
463
+ for k, v in kwargs.items():
464
+ if v is None:
465
+ continue
466
+ str_params[k] = ','.join([str(x) for x in v]) if isinstance(v, list) else str(v)
467
+ return str_params
@@ -5,8 +5,8 @@
5
5
  from typing import Any, Dict, Optional, Union
6
6
 
7
7
  from .parser import Parser
8
- from .view import View
9
- from ..core import DataObject
8
+ from .view import DefaultView
9
+ from ..core import DataObject, DataSource, ReqFieldsTree
10
10
  from ..core.relationship import RelationshipConfig
11
11
 
12
12
 
@@ -25,7 +25,7 @@ JsonRelationshipConfig = dict[
25
25
  ]
26
26
 
27
27
 
28
- class JsonApiConverter():
28
+ class JsonApiConverter:
29
29
 
30
30
  """
31
31
  Converts from JSON:API transfers to instances of
@@ -142,26 +142,40 @@ class JsonApiConverter():
142
142
  )
143
143
 
144
144
 
145
- class DataObjectConverter():
145
+ class DataObjectConverter:
146
146
 
147
147
  """
148
148
  Converts from instances of `DataObject` to
149
149
  JSON:API transfers.
150
150
  """
151
151
 
152
- def __init__(self, view: View) -> None:
153
- self.__view = view
152
+ def __init__(
153
+ self,
154
+ data_source: DataSource,
155
+ prefix: str | None = None,
156
+ ) -> None:
157
+ self.__data_source = data_source
158
+ self.__prefix = prefix
159
+
160
+ def __build_view(self, object_type):
161
+ req_fields_tree = ReqFieldsTree(object_type, self.__data_source)
162
+ return DefaultView(req_fields_tree, self.__prefix)
154
163
 
155
164
  def convert(self, input_: DataObject) -> JsonApiTransfer:
156
165
  """
157
166
  Converts a single `DataObject` instance to a JsonApiTransfer
158
167
  """
159
168
 
160
- return self.__view.dump(input_)
169
+ view = self.__build_view(input_.type)
170
+ return view.dump(input_)
161
171
 
162
172
  def convert_list(self, input_: list[DataObject]) -> JsonApiTransfer:
163
173
  """
164
174
  Converts a `list` of `DataObject` instances to a JsonApiTransfer
165
175
  """
166
176
 
167
- return self.__view.dump_bulk(input_)
177
+ if not input_:
178
+ msg = 'Cannot convert empty list'
179
+ raise ValueError(msg)
180
+ view = self.__build_view(input_[0].type)
181
+ return view.dump_bulk(input_)
tol/api_client/factory.py CHANGED
@@ -17,7 +17,6 @@ from .converter import (
17
17
  )
18
18
  from .filter import DefaultApiFilter
19
19
  from .parser import DefaultParser
20
- from .view import DefaultView
21
20
  from ..core import DataSource
22
21
 
23
22
 
@@ -66,8 +65,7 @@ class _ConverterFactory:
66
65
  Returns an instantiated `DataObjectConverter`.
67
66
  """
68
67
 
69
- view = DefaultView(prefix=self.__prefix)
70
- return DataObjectConverter(view)
68
+ return DataObjectConverter(self.__data_source, prefix=self.__prefix)
71
69
 
72
70
  def json_converter_factory(self) -> JsonConverterFactory:
73
71
  """
@@ -87,6 +85,7 @@ def _get_client_factory(
87
85
  token: Optional[str],
88
86
  data_prefix: str,
89
87
  retries: int,
88
+ status_forcelist: list[int],
90
89
  merge_collections: bool | None,
91
90
  ) -> Callable[[], JsonApiClient]:
92
91
  """
@@ -99,6 +98,7 @@ def _get_client_factory(
99
98
  token=token,
100
99
  data_prefix=data_prefix,
101
100
  retries=retries,
101
+ status_forcelist=status_forcelist,
102
102
  merge_collections=merge_collections,
103
103
  )
104
104
 
@@ -113,6 +113,7 @@ def create_api_datasource(
113
113
 
114
114
  data_prefix: str = '/data',
115
115
  retries: int = 5,
116
+ status_forcelist: Optional[list[int]] = None,
116
117
  merge_collections: bool | None = None,
117
118
  ) -> ApiDataSource:
118
119
  """
@@ -127,6 +128,7 @@ def create_api_datasource(
127
128
  token=token,
128
129
  data_prefix=data_prefix,
129
130
  retries=retries,
131
+ status_forcelist=status_forcelist,
130
132
  merge_collections=merge_collections,
131
133
  )
132
134
  manager = _ConverterFactory(data_prefix)