-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcsv_handler.py
More file actions
174 lines (159 loc) · 5.74 KB
/
Copy pathcsv_handler.py
File metadata and controls
174 lines (159 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
"""Module to handle processing legacy csv files"""
import re
from collections.abc import Mapping
from copy import deepcopy
from datetime import datetime
from typing import Any, Dict
from aind_data_schema_models.modalities import Modality
from aind_data_transfer_service.configs.platforms_v1 import Platform
from aind_data_transfer_service.models.core import Task, UploadJobConfigsV2
DATETIME_PATTERN2 = re.compile(
r"^\d{1,2}/\d{1,2}/\d{4} \d{1,2}:\d{2}:\d{2} [APap][Mm]$"
)
def nested_update(dict_to_update: Dict[str, Any], updates: Mapping):
"""
Update a nested dictionary in-place.
Parameters
----------
dict_to_update : Dict[str, Any]
updates : Mapping
"""
for k, v in updates.items():
if isinstance(v, Mapping):
dict_to_update[k] = nested_update(dict_to_update.get(k, {}), v)
else:
dict_to_update[k] = v
return dict_to_update
def create_nested_dict(
dict_to_update: Dict[str, Any], key_string: str, value: Any
):
"""
Updates in-place a nested dictionary with a period delimited key and value.
Parameters
----------
dict_to_update : Dict[str, Any]
key_string : str
value : Any
"""
keys = key_string.split(".", 1)
current_key = keys[0]
if len(keys) == 1:
dict_to_update[current_key] = value
else:
if current_key not in dict_to_update:
dict_to_update[current_key] = dict()
create_nested_dict(dict_to_update[current_key], keys[1], value)
def map_csv_row_to_job(row: dict) -> UploadJobConfigsV2:
"""
Maps csv row into a UploadJobConfigsV2 model. This attempts to be somewhat
backwards compatible with previous csv files.
Parameters
----------
row : dict
Returns
-------
UploadJobConfigsV2
"""
modality_configs = dict()
job_configs = dict()
check_s3_folder_exists_task = None
codeocean_tasks = dict()
for key, value in row.items():
# Strip white spaces and replace dashes with underscores
clean_key = str(key).strip(" ").replace("-", "_")
clean_val = str(value).strip(" ")
# Check empty strings or None values
if clean_val is None or clean_val == "":
continue
if clean_key.startswith("modality"):
modality_parts = clean_key.split(".")
modality_key = modality_parts[0]
sub_key = (
"modality"
if len(modality_parts) == 1
else ".".join(modality_parts[1:])
)
modality_configs.setdefault(modality_key, dict())
# Temp backwards compatibility check
if sub_key == "source":
sub_key = "input_source"
if sub_key in ["process_capsule_id", "capsule_id", "pipeline_id"]:
if sub_key == "pipeline_id":
codeocean_pipeline_monitor_settings = {
"pipeline_monitor_settings": {
"run_params": {"pipeline_id": clean_val}
}
}
else:
codeocean_pipeline_monitor_settings = {
"pipeline_monitor_settings": {
"run_params": {"capsule_id": clean_val}
}
}
codeocean_tasks[modality_key] = Task(
skip_task=False,
job_settings=codeocean_pipeline_monitor_settings,
)
else:
nested_val = dict()
create_nested_dict(
dict_to_update=nested_val,
key_string=sub_key,
value=clean_val,
)
current_dict = deepcopy(
modality_configs.get(modality_key, dict())
)
nested_update(current_dict, nested_val)
modality_configs[modality_key] = current_dict
elif clean_key == "force_cloud_sync" and clean_val.upper() in [
"TRUE",
"T",
]:
check_s3_folder_exists_task = {"skip_task": True}
else:
job_configs[clean_key] = clean_val
# Rename codeocean config keys with correct modality
keys = list(codeocean_tasks.keys())
for key in keys:
modality_abbreviation = modality_configs[key]["modality"]
codeocean_tasks[modality_abbreviation] = codeocean_tasks.pop(key)
# Create Tasks from parsed configs
modality_tasks = {
m.pop("modality"): Task(job_settings=m)
for m in modality_configs.values()
if m.get("modality") is not None
}
metadata_task = (
Task(job_settings={"metadata_dir": job_configs.pop("metadata_dir")})
if "metadata_dir" in job_configs
else None
)
tasks = {
"gather_preliminary_metadata": metadata_task,
"check_s3_folder_exists": check_s3_folder_exists_task,
"modality_transformation_settings": modality_tasks,
"codeocean_pipeline_settings": (
None if codeocean_tasks == dict() else codeocean_tasks
),
}
platform = (
None
if job_configs.get("platform") is None
else Platform.from_abbreviation(job_configs["platform"])
)
job_configs.update(
{
"platform": platform,
"modalities": [
Modality.from_abbreviation(m) for m in modality_tasks.keys()
],
"tasks": {k: v for k, v in tasks.items() if v is not None},
}
)
acq_dt = job_configs.get("acq_datetime")
if acq_dt is not None and re.match(DATETIME_PATTERN2, acq_dt):
job_configs["acq_datetime"] = datetime.strptime(
acq_dt, "%m/%d/%Y %I:%M:%S %p"
)
return UploadJobConfigsV2(**job_configs)