tol-sdk 1.7.3__py3-none-any.whl → 1.7.4__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.
@@ -49,7 +49,7 @@ REQUIRED_FIELDS: List = [
49
49
  def pipeline_steps_blueprint(
50
50
  sql_ds: SqlDataSource,
51
51
  prefect_ds: PrefectDataSource,
52
- role: str | None = 'exporter',
52
+ role: str | None = None,
53
53
  url_prefix: str = '/run-pipeline',
54
54
 
55
55
  ctx_getter: CtxGetter = default_ctx_getter,
@@ -155,7 +155,7 @@ def pipeline_steps_blueprint(
155
155
  'deployment_name': flow_name,
156
156
  'parameters': flow_params,
157
157
  'tags': [
158
- 'app_name:treeofsex',
158
+ f'app_name: {os.environ.get("APP_NAME", "tol")}',
159
159
  ],
160
160
  }
161
161
  )
@@ -190,10 +190,13 @@ def pipeline_steps_blueprint(
190
190
  def run_pipeline_steps() -> tuple[dict[str, Any], int]:
191
191
 
192
192
  ctx = ctx_getter()
193
- user_id = ctx.user_id
193
+ if role is not None:
194
+ if not ctx or not ctx.authenticated:
195
+ raise ForbiddenError()
196
+ if role not in ctx.roles:
197
+ raise ForbiddenError()
194
198
 
195
- if role is not None and role not in ctx.roles:
196
- raise ForbiddenError()
199
+ user_id = ctx.user_id if ctx and ctx.authenticated else None
197
200
 
198
201
  body: dict[str, Any] = request.json.get('data', {})
199
202
 
tol/s3/__init__.py CHANGED
@@ -6,3 +6,4 @@ from .parser import Parser # noqa F401
6
6
  from .converter import S3Converter # noqa F401
7
7
  from .factory import create_s3_datasource # noqa F401
8
8
  from .s3_datasource import S3DataSource # noqa F401
9
+ from .data_upload.blueprint import data_upload_blueprint # noqa F401
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2025 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,83 @@
1
+ # SPDX-FileCopyrightText: 2025 Genome Research Ltd.
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from tempfile import NamedTemporaryFile
6
+ from typing import Any
7
+
8
+ from flask import Blueprint, request, send_file
9
+
10
+ from ...api_base import (
11
+ custom_blueprint,
12
+ )
13
+ from ...services.s3_client import S3Client
14
+
15
+
16
+ ALLOWED_EXTENSIONS: set[str] = {'csv', 'json', 'xlsx'}
17
+
18
+
19
+ def allowed_file(filename: str) -> bool:
20
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
21
+
22
+
23
+ def data_upload_blueprint(
24
+ url_prefix: str = '/pipeline/data_upload',
25
+ ) -> Blueprint:
26
+
27
+ data_upload_blueprint = custom_blueprint(
28
+ name='data_upload',
29
+ url_prefix=url_prefix
30
+ )
31
+
32
+ @data_upload_blueprint.route('/upload', methods=['POST'])
33
+ def upload_file() -> tuple[dict[str, str], int]:
34
+ file = request.files['file']
35
+ s3_bucket: str = request.form.get('s3_bucket')
36
+
37
+ if not file:
38
+ return {'error': 'No file provided'}, 400
39
+
40
+ if not allowed_file(file.filename):
41
+ return {'error': 'File type not allowed'}, 400
42
+
43
+ if not s3_bucket:
44
+ return {'error': 'S3 bucket not specified'}, 400
45
+
46
+ try:
47
+ s3_client = S3Client()
48
+ with NamedTemporaryFile() as temp_file:
49
+ file.save(temp_file.name)
50
+ s3_client.put_object(s3_bucket, file.filename, temp_file.name)
51
+
52
+ return {'message': 'File uploaded successfully'}, 200
53
+
54
+ except Exception as e:
55
+ return {'error': f'Failed to upload file: {str(e)}'}, 500
56
+
57
+ @data_upload_blueprint.route('/download', methods=['POST'])
58
+ def download_file() -> tuple[dict[str, str], int]:
59
+ body: dict[str, Any] = request.json.get('data', {})
60
+
61
+ if 's3_bucket' not in body or 'file_name' not in body:
62
+ return {'error': 'S3 bucket or file name not specified'}, 400
63
+
64
+ bucket_name = body['s3_bucket']
65
+ file_name = body['file_name']
66
+
67
+ try:
68
+ s3_client = S3Client()
69
+
70
+ with NamedTemporaryFile() as temp_file:
71
+ s3_client.get_object(bucket_name, file_name, temp_file.name)
72
+ download_name = request.json.get('download_name', file_name)
73
+
74
+ return send_file(
75
+ temp_file.name,
76
+ as_attachment=True,
77
+ download_name=download_name,
78
+ mimetype='application/octet-stream'
79
+ )
80
+ except Exception as e:
81
+ return {'error': f'Failed to download file: {str(e)}'}, 500
82
+
83
+ return data_upload_blueprint
tol/services/s3_client.py CHANGED
@@ -26,7 +26,7 @@ class S3Client:
26
26
  secure = os.getenv('S3_SECURE', 'true').lower() == 'true'
27
27
 
28
28
  self.client = Minio(
29
- self.s3_uri,
29
+ endpoint=self.s3_uri,
30
30
  access_key=self.access_key,
31
31
  secret_key=self.secret_key,
32
32
  secure=secure
@@ -39,7 +39,8 @@ class S3Client:
39
39
  file_path: str
40
40
  ) -> None:
41
41
 
42
- self.client.fget_object(bucket_name, object_name, file_path)
42
+ self.client.fget_object(bucket_name=bucket_name,
43
+ object_name=object_name, file_path=file_path)
43
44
 
44
45
  def list_objects(
45
46
  self,
@@ -55,4 +56,5 @@ class S3Client:
55
56
  file_path: str
56
57
  ) -> None:
57
58
 
58
- self.client.fput_object(bucket_name, object_name, file_path)
59
+ self.client.fput_object(bucket_name=bucket_name,
60
+ object_name=object_name, file_path=file_path)
@@ -146,7 +146,7 @@ def create_pipeline_step_models(
146
146
 
147
147
  user_id: Mapped[int] = mapped_column(
148
148
  ForeignKey('user.id'),
149
- nullable=False
149
+ nullable=True
150
150
  )
151
151
 
152
152
  pipeline_id: Mapped[int] = mapped_column(
@@ -14,7 +14,7 @@ class UniqueValuesValidator(Validator):
14
14
 
15
15
  def __init__(
16
16
  self,
17
- unique_keys: list[str],
17
+ unique_keys: list[list[str] | str],
18
18
  *,
19
19
  detail: str = 'Value is not unique',
20
20
  is_error: bool = True,
@@ -26,21 +26,49 @@ class UniqueValuesValidator(Validator):
26
26
  self.__detail = detail
27
27
  self.__is_error = is_error
28
28
  self.__duplicates: dict[str, list[str]] = {}
29
- self.__existing_values: dict[str, set] = {key: set() for key in unique_keys}
29
+ self.__existing_values: dict[str, set] = {}
30
+ for key in self.__keys:
31
+ if isinstance(key, str):
32
+ self.__existing_values[key] = set()
33
+ elif isinstance(key, list):
34
+ concat_key = '/'.join(key)
35
+ self.__existing_values[concat_key] = set()
30
36
 
31
37
  def _validate_data_object(
32
38
  self,
33
39
  obj: DataObject
34
40
  ) -> None:
35
41
 
36
- for key in obj.attributes:
37
- if key in self.__keys:
38
- if obj.attributes[key] in self.__existing_values[key]:
39
- if key not in self.__duplicates:
40
- self.__duplicates[key] = []
41
- self.__duplicates[key].append(obj.attributes[key])
42
+ for unique_key in self.__keys:
43
+ if isinstance(unique_key, list):
44
+ concat = ''
45
+ for key in unique_key:
46
+ concat = concat + '/' + (str(obj.attributes[key]))
47
+ if concat in self.__existing_values['/'.join(unique_key)]:
48
+ self._duplicate_checks(
49
+ key=key,
50
+ value=concat
51
+ )
42
52
  else:
43
- self.__existing_values[key].add(obj.attributes[key])
53
+ self.__existing_values['/'.join(unique_key)].add(concat)
54
+
55
+ else:
56
+ if obj.attributes[unique_key] in self.__existing_values[unique_key]:
57
+ self._duplicate_checks(
58
+ key=unique_key,
59
+ value=obj.attributes[unique_key]
60
+ )
61
+ else:
62
+ self.__existing_values[unique_key].add(obj.attributes[unique_key])
63
+
64
+ def _duplicate_checks(
65
+ self,
66
+ key: str,
67
+ value: str
68
+ ):
69
+ if key not in self.__duplicates:
70
+ self.__duplicates[key] = []
71
+ self.__duplicates[key].append(value)
44
72
 
45
73
  def _post_validation(
46
74
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tol-sdk
3
- Version: 1.7.3
3
+ Version: 1.7.4
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
@@ -8,7 +8,7 @@ tol/api_base/__init__.py,sha256=xv9kkAyb1Q5vhKgq6f-JpY7lIAla51U-gIpA5U1OfkA,312
8
8
  tol/api_base/action.py,sha256=l3MzD3jSDdYiZs-HCT4YEsXkp34X26k6yWugXrRB2XU,6575
9
9
  tol/api_base/blueprint.py,sha256=lyNXgyyZ7kEL5uuIuSNkardFy8ZcDIfftt8c5xiRjyA,19061
10
10
  tol/api_base/controller.py,sha256=5Fp2FobjXXPlongu1j7adGJKlSVufW0_jpk_wZIMRa4,26818
11
- tol/api_base/pipeline_steps.py,sha256=lz888M2cF-5z-EkGK_sWfZf1otnYRmgnNuC0WUqI-po,5667
11
+ tol/api_base/pipeline_steps.py,sha256=Y7tOwaBBanxAqiUrTpmF6yaLkEJI6o0ELnM-2CaCvHs,5832
12
12
  tol/api_base/system.py,sha256=c47sbcNaCd3teiJLO-Zwg1cUNvfCvRO4GmKzAMNxZrw,1219
13
13
  tol/api_base/auth/__init__.py,sha256=LNbVtQDlzKWzLoCmxAPSIAcM6mlVMnn0Aj9HFUudseo,460
14
14
  tol/api_base/auth/asserts.py,sha256=3kHP2yW6HZxevIgeO8Rl3iKovi8WHZcguVbPSFUjawQ,2765
@@ -237,11 +237,13 @@ tol/prefect/factory.py,sha256=mO4KVnaEYMv-ojGJuiencNQMq6PAMU8cIc4QN5Kq8Gw,2208
237
237
  tol/prefect/filter.py,sha256=aNVTSmcrbb_4EllcFtBOSPuUGgwE-f-Kd5_an6lREtM,7044
238
238
  tol/prefect/prefect_datasource.py,sha256=JXLS4odWNWUu4mgojQogwE28mBTAbj-OQYFxhYNYHnM,8865
239
239
  tol/prefect/prefect_object.py,sha256=Nn3Y6ErQnXubX7M-GzRsfLHEzdHwKr5RWCjIJ4FA28U,495
240
- tol/s3/__init__.py,sha256=L2Aa12NAYjTRr8W_ArJi3z3ME2bUtCf4CG3JDw1MoMk,282
240
+ tol/s3/__init__.py,sha256=AnYM6rWRDJ-AkUBPuIFGrF3wyeh7euwUCiv2-gPy9J4,351
241
241
  tol/s3/converter.py,sha256=KylGAQvu1jRYJl4dcYK1IgbFsmHP3JXRI22MX8UzHz4,1023
242
242
  tol/s3/factory.py,sha256=rFcRVy4B8KVsUaaHhtpLUE90_hRnixkO2IjnB6-arkc,2163
243
243
  tol/s3/parser.py,sha256=EOiPYJ8ZLigAxKGL4cC8r7OGyjGdSMHmJMnDqHSiG_E,1907
244
244
  tol/s3/s3_datasource.py,sha256=tSUc78FN1OudpJcQ-7FI4S_LPT5j-BYIcwFaQPDy_VA,2072
245
+ tol/s3/data_upload/__init__.py,sha256=4dkK7MPB0CzkkujFSVj0FzoG5ri_usQ7XaBB81Vc_qE,85
246
+ tol/s3/data_upload/blueprint.py,sha256=rwZAXsHe_AJXAdRZVdUhtJg3P3SxOs_G_0F1eewPuT4,2574
245
247
  tol/sciops/__init__.py,sha256=PaC1_5wZhsZlqeYQRMpDeIf9L6Hi0eXs_pHXaWfscAo,131
246
248
  tol/sciops/configuration.py,sha256=21ddgHOmhZE1tcE-5_f4KiV-7CUqkF8yXi4iBYPtfmc,1279
247
249
  tol/sciops/consumer.py,sha256=Ikl8glrZ0yQKBhegC84vwNj43iK1Ca_SAn_gcyAhbKQ,2137
@@ -250,7 +252,7 @@ tol/sciops/messages.py,sha256=0kFsa9pIyWV6rADHjcqVa2q2D61U1AmtFK9Lq5mfjbU,1750
250
252
  tol/sciops/response_processors.py,sha256=QDBYT__3tmWArTzeVR29k6yfYQdXanvWSy5VI7nrpio,2658
251
253
  tol/sciops/sequencing_datasource.py,sha256=JT5eenYjrN-pJmaReSGigMq5PvmE2NSDI4bUMHfeMz4,7553
252
254
  tol/services/__init__.py,sha256=FgW9Rb3vp-bfG7OYGyyNdh0JC420opH0gKtr89z6WmE,125
253
- tol/services/s3_client.py,sha256=kMs49KJXyh9gwEAePAPaEAhOESoiguGXdr-Y0hHl_mg,1424
255
+ tol/services/s3_client.py,sha256=OR2PDz7EiU2i__UUIwtvd18MpMEOlPGJqoTGYxIT4Mo,1565
254
256
  tol/sources/__init__.py,sha256=4LbvDIZOOG7p-ebbvivP7NvrJeApUvGEIcDL58ahQJE,85
255
257
  tol/sources/benchling.py,sha256=C1mDri10F6DJ3PYOHHnrf8sJI2WF3xwMoFGLZw0nkKI,585
256
258
  tol/sources/benchling_warehouse.py,sha256=8i91fBSYWy0YvYj031BTGUL4bdi4vzQzcOBkhHM2s_Q,694
@@ -303,7 +305,7 @@ tol/sql/auth/__init__.py,sha256=e3JuwugXmXobklqZ1Mt1w03qPgb1WdUaJVM7oblzHyk,202
303
305
  tol/sql/auth/blueprint.py,sha256=u0vT_TC9IMKrg08QFa9W29_83mT0y0jzLj3DvXy1BBw,25906
304
306
  tol/sql/auth/models.py,sha256=4xNWBvyGiI3mwRyDY5ty48Bv9178ApQXPR-YjIdCsvk,11879
305
307
  tol/sql/pipeline_step/__init__.py,sha256=O7u4RrLfuoB0mwLcPxFoUrdTBZGB_4bE1vWCn5ho-qw,147
306
- tol/sql/pipeline_step/factory.py,sha256=SFJ-BE1I_qmEsqrJ10evdGCdoUHjdiJq6iqrzIlX2jU,5132
308
+ tol/sql/pipeline_step/factory.py,sha256=hrv9iAjdUu3sq6R94PXKclatZNMKY8YMgBDwLWKuvR0,5131
307
309
  tol/sql/standard/__init__.py,sha256=2NbLXFk0rneGZosZ2ESIRcT0WMK0KncmPWaLPqvX-i4,142
308
310
  tol/sql/standard/factory.py,sha256=yY8iWmZRMvUqphwnzBeOIQtKGgxsU6AcA7YTz53UYvc,20010
309
311
  tol/status/__init__.py,sha256=sBo-j1wCmberl89uryVCBEJk8ohbfsYhaNpIp_brR9Y,146
@@ -322,10 +324,10 @@ tol/validators/allowed_keys.py,sha256=BJMomJtaQdxsdGsueDtLewv75TlwdIXiQipLGFcJ7_
322
324
  tol/validators/allowed_values.py,sha256=yJ5SdiUlV7PSKORtsBJ9hYSqwvlx_esbFmFL_Gxh-p4,2262
323
325
  tol/validators/regex.py,sha256=dKodGH0sv6DbqWeV6QXE6-GYjnG4rMO0rg8IEIaQG60,2364
324
326
  tol/validators/regex_by_value.py,sha256=o99NJlWPgQ0GrpVnep8-cHfjWnc9F2rChmXHIxjrMrk,2543
325
- tol/validators/unique_values.py,sha256=stI-1i006WEbERcjSMapRggJkEF-RFDzw2uUtXBAE_M,1885
326
- tol_sdk-1.7.3.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
327
- tol_sdk-1.7.3.dist-info/METADATA,sha256=2_ZEKrI2dmJ-OUhYB16-GpGr4Mb6ybceAvXL4GcPU0E,3079
328
- tol_sdk-1.7.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
329
- tol_sdk-1.7.3.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
330
- tol_sdk-1.7.3.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
331
- tol_sdk-1.7.3.dist-info/RECORD,,
327
+ tol/validators/unique_values.py,sha256=tEXDNJj95XUJdhsZlO-pkXdQz-EYaSG-mcp4MDTL7eY,2835
328
+ tol_sdk-1.7.4.dist-info/licenses/LICENSE,sha256=RF9Jacy-9BpUAQQ20INhTgtaNBkmdTolYCHtrrkM2-8,1077
329
+ tol_sdk-1.7.4.dist-info/METADATA,sha256=1gosjFEvdmraYpLVv81dHvwCKBFGXDUdz__wwvImdLo,3079
330
+ tol_sdk-1.7.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
331
+ tol_sdk-1.7.4.dist-info/entry_points.txt,sha256=jH3HfTwxjzog7E3lq8CKpUWGIRY9FSXbyL6CpUmv6D0,36
332
+ tol_sdk-1.7.4.dist-info/top_level.txt,sha256=PwKMQLphyZNvagBoriVbl8uwHXQl8IC1niawVG0iXMM,10
333
+ tol_sdk-1.7.4.dist-info/RECORD,,