sapiopycommons 2024.8.27a312__tar.gz → 2024.8.28a313__tar.gz
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.
Potentially problematic release.
This version of sapiopycommons might be problematic. Click here for more details.
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/.gitignore +0 -2
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/PKG-INFO +2 -4
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/README.md +0 -1
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/pyproject.toml +2 -2
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/callbacks/callback_util.py +35 -277
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/chem/IndigoMolecules.py +0 -1
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/chem/Molecules.py +0 -1
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_bridge.py +10 -16
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_util.py +6 -13
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_validator.py +0 -71
- sapiopycommons-2024.8.28a313/src/sapiopycommons/general/custom_report_util.py +90 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/recordmodel/record_handler.py +45 -278
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/webhook/webhook_handlers.py +1 -58
- sapiopycommons-2024.8.27a312/src/sapiopycommons/eln/experiment_report_util.py +0 -214
- sapiopycommons-2024.8.27a312/src/sapiopycommons/files/file_bridge_handler.py +0 -318
- sapiopycommons-2024.8.27a312/src/sapiopycommons/general/accession_service.py +0 -375
- sapiopycommons-2024.8.27a312/src/sapiopycommons/general/custom_report_util.py +0 -262
- sapiopycommons-2024.8.27a312/src/sapiopycommons/multimodal/multimodal.py +0 -146
- sapiopycommons-2024.8.27a312/src/sapiopycommons/multimodal/multimodal_data.py +0 -487
- sapiopycommons-2024.8.27a312/tests/_do_not_add_init_py_here +0 -0
- sapiopycommons-2024.8.27a312/tests/accession_test.py +0 -41
- sapiopycommons-2024.8.27a312/tests/bio_reg_test.py +0 -26
- sapiopycommons-2024.8.27a312/tests/chem_test.py +0 -229
- sapiopycommons-2024.8.27a312/tests/data_type_models.py +0 -37332
- sapiopycommons-2024.8.27a312/tests/kappa.chains.fasta +0 -45
- sapiopycommons-2024.8.27a312/tests/mafft_test.py +0 -66
- sapiopycommons-2024.8.27a312/tests/test.gb +0 -124
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/LICENSE +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/callbacks/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/chem/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/datatype/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/datatype/attachment_util.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/eln/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/eln/experiment_handler.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/eln/plate_designer.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/complex_data_loader.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_data_handler.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_writer.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/aliases.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/exceptions.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/popup_util.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/storage_util.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/general/time_util.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/processtracking/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/processtracking/endpoints.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/recordmodel/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/rules/__init__.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/rules/eln_rule_handler.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/rules/on_save_rule_handler.py +0 -0
- {sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/webhook/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sapiopycommons
|
|
3
|
-
Version: 2024.8.
|
|
3
|
+
Version: 2024.8.28a313
|
|
4
4
|
Summary: Official Sapio Python API Utilities Package
|
|
5
5
|
Project-URL: Homepage, https://github.com/sapiosciences
|
|
6
6
|
Author-email: Jonathan Steck <jsteck@sapiosciences.com>, Yechen Qiao <yqiao@sapiosciences.com>
|
|
@@ -17,8 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
Requires-Dist: sapiopylib>=2024.5.24.210
|
|
20
|
+
Requires-Dist: sapiopylib>=2023.12.13.174
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
|
|
24
23
|
|
|
@@ -51,7 +50,6 @@ This license does not provide any rights to use any other copyrighted artifacts
|
|
|
51
50
|
## Dependencies
|
|
52
51
|
The following dependencies are required for this package:
|
|
53
52
|
- [sapiopylib - The official Sapio Informatics Platform Python API package.](https://pypi.org/project/sapiopylib/)
|
|
54
|
-
- [databind - Databind is a library inspired by jackson-databind to de-/serialize Python dataclasses.](https://pypi.org/project/databind/)
|
|
55
53
|
|
|
56
54
|
## Getting Help
|
|
57
55
|
If you have a support contract with Sapio Sciences, please use our [technical support channels](https://sapio-sciences.atlassian.net/servicedesk/customer/portals).
|
|
@@ -28,7 +28,6 @@ This license does not provide any rights to use any other copyrighted artifacts
|
|
|
28
28
|
## Dependencies
|
|
29
29
|
The following dependencies are required for this package:
|
|
30
30
|
- [sapiopylib - The official Sapio Informatics Platform Python API package.](https://pypi.org/project/sapiopylib/)
|
|
31
|
-
- [databind - Databind is a library inspired by jackson-databind to de-/serialize Python dataclasses.](https://pypi.org/project/databind/)
|
|
32
31
|
|
|
33
32
|
## Getting Help
|
|
34
33
|
If you have a support contract with Sapio Sciences, please use our [technical support channels](https://sapio-sciences.atlassian.net/servicedesk/customer/portals).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sapiopycommons"
|
|
7
|
-
version='2024.08.
|
|
7
|
+
version='2024.08.28a313'
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Jonathan Steck", email="jsteck@sapiosciences.com" },
|
|
10
10
|
{ name="Yechen Qiao", email="yqiao@sapiosciences.com" },
|
|
@@ -14,7 +14,7 @@ license = "MPL-2.0"
|
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.10"
|
|
16
16
|
dependencies = [
|
|
17
|
-
'sapiopylib>=
|
|
17
|
+
'sapiopylib>=2023.12.13.174'
|
|
18
18
|
]
|
|
19
19
|
classifiers = [
|
|
20
20
|
"Intended Audience :: Developers",
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import io
|
|
4
2
|
from typing import Any
|
|
5
3
|
|
|
@@ -11,11 +9,11 @@ from sapiopylib.rest.pojo.DataRecord import DataRecord
|
|
|
11
9
|
from sapiopylib.rest.pojo.datatype.DataType import DataTypeDefinition
|
|
12
10
|
from sapiopylib.rest.pojo.datatype.DataTypeLayout import DataTypeLayout
|
|
13
11
|
from sapiopylib.rest.pojo.datatype.FieldDefinition import AbstractVeloxFieldDefinition, VeloxStringFieldDefinition, \
|
|
14
|
-
VeloxIntegerFieldDefinition, VeloxDoubleFieldDefinition
|
|
12
|
+
VeloxIntegerFieldDefinition, VeloxDoubleFieldDefinition
|
|
15
13
|
from sapiopylib.rest.pojo.webhook.ClientCallbackRequest import OptionDialogRequest, ListDialogRequest, \
|
|
16
14
|
FormEntryDialogRequest, InputDialogCriteria, TableEntryDialogRequest, ESigningRequestPojo, \
|
|
17
|
-
DataRecordDialogRequest, InputSelectionRequest, FilePromptRequest,
|
|
18
|
-
|
|
15
|
+
DataRecordSelectionRequest, DataRecordDialogRequest, InputSelectionRequest, FilePromptRequest, \
|
|
16
|
+
MultiFilePromptRequest
|
|
19
17
|
from sapiopylib.rest.pojo.webhook.ClientCallbackResult import ESigningResponsePojo
|
|
20
18
|
from sapiopylib.rest.pojo.webhook.WebhookContext import SapioWebhookContext
|
|
21
19
|
from sapiopylib.rest.pojo.webhook.WebhookEnums import FormAccessLevel, ScanToSelectCriteria, SearchType
|
|
@@ -211,15 +209,15 @@ class CallbackUtil:
|
|
|
211
209
|
type_def: DataTypeDefinition = self.dt_cache.get_data_type(data_type)
|
|
212
210
|
field_defs: dict[str, AbstractVeloxFieldDefinition] = self.dt_cache.get_fields_for_type(data_type)
|
|
213
211
|
|
|
214
|
-
# Make everything visible, because presumably the caller gave a field name because they want it to be seen.
|
|
215
|
-
modifier = FieldModifier(visible=True, editable=editable)
|
|
216
|
-
|
|
217
212
|
# Build the form using only those fields that are desired.
|
|
218
213
|
builder = FormBuilder(data_type, type_def.display_name, type_def.plural_display_name)
|
|
219
214
|
for field_name in fields:
|
|
220
215
|
field_def = field_defs.get(field_name)
|
|
221
216
|
if field_def is None:
|
|
222
217
|
raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
|
|
218
|
+
if editable is not None:
|
|
219
|
+
field_def.editable = editable
|
|
220
|
+
field_def.visible = True
|
|
223
221
|
if hasattr(field_def, "default_value"):
|
|
224
222
|
field_def.default_value = record.get_field_value(field_name)
|
|
225
223
|
column: int = 0
|
|
@@ -228,7 +226,7 @@ class CallbackUtil:
|
|
|
228
226
|
position = column_positions.get(field_name)
|
|
229
227
|
column = position[0]
|
|
230
228
|
span = position[1]
|
|
231
|
-
builder.add_field(
|
|
229
|
+
builder.add_field(field_def, column, span)
|
|
232
230
|
|
|
233
231
|
request = FormEntryDialogRequest(title, msg, builder.get_temporary_data_type())
|
|
234
232
|
response: FieldMap | None = self.callback.show_form_entry_dialog(request)
|
|
@@ -261,7 +259,7 @@ class CallbackUtil:
|
|
|
261
259
|
:param field_name: The name and display name of the string field.
|
|
262
260
|
:param default_value: The default value to place into the string field, if any.
|
|
263
261
|
:param max_length: The max length of the string value. If not provided, uses the length of the default value.
|
|
264
|
-
If neither this
|
|
262
|
+
If neither this or a default value are not provided, defaults to 100 characters.
|
|
265
263
|
:param editable: Whether the field is editable by the user.
|
|
266
264
|
:param kwargs: Any additional keyword arguments to pass to the field definition.
|
|
267
265
|
:return: The string that the user input into the dialog.
|
|
@@ -348,13 +346,9 @@ class CallbackUtil:
|
|
|
348
346
|
if plural_display_name is None:
|
|
349
347
|
plural_display_name = display_name + "s"
|
|
350
348
|
|
|
351
|
-
# Key fields display their columns in order before all non-key fields.
|
|
352
|
-
# Unmark key fields so that the column order is respected exactly as the caller provides it.
|
|
353
|
-
modifier = FieldModifier(key_field=False)
|
|
354
|
-
|
|
355
349
|
builder = FormBuilder(data_type, display_name, plural_display_name)
|
|
356
|
-
for
|
|
357
|
-
builder.add_field(
|
|
350
|
+
for column in fields:
|
|
351
|
+
builder.add_field(column)
|
|
358
352
|
|
|
359
353
|
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values)
|
|
360
354
|
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
@@ -370,9 +364,8 @@ class CallbackUtil:
|
|
|
370
364
|
editable: bool | None = True) -> list[FieldMap]:
|
|
371
365
|
"""
|
|
372
366
|
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
373
|
-
a given list of records
|
|
374
|
-
|
|
375
|
-
the given records.
|
|
367
|
+
a given list of records. Provided field names must match fields on the definition of the data type of the given
|
|
368
|
+
records. The fields that are displayed will have their default value be that of the fields on the given records.
|
|
376
369
|
|
|
377
370
|
Makes webservice calls to get the data type definition and fields of the given records if they weren't
|
|
378
371
|
previously cached.
|
|
@@ -397,181 +390,25 @@ class CallbackUtil:
|
|
|
397
390
|
type_def: DataTypeDefinition = self.dt_cache.get_data_type(data_type)
|
|
398
391
|
field_defs: dict[str, AbstractVeloxFieldDefinition] = self.dt_cache.get_fields_for_type(data_type)
|
|
399
392
|
|
|
400
|
-
# Key fields display their columns in order before all non-key fields.
|
|
401
|
-
# Unmark key fields so that the column order is respected exactly as the caller provides it.
|
|
402
|
-
# Also make everything visible, because presumably the caller gave a field name because they want it to be seen.
|
|
403
|
-
modifier = FieldModifier(visible=True, key_field=False, editable=editable)
|
|
404
|
-
|
|
405
393
|
# Build the form using only those fields that are desired.
|
|
406
394
|
builder = FormBuilder(data_type, type_def.display_name, type_def.plural_display_name)
|
|
407
395
|
for field_name in fields:
|
|
408
396
|
field_def = field_defs.get(field_name)
|
|
409
397
|
if field_def is None:
|
|
410
398
|
raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
|
|
411
|
-
|
|
399
|
+
if editable is not None:
|
|
400
|
+
field_def.editable = editable
|
|
401
|
+
field_def.visible = True
|
|
402
|
+
# Key fields display their columns in order before all non-key fields.
|
|
403
|
+
# Unmark key fields so that the column order is respected exactly as the caller provides it.
|
|
404
|
+
field_def.key_field = False
|
|
405
|
+
builder.add_field(field_def)
|
|
412
406
|
|
|
413
407
|
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), field_map_list)
|
|
414
408
|
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
415
409
|
if response is None:
|
|
416
410
|
raise SapioUserCancelledException()
|
|
417
411
|
return response
|
|
418
|
-
|
|
419
|
-
def multi_type_table_dialog(self,
|
|
420
|
-
title: str,
|
|
421
|
-
msg: str,
|
|
422
|
-
fields: list[(str, str) | AbstractVeloxFieldDefinition],
|
|
423
|
-
row_contents: list[list[SapioRecord | FieldMap]],
|
|
424
|
-
*,
|
|
425
|
-
default_modifier: FieldModifier | None = None,
|
|
426
|
-
field_modifiers: dict[str, FieldModifier] | None = None,
|
|
427
|
-
data_type: str = "Default",
|
|
428
|
-
display_name: str | None = None,
|
|
429
|
-
plural_display_name: str | None = None) -> list[FieldMap]:
|
|
430
|
-
"""
|
|
431
|
-
Create a table dialog where the user may input data into the fields of the table. The table is constructed from
|
|
432
|
-
a given list of records of multiple data types or field maps. Provided field names must match with field names
|
|
433
|
-
from the data type definition of the given records. The fields that are displayed will have their default value
|
|
434
|
-
be that of the fields on the given records or field maps.
|
|
435
|
-
|
|
436
|
-
Makes webservice calls to get the data type field definitions of the given records if they weren't
|
|
437
|
-
previously cached.
|
|
438
|
-
|
|
439
|
-
:param title: The title of the dialog.
|
|
440
|
-
:param msg: The message to display in the dialog.
|
|
441
|
-
:param fields: A list of objects representing the fields in the table. This could either be a two-element tuple
|
|
442
|
-
where the first element is a data type name and the second is a field name, or it could be a field
|
|
443
|
-
definition. If it is the former, a query will be made to find the field definition matching tht data type.
|
|
444
|
-
The data type names of the fields must match the data type names of the records in the row contents.
|
|
445
|
-
See the description of row_contents for what to do if you want to construct a field that pulls from a field
|
|
446
|
-
map.
|
|
447
|
-
If two fields share the same field name, an exception will be thrown. This is even true in the case where
|
|
448
|
-
the data type name of the fields is different. If you wish to display two fields from two data types with
|
|
449
|
-
the same name, then you must provide a FieldModifier for at least one of the fields where prepend_data_type
|
|
450
|
-
is True in order to make that field's name unique again. Note that if you do this for a field, the mapping
|
|
451
|
-
of record to field name will use the unedited field name, but the return results of this function will
|
|
452
|
-
use the edited field name in the results dictionary for a row.
|
|
453
|
-
:param row_contents: A list where each element is another list representing the records or a field map that will
|
|
454
|
-
be used to populate the columns of the table. If the data type of a given record doesn't match any of the
|
|
455
|
-
data type names of the given fields, then it will not be used.
|
|
456
|
-
This list can contain up to one field map, which are fields not tied to a record. This is so that you can
|
|
457
|
-
create abstract field definition not tied to a specific record in the system. If you want to define an
|
|
458
|
-
abstract field that pulls from the field map in the row contents, then you must set the data type name to
|
|
459
|
-
Default.
|
|
460
|
-
If a record of a given data type appears more than once in one of the inner-lists of the row contents, or
|
|
461
|
-
there is more than one field map, then an exception will be thrown, as there is no way of distinguishing
|
|
462
|
-
which record should be used for a field, and not all fields could have their values combined across multiple
|
|
463
|
-
records.
|
|
464
|
-
The row contents may have an inner-list which is missing a record of a data type that matches one of the
|
|
465
|
-
fields. In this case, the field value for that row under that column will be null.
|
|
466
|
-
The inner-list does not need to be sorted in any way, as this function will map the inner-list contents to
|
|
467
|
-
fields as necessary.
|
|
468
|
-
The inner-list may contain null elements; these will simply be discarded by this function.
|
|
469
|
-
:param default_modifier: A default field modifier that will be applied to the given fields. This can be used to
|
|
470
|
-
make field definitions from the system behave differently than their system values. If this value is None,
|
|
471
|
-
then a default field modifier is created that causes all specified fields to be both visible and not key
|
|
472
|
-
fields. (Key fields get displayed first before any non-key fields in tables, so the key field setting is
|
|
473
|
-
disabled by default in order to have the columns in the table respect the order of the fields as they are
|
|
474
|
-
provided to this function.)
|
|
475
|
-
:param field_modifiers: A mapping of data field name to field modifier for changes that should be applied to
|
|
476
|
-
the matching field. If a data field name is not present in the provided dict, or the provided dictionary is
|
|
477
|
-
None, then the default modifier will be used.
|
|
478
|
-
:param data_type: The data type name for the temporary data type that will be created for this table.
|
|
479
|
-
:param display_name: The display name for the temporary data type. If not provided, defaults to the data type
|
|
480
|
-
name.
|
|
481
|
-
:param plural_display_name: The plural display name for the temporary data type. If not provided, defaults to
|
|
482
|
-
the display name + "s".
|
|
483
|
-
:return: A list of dictionaries mapping the data field names of the given field definitions to the response
|
|
484
|
-
value from the user for that field for each row.
|
|
485
|
-
"""
|
|
486
|
-
# Set the default modifier to make all fields visible and not key if no default was provided.
|
|
487
|
-
if default_modifier is None:
|
|
488
|
-
default_modifier = FieldModifier(visible=True, key_field=False)
|
|
489
|
-
# To make things simpler, treat null field modifiers as an empty dict.
|
|
490
|
-
if field_modifiers is None:
|
|
491
|
-
field_modifiers = {}
|
|
492
|
-
|
|
493
|
-
# Construct the final fields list from the possible field objects.
|
|
494
|
-
final_fields: list[AbstractVeloxFieldDefinition] = []
|
|
495
|
-
# Keep track of whether any given field name appears more than once, as two fields could have the same
|
|
496
|
-
# field name but different data types. In this case, the user should provide a field modifier or field
|
|
497
|
-
# definition that changes one of the field names.
|
|
498
|
-
field_names: list[str] = []
|
|
499
|
-
for field in fields:
|
|
500
|
-
# Find the field definition for this field object.
|
|
501
|
-
if isinstance(field, tuple):
|
|
502
|
-
field_def: AbstractVeloxFieldDefinition = self.dt_cache.get_fields_for_type(field[0]).get(field[1])
|
|
503
|
-
elif isinstance(field, AbstractVeloxFieldDefinition):
|
|
504
|
-
field_def: AbstractVeloxFieldDefinition = field
|
|
505
|
-
else:
|
|
506
|
-
raise SapioException("Unrecognized field object.")
|
|
507
|
-
|
|
508
|
-
# Locate the modifier for this field and store the modified field.
|
|
509
|
-
name: str = field_def.data_field_name
|
|
510
|
-
modifier: FieldModifier = field_modifiers.get(name, default_modifier)
|
|
511
|
-
field_def: AbstractVeloxFieldDefinition = modifier.modify_field(field_def)
|
|
512
|
-
final_fields.append(field_def)
|
|
513
|
-
|
|
514
|
-
# Verify that this field name isn't a duplicate.
|
|
515
|
-
# The field name may have changed due to the modifier.
|
|
516
|
-
name: str = field_def.data_field_name
|
|
517
|
-
if name in field_names:
|
|
518
|
-
raise SapioException(f"The field name \"{name}\" appears more than once in the given fields. "
|
|
519
|
-
f"If you have provided two fields with the same name but different data types, "
|
|
520
|
-
f"consider providing a FieldModifier where prepend_data_type is true for one of "
|
|
521
|
-
f"the fields so that the field names will be different.")
|
|
522
|
-
field_names.append(name)
|
|
523
|
-
|
|
524
|
-
# Get the values for each row.
|
|
525
|
-
values: list[dict[str, Any]] = []
|
|
526
|
-
for row in row_contents:
|
|
527
|
-
# The final values for this row:
|
|
528
|
-
row_values: dict[str, Any] = {}
|
|
529
|
-
|
|
530
|
-
# Map the records for this row by their data type. If a field map is provided, its data type is Default.
|
|
531
|
-
row_records: dict[str, SapioRecord | FieldMap] = {}
|
|
532
|
-
for rec in row:
|
|
533
|
-
# Toss out null elements.
|
|
534
|
-
if rec is None:
|
|
535
|
-
continue
|
|
536
|
-
# Map records to their data type name. Map field maps to Default.
|
|
537
|
-
dt: str = "Default" if isinstance(rec, dict) else rec.data_type_name
|
|
538
|
-
# Warn if the same data type name appears more than once.
|
|
539
|
-
if dt in row_records:
|
|
540
|
-
raise SapioException(f"The data type \"{dt}\" appears more than once in the given row contents.")
|
|
541
|
-
row_records[dt] = rec
|
|
542
|
-
|
|
543
|
-
# Get the field values from the above records.
|
|
544
|
-
for field in final_fields:
|
|
545
|
-
# Find the object that corresponds to this field given its data type name.
|
|
546
|
-
record: SapioRecord | FieldMap | None = row_records.get(field.data_type_name)
|
|
547
|
-
# This could be either a record, a field map, or null. Convert any records to field maps.
|
|
548
|
-
if not isinstance(record, dict) and record is not None:
|
|
549
|
-
record: FieldMap | None = AliasUtil.to_field_map_lists([record])[0]
|
|
550
|
-
|
|
551
|
-
# Find out if this field had its data type prepended to it. If this is the case, then we need to find
|
|
552
|
-
# the true data field name before retrieving the value from the field map.
|
|
553
|
-
name: str = field.data_field_name
|
|
554
|
-
if field_modifiers.get(name, default_modifier).prepend_data_type is True:
|
|
555
|
-
name = name.split(".")[1]
|
|
556
|
-
|
|
557
|
-
# Set the value for this particular field.
|
|
558
|
-
row_values[field.data_field_name] = record.get(name) if record else None
|
|
559
|
-
values.append(row_values)
|
|
560
|
-
|
|
561
|
-
if display_name is None:
|
|
562
|
-
display_name = data_type
|
|
563
|
-
if plural_display_name is None:
|
|
564
|
-
plural_display_name = display_name + "s"
|
|
565
|
-
|
|
566
|
-
builder = FormBuilder(data_type, display_name, plural_display_name)
|
|
567
|
-
for field in final_fields:
|
|
568
|
-
builder.add_field(field)
|
|
569
|
-
|
|
570
|
-
request = TableEntryDialogRequest(title, msg, builder.get_temporary_data_type(), values)
|
|
571
|
-
response: list[FieldMap] | None = self.callback.show_table_entry_dialog(request)
|
|
572
|
-
if response is None:
|
|
573
|
-
raise SapioUserCancelledException()
|
|
574
|
-
return response
|
|
575
412
|
|
|
576
413
|
def record_view_dialog(self,
|
|
577
414
|
title: str,
|
|
@@ -627,8 +464,7 @@ class CallbackUtil:
|
|
|
627
464
|
values: list[FieldMap],
|
|
628
465
|
multi_select: bool = True,
|
|
629
466
|
*,
|
|
630
|
-
|
|
631
|
-
display_name: str | None = None,
|
|
467
|
+
display_name: str = "Default",
|
|
632
468
|
plural_display_name: str | None = None) -> list[FieldMap]:
|
|
633
469
|
"""
|
|
634
470
|
Create a selection dialog for a list of field maps for the user to choose from. Requires that the caller
|
|
@@ -639,25 +475,18 @@ class CallbackUtil:
|
|
|
639
475
|
they are provided in this list.
|
|
640
476
|
:param values: The values to set for each row of the table.
|
|
641
477
|
:param multi_select: Whether the user is able to select multiple rows from the list.
|
|
642
|
-
:param
|
|
643
|
-
:param display_name: The display name for the temporary data type. If not provided, defaults to the data type
|
|
644
|
-
name.
|
|
478
|
+
:param display_name: The display name for the temporary data type that will be created.
|
|
645
479
|
:param plural_display_name: The plural display name for the temporary data type. If not provided, defaults to
|
|
646
480
|
the display name + "s".
|
|
647
481
|
:return: A list of field maps corresponding to the chosen input field maps.
|
|
648
482
|
"""
|
|
649
|
-
if display_name is None:
|
|
650
|
-
display_name = data_type
|
|
651
483
|
if plural_display_name is None:
|
|
652
484
|
plural_display_name = display_name + "s"
|
|
653
485
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
request = TempTableSelectionRequest(builder.get_temporary_data_type(), msg, values,
|
|
659
|
-
multi_select=multi_select)
|
|
660
|
-
response: list[FieldMap] | None = self.callback.show_temp_table_selection_dialog(request)
|
|
486
|
+
# Build the form using only those fields that are desired.
|
|
487
|
+
request = DataRecordSelectionRequest(display_name, plural_display_name,
|
|
488
|
+
fields, values, msg, multi_select)
|
|
489
|
+
response: list[FieldMap] | None = self.callback.show_data_record_selection_dialog(request)
|
|
661
490
|
if response is None:
|
|
662
491
|
raise SapioUserCancelledException()
|
|
663
492
|
return response
|
|
@@ -692,22 +521,21 @@ class CallbackUtil:
|
|
|
692
521
|
type_def: DataTypeDefinition = self.dt_cache.get_data_type(data_type)
|
|
693
522
|
field_defs: dict[str, AbstractVeloxFieldDefinition] = self.dt_cache.get_fields_for_type(data_type)
|
|
694
523
|
|
|
695
|
-
# Key fields display their columns in order before all non-key fields.
|
|
696
|
-
# Unmark key fields so that the column order is respected exactly as the caller provides it.
|
|
697
|
-
# Also make everything visible, because presumably the caller give a field name because they want it to be seen.
|
|
698
|
-
modifier = FieldModifier(visible=True, key_field=False)
|
|
699
|
-
|
|
700
524
|
# Build the form using only those fields that are desired.
|
|
701
|
-
|
|
525
|
+
field_def_list: list = []
|
|
702
526
|
for field_name in fields:
|
|
703
527
|
field_def = field_defs.get(field_name)
|
|
704
528
|
if field_def is None:
|
|
705
529
|
raise SapioException(f"No field of name \"{field_name}\" in field definitions of type \"{data_type}\"")
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
530
|
+
field_def.visible = True
|
|
531
|
+
# Key fields display their columns in order before all non-key fields.
|
|
532
|
+
# Unmark key fields so that the column order is respected exactly as the caller provides it.
|
|
533
|
+
field_def.key_field = False
|
|
534
|
+
field_def_list.append(field_def)
|
|
535
|
+
|
|
536
|
+
request = DataRecordSelectionRequest(type_def.display_name, type_def.plural_display_name,
|
|
537
|
+
field_def_list, field_map_list, msg, multi_select)
|
|
538
|
+
response: list[FieldMap] | None = self.callback.show_data_record_selection_dialog(request)
|
|
711
539
|
if response is None:
|
|
712
540
|
raise SapioUserCancelledException()
|
|
713
541
|
# Map the field maps in the response back to the record they come from, returning the chosen record instead of
|
|
@@ -908,73 +736,3 @@ class CallbackUtil:
|
|
|
908
736
|
"""
|
|
909
737
|
data = io.StringIO(file_data) if isinstance(file_data, str) else io.BytesIO(file_data)
|
|
910
738
|
self.callback.send_file(file_name, False, data)
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
class FieldModifier:
|
|
914
|
-
"""
|
|
915
|
-
A FieldModifier can be used to update the settings of a field definition from the system.
|
|
916
|
-
"""
|
|
917
|
-
prepend_data_type: bool
|
|
918
|
-
display_name: str | None
|
|
919
|
-
required: bool | None
|
|
920
|
-
editable: bool | None
|
|
921
|
-
visible: bool | None
|
|
922
|
-
key_field: bool | None
|
|
923
|
-
column_width: int | None
|
|
924
|
-
|
|
925
|
-
def __init__(self, *, prepend_data_type: bool = False,
|
|
926
|
-
display_name: str | None = None, required: bool | None = None, editable: bool | None = None,
|
|
927
|
-
visible: bool | None = None, key_field: bool | None = None, column_width: int | None = None):
|
|
928
|
-
"""
|
|
929
|
-
If any values are given as None then that value will not be changed on the given field.
|
|
930
|
-
|
|
931
|
-
:param prepend_data_type: If true, prepends the data type name of the field to the data field name. For example,
|
|
932
|
-
if a field has a data type name X and a data field name Y, then the field name would become "X.Y". This is
|
|
933
|
-
useful for cases where you have the same field name on two different data types and want to distinguish one
|
|
934
|
-
or both of them.
|
|
935
|
-
:param display_name: Change the display name.
|
|
936
|
-
:param required: Change the required status.
|
|
937
|
-
:param editable: Change the editable status.
|
|
938
|
-
:param visible: Change the visible status.
|
|
939
|
-
:param key_field: Change the key field status.
|
|
940
|
-
:param column_width: Change the column width.
|
|
941
|
-
"""
|
|
942
|
-
self.prepend_data_type = prepend_data_type
|
|
943
|
-
self.display_name = display_name
|
|
944
|
-
self.required = required
|
|
945
|
-
self.editable = editable
|
|
946
|
-
self.visible = visible
|
|
947
|
-
self.key_field = key_field
|
|
948
|
-
self.column_width = column_width
|
|
949
|
-
|
|
950
|
-
def modify_field(self, field: AbstractVeloxFieldDefinition) -> AbstractVeloxFieldDefinition:
|
|
951
|
-
"""
|
|
952
|
-
Apply modifications to a given field.
|
|
953
|
-
|
|
954
|
-
:param field: The field to modify.
|
|
955
|
-
:return: A copy of the input field with the modifications applied.
|
|
956
|
-
"""
|
|
957
|
-
field = copy_field(field)
|
|
958
|
-
if self.prepend_data_type is True:
|
|
959
|
-
field._data_field_name = field.data_field_name + "." + field.data_field_name
|
|
960
|
-
if self.display_name is not None:
|
|
961
|
-
field.display_name = self.display_name
|
|
962
|
-
if self.required is not None:
|
|
963
|
-
field.required = self.required
|
|
964
|
-
if self.editable is not None:
|
|
965
|
-
field.editable = self.editable
|
|
966
|
-
if self.visible is not None:
|
|
967
|
-
field.visible = self.visible
|
|
968
|
-
if self.key_field is not None:
|
|
969
|
-
field.key_field = self.key_field
|
|
970
|
-
if self.column_width is not None:
|
|
971
|
-
field.default_table_column_width = self.column_width
|
|
972
|
-
return field
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
def copy_field(field: AbstractVeloxFieldDefinition) -> AbstractVeloxFieldDefinition:
|
|
976
|
-
"""
|
|
977
|
-
Create a copy of a given field definition. This is used to modify field definitions from the server for existing
|
|
978
|
-
data types without also modifying the field definition in the cache.
|
|
979
|
-
"""
|
|
980
|
-
return FieldDefinitionParser.to_field_definition(field.to_json())
|
|
@@ -6,7 +6,6 @@ indigo = Indigo()
|
|
|
6
6
|
renderer = IndigoRenderer(indigo)
|
|
7
7
|
indigo.setOption("render-output-format", "svg")
|
|
8
8
|
indigo.setOption("ignore-stereochemistry-errors", True)
|
|
9
|
-
indigo.setOption("render-stereo-style", "ext")
|
|
10
9
|
indigo.setOption("aromaticity-model", "generic")
|
|
11
10
|
indigo.setOption("render-coloring", True)
|
|
12
11
|
indigo_inchi = IndigoInchi(indigo);
|
{sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/chem/Molecules.py
RENAMED
|
@@ -181,7 +181,6 @@ def mol_to_sapio_substance(mol: Mol, include_stereoisomers: bool = False,
|
|
|
181
181
|
# We need to test the INCHI can be loaded back to indigo.
|
|
182
182
|
indigo_mol = indigo.loadMolecule(molBlock)
|
|
183
183
|
indigo_mol.aromatize()
|
|
184
|
-
indigo_inchi.resetOptions()
|
|
185
184
|
indigo_inchi_str = indigo_inchi.getInchi(indigo_mol)
|
|
186
185
|
molecule["inchi"] = indigo_inchi_str
|
|
187
186
|
indigo_inchi_key_str = indigo_inchi.getInchiKey(indigo_inchi_str)
|
|
@@ -16,8 +16,7 @@ class FileBridge:
|
|
|
16
16
|
Read a file from FileBridge.
|
|
17
17
|
|
|
18
18
|
:param context: The current webhook context or a user object to send requests from.
|
|
19
|
-
:param bridge_name: The name of the bridge to use.
|
|
20
|
-
file bridge configurations.
|
|
19
|
+
:param bridge_name: The name of the bridge to use.
|
|
21
20
|
:param file_path: The path to read the file from.
|
|
22
21
|
:param base64_decode: If true, base64 decode the file. Files are by default base64 encoded when retrieved from
|
|
23
22
|
FileBridge.
|
|
@@ -43,8 +42,7 @@ class FileBridge:
|
|
|
43
42
|
Write a file to FileBridge.
|
|
44
43
|
|
|
45
44
|
:param context: The current webhook context or a user object to send requests from.
|
|
46
|
-
:param bridge_name: The name of the bridge to use.
|
|
47
|
-
file bridge configurations.
|
|
45
|
+
:param bridge_name: The name of the bridge to use.
|
|
48
46
|
:param file_path: The path to write the file to. If a file already exists at the given path then the file is
|
|
49
47
|
overwritten.
|
|
50
48
|
:param file_data: A string or bytes of the file to be written.
|
|
@@ -65,10 +63,9 @@ class FileBridge:
|
|
|
65
63
|
List the contents of a FileBridge directory.
|
|
66
64
|
|
|
67
65
|
:param context: The current webhook context or a user object to send requests from.
|
|
68
|
-
:param bridge_name: The name of the bridge to use.
|
|
69
|
-
file bridge configurations.
|
|
66
|
+
:param bridge_name: The name of the bridge to use.
|
|
70
67
|
:param file_path: The path to read the directory from.
|
|
71
|
-
:return: A list of
|
|
68
|
+
:return: A list of name of files and folders in the directory.
|
|
72
69
|
"""
|
|
73
70
|
sub_path = '/ext/filebridge/listDirectory'
|
|
74
71
|
params = {
|
|
@@ -80,7 +77,7 @@ class FileBridge:
|
|
|
80
77
|
|
|
81
78
|
response_body: list[str] = response.json()
|
|
82
79
|
path_length = len(f"bridge://{bridge_name}/")
|
|
83
|
-
return [urllib.parse.unquote(value
|
|
80
|
+
return [urllib.parse.unquote(value[path_length:]) for value in response_body]
|
|
84
81
|
|
|
85
82
|
@staticmethod
|
|
86
83
|
def create_directory(context: SapioWebhookContext | SapioUser, bridge_name: str, file_path: str) -> None:
|
|
@@ -88,8 +85,7 @@ class FileBridge:
|
|
|
88
85
|
Create a new directory in FileBridge.
|
|
89
86
|
|
|
90
87
|
:param context: The current webhook context or a user object to send requests from.
|
|
91
|
-
:param bridge_name: The name of the bridge to use.
|
|
92
|
-
file bridge configurations.
|
|
88
|
+
:param bridge_name: The name of the bridge to use.
|
|
93
89
|
:param file_path: The path to create the directory at. If a directory already exists at the given path then an
|
|
94
90
|
exception is raised.
|
|
95
91
|
"""
|
|
@@ -107,8 +103,7 @@ class FileBridge:
|
|
|
107
103
|
Delete an existing file in FileBridge.
|
|
108
104
|
|
|
109
105
|
:param context: The current webhook context or a user object to send requests from.
|
|
110
|
-
:param bridge_name: The name of the bridge to use.
|
|
111
|
-
file bridge configurations.
|
|
106
|
+
:param bridge_name: The name of the bridge to use.
|
|
112
107
|
:param file_path: The path to the file to delete.
|
|
113
108
|
"""
|
|
114
109
|
sub_path = '/ext/filebridge/deleteFile'
|
|
@@ -116,7 +111,7 @@ class FileBridge:
|
|
|
116
111
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
117
112
|
}
|
|
118
113
|
user: SapioUser = context if isinstance(context, SapioUser) else context.user
|
|
119
|
-
response = user.
|
|
114
|
+
response = user.post(sub_path, params=params)
|
|
120
115
|
user.raise_for_status(response)
|
|
121
116
|
|
|
122
117
|
@staticmethod
|
|
@@ -125,8 +120,7 @@ class FileBridge:
|
|
|
125
120
|
Delete an existing directory in FileBridge.
|
|
126
121
|
|
|
127
122
|
:param context: The current webhook context or a user object to send requests from.
|
|
128
|
-
:param bridge_name: The name of the bridge to use.
|
|
129
|
-
file bridge configurations.
|
|
123
|
+
:param bridge_name: The name of the bridge to use.
|
|
130
124
|
:param file_path: The path to the directory to delete.
|
|
131
125
|
"""
|
|
132
126
|
sub_path = '/ext/filebridge/deleteDirectory'
|
|
@@ -134,5 +128,5 @@ class FileBridge:
|
|
|
134
128
|
'Filepath': f"bridge://{bridge_name}/{file_path}"
|
|
135
129
|
}
|
|
136
130
|
user: SapioUser = context if isinstance(context, SapioUser) else context.user
|
|
137
|
-
response = user.
|
|
131
|
+
response = user.post(sub_path, params=params)
|
|
138
132
|
user.raise_for_status(response)
|
{sapiopycommons-2024.8.27a312 → sapiopycommons-2024.8.28a313}/src/sapiopycommons/files/file_util.py
RENAMED
|
@@ -21,7 +21,7 @@ class FileUtil:
|
|
|
21
21
|
"""
|
|
22
22
|
@staticmethod
|
|
23
23
|
def tokenize_csv(file_bytes: bytes, required_headers: list[str] | None = None, header_row_index: int | None = 0,
|
|
24
|
-
seperator: str = ","
|
|
24
|
+
seperator: str = ",") -> tuple[list[dict[str, str]], list[list[str]]]:
|
|
25
25
|
"""
|
|
26
26
|
Tokenize a CSV file. The provided file must be uniform. That is, if row 1 has 10 cells, all the rows in the file
|
|
27
27
|
must have 10 cells. Otherwise, the Pandas parser throws a tokenizer exception.
|
|
@@ -34,17 +34,13 @@ class FileUtil:
|
|
|
34
34
|
meaning that required headers are also ignored if any are provided. By default, the first row (0th index)
|
|
35
35
|
is assumed to be the header row.
|
|
36
36
|
:param seperator: The character that separates cells in the table.
|
|
37
|
-
:param encoding: The encoding used to read the given file bytes. If not provided, uses utf-8. If your file
|
|
38
|
-
contains a non-utf-8 character, then a UnicodeDecodeError will be thrown. If this happens, consider using
|
|
39
|
-
ISO-8859-1 as the encoding.
|
|
40
37
|
:return: The CSV parsed into a list of dicts where each dict is a row, mapping the headers to the cells for
|
|
41
38
|
that row. Also returns a list of each row above the headers (the metadata), parsed into a list of each cell.
|
|
42
39
|
If the header row index is 0 or None, this list will be empty.
|
|
43
40
|
"""
|
|
44
41
|
# Parse the file bytes into two DataFrames. The first is metadata of the file located above the header row,
|
|
45
42
|
# while the second is the body of the file below the header row.
|
|
46
|
-
file_body, file_metadata = FileUtil.csv_to_data_frames(file_bytes, header_row_index, seperator
|
|
47
|
-
encoding=encoding)
|
|
43
|
+
file_body, file_metadata = FileUtil.csv_to_data_frames(file_bytes, header_row_index, seperator)
|
|
48
44
|
# Parse the metadata from above the header row index into a list of lists.
|
|
49
45
|
metadata: list[list[str]] = FileUtil.data_frame_to_lists(file_metadata)
|
|
50
46
|
# Parse the data from the file body into a list of dicts.
|
|
@@ -78,8 +74,8 @@ class FileUtil:
|
|
|
78
74
|
return rows, metadata
|
|
79
75
|
|
|
80
76
|
@staticmethod
|
|
81
|
-
def csv_to_data_frames(file_bytes: bytes, header_row_index: int | None = 0, seperator: str = ","
|
|
82
|
-
|
|
77
|
+
def csv_to_data_frames(file_bytes: bytes, header_row_index: int | None = 0, seperator: str = ",") \
|
|
78
|
+
-> tuple[DataFrame, DataFrame | None]:
|
|
83
79
|
"""
|
|
84
80
|
Parse the file bytes for a CSV into DataFrames. The provided file must be uniform. That is, if row 1 has 10
|
|
85
81
|
cells, all the rows in the file must have 10 cells. Otherwise, the Pandas parser throws a tokenizer exception.
|
|
@@ -90,9 +86,6 @@ class FileUtil:
|
|
|
90
86
|
meaning that required headers are also ignored if any are provided. By default, the first row (0th index)
|
|
91
87
|
is assumed to be the header row.
|
|
92
88
|
:param seperator: The character that separates cells in the table.
|
|
93
|
-
:param encoding: The encoding used to read the given file bytes. If not provided, uses utf-8. If your file
|
|
94
|
-
contains a non-utf-8 character, then a UnicodeDecodeError will be thrown. If this happens, consider using
|
|
95
|
-
ISO-8859-1 as the encoding.
|
|
96
89
|
:return: A tuple of two DataFrames. The first is the frame for the CSV table body, while the second is for the
|
|
97
90
|
metadata from above the header row, or None if there is no metadata.
|
|
98
91
|
"""
|
|
@@ -104,13 +97,13 @@ class FileUtil:
|
|
|
104
97
|
# can throw off the header row index.
|
|
105
98
|
file_metadata = pandas.read_csv(file_io, header=None, dtype=dtype(str),
|
|
106
99
|
skiprows=lambda x: x >= header_row_index,
|
|
107
|
-
skip_blank_lines=False, sep=seperator
|
|
100
|
+
skip_blank_lines=False, sep=seperator)
|
|
108
101
|
with io.BytesIO(file_bytes) as file_io:
|
|
109
102
|
# The use of the dtype argument is to ensure that everything from the file gets read as a string. Added
|
|
110
103
|
# because some numerical values would get ".0" appended to them, even when casting the DataFrame cell to a
|
|
111
104
|
# string.
|
|
112
105
|
file_body: DataFrame = pandas.read_csv(file_io, header=header_row_index, dtype=dtype(str),
|
|
113
|
-
skip_blank_lines=False, sep=seperator
|
|
106
|
+
skip_blank_lines=False, sep=seperator)
|
|
114
107
|
|
|
115
108
|
return file_body, file_metadata
|
|
116
109
|
|