Skip to content

Commit 6f3a2ef

Browse files
authored
Merge pull request #417 from AllenNeuralDynamics/release-v2.1.1
Release v2.1.1
2 parents fa8f56e + 24aca6e commit 6f3a2ef

10 files changed

Lines changed: 98 additions & 46 deletions

File tree

src/aind_data_transfer_service/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import yaml
99
from pythonjsonlogger import json as log_json
1010

11-
__version__ = "2.1.0"
11+
__version__ = "2.1.1"
1212

1313

1414
# We want to standardize the timestamp format to UTC and ISO-8601, which

src/aind_data_transfer_service/models/core.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ def __init__(self, /, **data: Any) -> None:
141141
user_email: Optional[EmailStr] = Field(
142142
default=None,
143143
description=(
144-
"Optional email address to receive job status notifications"
144+
"Email address to receive job status notifications. "
145+
"If not set, will use email from SubmitJobRequestV2."
145146
),
146147
)
147148
email_notification_types: Optional[
@@ -284,10 +285,10 @@ def __init__(self, /, **data: Any) -> None:
284285
model_config = ConfigDict(use_enum_values=True, extra="ignore")
285286

286287
dag_id: Literal["transform_and_upload_v2"] = "transform_and_upload_v2"
287-
user_email: EmailStr = Field(
288-
...,
288+
user_email: Optional[EmailStr] = Field(
289+
default=None,
289290
description=(
290-
"Required email address to receive job status notifications"
291+
"Email address to use if not set in individual job configs"
291292
),
292293
)
293294
email_notification_types: Set[
@@ -311,7 +312,13 @@ def propagate_email_settings(self):
311312
global_email_user = self.user_email
312313
global_email_notification_types = self.email_notification_types
313314
for upload_job in self.upload_jobs:
314-
if global_email_user is not None and upload_job.user_email is None:
315+
if upload_job.user_email is None and global_email_user is None:
316+
raise ValueError(
317+
f"No user_email set for job {upload_job.s3_prefix}. "
318+
"Either set user_email in the job config or in the "
319+
"SubmitJobRequestV2."
320+
)
321+
elif upload_job.user_email is None:
315322
upload_job.user_email = global_email_user
316323
if upload_job.email_notification_types is None:
317324
upload_job.email_notification_types = (

tests/resources/legacy_sample.csv

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
project_name, modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime, force_cloud_sync, modality0.process_capsule_id, job_type
2-
Ephys Platform, ecephys, dir/data_set_1, ,, private, 123454, ecephys, 2020-10-10 14:10:10, True,, default
3-
Behavior Platform, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open, 123456, behavior, 10/13/2020 1:10:10 PM,,1f999652-00a0-4c4b-99b5-64c2985ad070, default
4-
Behavior Platform, behavior-videos, dir/data_set_2, behavior-videos, dir/data_set_3, default, 123456, behavior, 10/13/2020 1:10:10 PM,,, default
1+
project_name, modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime, force_cloud_sync, modality0.process_capsule_id, job_type, user_email
2+
Ephys Platform, ecephys, dir/data_set_1, ,, private, 123454, ecephys, 2020-10-10 14:10:10, True,, default, test@example.com
3+
Behavior Platform, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open, 123456, behavior, 10/13/2020 1:10:10 PM,,1f999652-00a0-4c4b-99b5-64c2985ad070, default, test@example.com
4+
Behavior Platform, behavior-videos, dir/data_set_2, behavior-videos, dir/data_set_3, default, 123456, behavior, 10/13/2020 1:10:10 PM,,, default, test@example.com

tests/resources/legacy_sample2.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
project_name, modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime, modality0.pipeline_id, job_type
2-
Behavior Platform, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open, 123456, behavior, 10/13/2020 1:10:10 PM,1f999652-00a0-4c4b-99b5-64c2985ad070, default
1+
project_name, modality0, modality0.source, modality1, modality1.source, s3-bucket, subject-id, platform, acq-datetime, modality0.pipeline_id, job_type, user_email
2+
Behavior Platform, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open, 123456, behavior, 10/13/2020 1:10:10 PM,1f999652-00a0-4c4b-99b5-64c2985ad070, default, test@example.com

tests/resources/nested_sample.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
project_name, modality0, modality0.input_source, modality0.compression_requested.compression_enum, subject-id, platform, acq-datetime, job_type
2-
Ephys Platform, ecephys, dir/data_set_1, gamma fix colorspace, 123454, ecephys, 2020-10-10 14:10:10, ecephys
1+
project_name, modality0, modality0.input_source, modality0.compression_requested.compression_enum, subject-id, platform, acq-datetime, job_type, user_email
2+
Ephys Platform, ecephys, dir/data_set_1, gamma fix colorspace, 123454, ecephys, 2020-10-10 14:10:10, ecephys, test@example.com

tests/resources/new_sample.csv

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
project_name, modality0, modality0.input_source, modality1, modality1.input_source, s3-bucket, subject-id, platform, acq-datetime, job_type, metadata_dir
2-
Ephys Platform, ecephys, dir/data_set_1, , , default,123454, ecephys, 2020-10-10 14:10:10, ecephys, dir/metadata
3-
Behavior Platform, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open,123456, behavior, 2020-10-13T13:10:10, custom,
4-
Behavior Platform, behavior-videos, dir/data_set_2, , , ,123456, behavior, 2020-10-13 13:10:10, default,
1+
project_name, modality0, modality0.input_source, modality1, modality1.input_source, s3-bucket, subject-id, platform, acq-datetime, job_type, metadata_dir, user_email
2+
Ephys Platform, ecephys, dir/data_set_1, , , default,123454, ecephys, 2020-10-10 14:10:10, ecephys, dir/metadata, test@example.com
3+
Behavior Platform, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open,123456, behavior, 2020-10-13T13:10:10, custom,, test@example.com
4+
Behavior Platform, behavior-videos, dir/data_set_2, , , ,123456, behavior, 2020-10-13 13:10:10, default,, test@example.com
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
project_name, modality0, modality0.input_source, modality1, modality1.input_source, s3-bucket, subject-id, platform, acq-datetime, job_type, metadata_dir
2-
Ephys Platform, ecephys, dir/data_set_1, , , default,123454, ecephys, 2020-10-10 14:10:10, ecephys, dir/metadata
3-
Behavior Platform, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open,123456, behavior, 2020-10-13T13:10:10, custom,
4-
Behavior Platform, behavior-videos, dir/data_set_2, , , ,123456, behavior, 2020-10-13 13:10:10, default,
5-
,,,,,,,,,,
6-
,,,,,,,,,,
7-
,,,,,,,,,,
1+
project_name, modality0, modality0.input_source, modality1, modality1.input_source, s3-bucket, subject-id, platform, acq-datetime, job_type, metadata_dir, user_email
2+
Ephys Platform, ecephys, dir/data_set_1, , , default,123454, ecephys, 2020-10-10 14:10:10, ecephys, dir/metadata, test@example.com
3+
Behavior Platform, behavior-videos, dir/data_set_2, MRI, dir/data_set_3, open,123456, behavior, 2020-10-13T13:10:10, custom,, test@example.com
4+
Behavior Platform, behavior-videos, dir/data_set_2, , , ,123456, behavior, 2020-10-13 13:10:10, default,, test@example.com
5+
,,,,,,,,,,,
6+
,,,,,,,,,,,
7+
,,,,,,,,,,,

tests/test_core.py

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def setUpClass(cls) -> None:
8989
"""Set up test class"""
9090
example_configs = UploadJobConfigsV2(
9191
job_type="default",
92+
user_email="test@example.com",
9293
project_name="Behavior Platform",
9394
platform=Platform.BEHAVIOR,
9495
modalities=[Modality.BEHAVIOR_VIDEOS],
@@ -315,6 +316,7 @@ def setUpClass(cls) -> None:
315316
"""Set up example configs to be used in tests"""
316317
example_upload_config = UploadJobConfigsV2(
317318
job_type="default",
319+
user_email="test@example.com",
318320
project_name="Behavior Platform",
319321
platform=Platform.BEHAVIOR,
320322
modalities=[
@@ -351,7 +353,7 @@ def test_max_length(self):
351353
with self.assertRaises(ValidationError) as e:
352354
SubmitJobRequestV2(
353355
upload_jobs=[upload_job for _ in range(0, 51)],
354-
user_email="abc@example.com"
356+
user_email="abc@example.com",
355357
)
356358
expected_message = (
357359
"List should have at most 50 items after validation, not 51"
@@ -366,8 +368,7 @@ def test_default_settings(self):
366368
**self.example_upload_config.model_dump(round_trip=True)
367369
)
368370
job_settings = SubmitJobRequestV2(
369-
upload_jobs=[upload_job],
370-
user_email="abc@example.com"
371+
upload_jobs=[upload_job], user_email="abc@example.com"
371372
)
372373
self.assertEqual("abc@example.com", job_settings.user_email)
373374
self.assertEqual({"fail"}, job_settings.email_notification_types)
@@ -417,28 +418,29 @@ def test_email_validation(self):
417418
self.assertIn(expected_error_message, actual_error_message)
418419

419420
def test_propagate_email_settings(self):
420-
"""Tests global email settings is propagated to individual jobs."""
421+
"""Tests that global email is propagated to jobs without email,
422+
and email notification types are propagated to jobs without them."""
421423
example_job_configs = self.example_upload_config.model_dump(
422424
exclude={"user_email", "email_notification_types"}, round_trip=True
423425
)
424426
new_job = UploadJobConfigsV2(
425-
user_email="xyz@example.org",
426427
email_notification_types=["all"],
427428
**example_job_configs,
428429
)
429430
job_settings = SubmitJobRequestV2(
430-
user_email="abc@example.org",
431+
user_email="global@example.com",
431432
email_notification_types={"begin", "fail"},
432433
upload_jobs=[
433434
new_job,
434435
UploadJobConfigsV2(**example_job_configs),
435436
],
436437
)
438+
self.assertEqual("global@example.com", job_settings.user_email)
437439
self.assertEqual(
438-
"xyz@example.org", job_settings.upload_jobs[0].user_email
440+
"global@example.com", job_settings.upload_jobs[0].user_email
439441
)
440442
self.assertEqual(
441-
"abc@example.org", job_settings.upload_jobs[1].user_email
443+
"global@example.com", job_settings.upload_jobs[1].user_email
442444
)
443445
self.assertEqual(
444446
{"all"}, job_settings.upload_jobs[0].email_notification_types
@@ -448,6 +450,44 @@ def test_propagate_email_settings(self):
448450
job_settings.upload_jobs[1].email_notification_types,
449451
)
450452

453+
def test_propagate_email_from_job(self):
454+
"""Tests that global email is set from first job if not provided."""
455+
example_job_configs = self.example_upload_config.model_dump(
456+
exclude={"email_notification_types"}, round_trip=True
457+
)
458+
job_settings = SubmitJobRequestV2(
459+
user_email=None,
460+
email_notification_types={"begin", "fail"},
461+
upload_jobs=[
462+
UploadJobConfigsV2(**example_job_configs),
463+
],
464+
)
465+
self.assertEqual(
466+
"test@example.com", job_settings.upload_jobs[0].user_email
467+
)
468+
469+
def test_missing_email_validation(self):
470+
"""Tests that validation error is raised when no email is provided."""
471+
example_job_configs = self.example_upload_config.model_dump(
472+
exclude={"user_email", "email_notification_types"}, round_trip=True
473+
)
474+
with self.assertRaises(ValidationError) as e:
475+
SubmitJobRequestV2(
476+
user_email=None,
477+
upload_jobs=[UploadJobConfigsV2(**example_job_configs)],
478+
)
479+
errors = json.loads(e.exception.json())
480+
self.assertEqual(1, len(errors))
481+
self.assertIn(
482+
"No user_email set for job",
483+
errors[0]["msg"],
484+
)
485+
self.assertIn(
486+
"Either set user_email in the job config or in the "
487+
"SubmitJobRequestV2",
488+
errors[0]["msg"],
489+
)
490+
451491
def test_check_duplicate_upload_jobs(self):
452492
"""Tests that duplicate upload jobs are not allowed."""
453493
job_configs = self.example_upload_config.model_dump(
@@ -459,8 +499,7 @@ def test_check_duplicate_upload_jobs(self):
459499
]
460500
with self.assertRaises(ValidationError) as e:
461501
SubmitJobRequestV2(
462-
upload_jobs=upload_jobs,
463-
user_email="abc@example.com"
502+
upload_jobs=upload_jobs, user_email="abc@example.com"
464503
)
465504
errors = json.loads(e.exception.json())
466505
self.assertEqual(1, len(errors))
@@ -489,8 +528,7 @@ def test_check_duplicate_upload_jobs_same_prefix(self):
489528
}
490529
upload_jobs.append(UploadJobConfigsV2(**job_configs, tasks=tasks))
491530
submit_job_request = SubmitJobRequestV2(
492-
upload_jobs=upload_jobs,
493-
user_email="abc@example.com"
531+
upload_jobs=upload_jobs, user_email="abc@example.com"
494532
)
495533
self.assertEqual(10, len(submit_job_request.upload_jobs))
496534

@@ -504,7 +542,7 @@ def test_current_jobs_validation(self):
504542
UploadJobConfigsV2(**job_configs, subject_id=subject_id)
505543
for subject_id in ["123456", "123457", "123458"]
506544
],
507-
user_email="abc@example.com"
545+
user_email="abc@example.com",
508546
)
509547
current_jobs = [
510548
j.model_dump(mode="json", exclude_none=True)
@@ -513,8 +551,7 @@ def test_current_jobs_validation(self):
513551
new_job = UploadJobConfigsV2(**job_configs, subject_id="123459")
514552
with validation_context({"current_jobs": current_jobs}):
515553
submit_job_request = SubmitJobRequestV2(
516-
upload_jobs=[new_job],
517-
user_email="abc@example.com"
554+
upload_jobs=[new_job], user_email="abc@example.com"
518555
)
519556
self.assertEqual(1, len(submit_job_request.upload_jobs))
520557
self.assertEqual(
@@ -526,7 +563,7 @@ def test_current_jobs_validation_fail(self):
526563
"""Tests job validation when an upload_job is already running."""
527564
submitted_job_request = SubmitJobRequestV2(
528565
upload_jobs=[self.example_upload_config],
529-
user_email="abc@example.com"
566+
user_email="abc@example.com",
530567
)
531568
current_jobs_1 = [
532569
j.model_dump(mode="json", exclude_none=True)
@@ -540,7 +577,7 @@ def test_current_jobs_validation_fail(self):
540577
with validation_context({"current_jobs": current_jobs}):
541578
SubmitJobRequestV2(
542579
upload_jobs=[self.example_upload_config],
543-
user_email="abc@example.com"
580+
user_email="abc@example.com",
544581
)
545582
err_msg = json.loads(err.exception.json())[0]["msg"]
546583
self.assertEqual(

tests/test_csv_handler.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def test_map_csv_row_to_job(self):
5858
expected_jobs = [
5959
UploadJobConfigsV2(
6060
project_name="Ephys Platform",
61+
user_email="test@example.com",
6162
s3_bucket="default",
6263
platform=Platform.ECEPHYS,
6364
modalities=[Modality.ECEPHYS],
@@ -77,6 +78,7 @@ def test_map_csv_row_to_job(self):
7778
),
7879
UploadJobConfigsV2(
7980
project_name="Behavior Platform",
81+
user_email="test@example.com",
8082
s3_bucket="open",
8183
platform=Platform.BEHAVIOR,
8284
modalities=[Modality.BEHAVIOR_VIDEOS, Modality.MRI],
@@ -96,6 +98,7 @@ def test_map_csv_row_to_job(self):
9698
),
9799
UploadJobConfigsV2(
98100
project_name="Behavior Platform",
101+
user_email="test@example.com",
99102
platform=Platform.BEHAVIOR,
100103
modalities=[Modality.BEHAVIOR_VIDEOS],
101104
tasks={
@@ -123,6 +126,7 @@ def test_map_nested_csv_row_to_job(self):
123126
expected_jobs = [
124127
UploadJobConfigsV2(
125128
job_type="ecephys",
129+
user_email="test@example.com",
126130
s3_bucket="default",
127131
project_name="Ephys Platform",
128132
platform=Platform.ECEPHYS,
@@ -159,6 +163,7 @@ def test_map_legacy_csv_row_to_job(self):
159163
expected_jobs = [
160164
UploadJobConfigsV2(
161165
job_type="default",
166+
user_email="test@example.com",
162167
s3_bucket="private",
163168
project_name="Ephys Platform",
164169
platform=Platform.ECEPHYS,
@@ -180,6 +185,7 @@ def test_map_legacy_csv_row_to_job(self):
180185
),
181186
UploadJobConfigsV2(
182187
job_type="default",
188+
user_email="test@example.com",
183189
s3_bucket="open",
184190
project_name="Behavior Platform",
185191
platform=Platform.BEHAVIOR,
@@ -217,6 +223,7 @@ def test_map_legacy_csv_row_to_job(self):
217223
),
218224
UploadJobConfigsV2(
219225
job_type="default",
226+
user_email="test@example.com",
220227
s3_bucket="default",
221228
project_name="Behavior Platform",
222229
platform=Platform.BEHAVIOR,
@@ -248,6 +255,7 @@ def test_map_old_csv_row_to_job(self):
248255
expected_jobs = [
249256
UploadJobConfigsV2(
250257
job_type="default",
258+
user_email="test@example.com",
251259
s3_bucket="open",
252260
project_name="Behavior Platform",
253261
platform=Platform.BEHAVIOR,

tests/test_server.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ def setUpClass(cls) -> None:
134134
)
135135
example_configs_v2 = UploadJobConfigsV2(
136136
job_type=job_type,
137+
user_email="test@example.com",
137138
project_name=project_name,
138139
platform=platform,
139140
subject_id=subject_id,
@@ -1598,8 +1599,7 @@ def test_submit_v1_v2_jobs_200(
15981599
)
15991600
mock_post.return_value = mock_response
16001601
job_request_v2 = SubmitJobRequestV2(
1601-
upload_jobs=[self.example_configs_v2],
1602-
user_email="abc@example.com"
1602+
upload_jobs=[self.example_configs_v2], user_email="abc@example.com"
16031603
)
16041604
request_json_v2 = job_request_v2.model_dump(mode="json")
16051605
with self.assertLogs(level="INFO") as captured:
@@ -1686,6 +1686,7 @@ def test_submit_v1_v2_jobs_exception_500(
16861686
"upload_jobs": [
16871687
{
16881688
"job_type": "ecephys",
1689+
"user_email": "abc@example.com",
16891690
"project_name": "Ephys Platform",
16901691
"platform": {
16911692
"name": "Electrophysiology platform",
@@ -1863,8 +1864,7 @@ def test_validate_json_v2_invalid_current(
18631864
mock_get_job_types.return_value = ["ecephys"]
18641865
# assume a job is already running
18651866
job_request = SubmitJobRequestV2(
1866-
upload_jobs=[self.example_configs_v2],
1867-
user_email="abc@example.com"
1867+
upload_jobs=[self.example_configs_v2], user_email="abc@example.com"
18681868
).model_dump(mode="json", exclude_none=True)
18691869
current_job = job_request["upload_jobs"][0]
18701870
airflow_response = {

0 commit comments

Comments
 (0)