-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat 50: Dynamically generate job template (#73)
* generate, save, and download job template xlsx * update UI link, add error handling * unit tests for download job template * update /api/job_upload_template to use StreamingResponse * create job_template_configs.py * create JobUploadTemplate class to handle template creation * remove old upload_template_link * add test suite for JobUploadTemplate and update unit tests for download endpoint * code cleanup * cleanup openpyxl * address PR comments
- Loading branch information
1 parent
eb0e062
commit 21760c1
Showing
7 changed files
with
255 additions
and
8 deletions.
There are no files selected for viewing
116 changes: 116 additions & 0 deletions
116
src/aind_data_transfer_service/configs/job_upload_template.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
"""Module to configure and create xlsx job upload template""" | ||
import datetime | ||
from io import BytesIO | ||
|
||
from aind_data_schema.core.data_description import Modality, Platform | ||
from openpyxl import Workbook | ||
from openpyxl.styles import Font | ||
from openpyxl.utils import get_column_letter | ||
from openpyxl.worksheet.datavalidation import DataValidation | ||
|
||
|
||
# TODO: convert to pydantic model | ||
class JobUploadTemplate: | ||
"""Class to configure and create xlsx job upload template""" | ||
|
||
FILE_NAME = "job_upload_template.xlsx" | ||
HEADERS = [ | ||
"platform", | ||
"acq_datetime", | ||
"subject_id", | ||
"s3_bucket", | ||
"modality0", | ||
"modality0.source", | ||
"modality1", | ||
"modality1.source", | ||
] | ||
SAMPLE_JOBS = [ | ||
[ | ||
Platform.BEHAVIOR.abbreviation, | ||
datetime.datetime(2023, 10, 4, 4, 0, 0), | ||
"123456", | ||
"aind-behavior-data", | ||
Modality.BEHAVIOR_VIDEOS.abbreviation, | ||
"/allen/aind/stage/fake/dir", | ||
Modality.BEHAVIOR.abbreviation, | ||
"/allen/aind/stage/fake/dir", | ||
], | ||
[ | ||
Platform.SMARTSPIM.abbreviation, | ||
datetime.datetime(2023, 3, 4, 16, 30, 0), | ||
"654321", | ||
"aind-open-data", | ||
Modality.SPIM.abbreviation, | ||
"/allen/aind/stage/fake/dir", | ||
], | ||
[ | ||
Platform.ECEPHYS.abbreviation, | ||
datetime.datetime(2023, 1, 30, 19, 1, 0), | ||
"654321", | ||
"aind-ephys-data", | ||
Modality.ECEPHYS.abbreviation, | ||
"/allen/aind/stage/fake/dir", | ||
Modality.BEHAVIOR_VIDEOS.abbreviation, | ||
"/allen/aind/stage/fake/dir", | ||
], | ||
] | ||
VALIDATORS = [ | ||
{ | ||
"name": "platform", | ||
"options": [p().abbreviation for p in Platform._ALL], | ||
"ranges": ["A2:A20"], | ||
}, | ||
{ | ||
"name": "modality", | ||
"options": [m().abbreviation for m in Modality._ALL], | ||
"ranges": ["E2:E20", "G2:G20"], | ||
}, | ||
{ | ||
"name": "s3_bucket", | ||
"options": [ | ||
"aind-ephys-data", | ||
"aind-ophys-data", | ||
"aind-behavior-data", | ||
"aind-private-data", | ||
], | ||
"ranges": ["D2:D20"], | ||
}, | ||
] | ||
|
||
@staticmethod | ||
def create_job_template(): | ||
"""Create job template as xlsx filestream""" | ||
# job template | ||
xl_io = BytesIO() | ||
workbook = Workbook() | ||
worksheet = workbook.active | ||
worksheet.append(JobUploadTemplate.HEADERS) | ||
for job in JobUploadTemplate.SAMPLE_JOBS: | ||
worksheet.append(job) | ||
# data validators | ||
for validator in JobUploadTemplate.VALIDATORS: | ||
dv = DataValidation( | ||
type="list", | ||
formula1=f'"{(",").join(validator["options"])}"', | ||
allow_blank=True, | ||
showErrorMessage=True, | ||
showInputMessage=True, | ||
) | ||
dv.promptTitle = validator["name"] | ||
dv.prompt = f'Select a {validator["name"]} from the dropdown' | ||
dv.error = f'Invalid {validator["name"]}.' | ||
for r in validator["ranges"]: | ||
dv.add(r) | ||
worksheet.add_data_validation(dv) | ||
# formatting | ||
bold = Font(bold=True) | ||
for header in worksheet["A1:H1"]: | ||
for cell in header: | ||
cell.font = bold | ||
worksheet.column_dimensions[ | ||
get_column_letter(cell.column) | ||
].auto_size = True | ||
# save file | ||
workbook.save(xl_io) | ||
workbook.close() | ||
return xl_io |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
"""Module to test job upload template configs and generation""" | ||
|
||
import os | ||
import unittest | ||
from pathlib import Path | ||
|
||
from openpyxl import load_workbook | ||
|
||
from aind_data_transfer_service.configs.job_upload_template import ( | ||
JobUploadTemplate, | ||
) | ||
|
||
TEST_DIRECTORY = Path(os.path.dirname(os.path.realpath(__file__))) | ||
SAMPLE_JOB_TEMPLATE = TEST_DIRECTORY / "resources" / "job_upload_template.xlsx" | ||
|
||
|
||
class TestJobUploadTemplate(unittest.TestCase): | ||
"""Tests job upload template class""" | ||
|
||
def read_xl_helper(self, source, return_validators=False): | ||
"""Helper function to read xlsx contents and validators""" | ||
lines = [] | ||
workbook = load_workbook(source, read_only=(not return_validators)) | ||
worksheet = workbook.active | ||
for row in worksheet.rows: | ||
row_contents = [cell.value for cell in row] | ||
lines.append(row_contents) | ||
if return_validators: | ||
validators = [] | ||
for dv in worksheet.data_validations.dataValidation: | ||
validators.append( | ||
{ | ||
"name": dv.promptTitle, | ||
"options": dv.formula1.strip('"').split(","), | ||
"ranges": str(dv.cells).split(" "), | ||
} | ||
) | ||
result = (lines, validators) | ||
else: | ||
result = lines | ||
workbook.close() | ||
return result | ||
|
||
def test_create_job_template(self): | ||
"""Tests that xlsx job template is created with | ||
correct contents and validators""" | ||
expected_lines = self.read_xl_helper(SAMPLE_JOB_TEMPLATE) | ||
(template_lines, template_validators) = self.read_xl_helper( | ||
JobUploadTemplate.create_job_template(), True | ||
) | ||
self.assertEqual(expected_lines, template_lines) | ||
self.assertCountEqual( | ||
JobUploadTemplate.VALIDATORS, template_validators | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters