From 9fc81a44a6d9d7a4831bbfb1c5e21b92be230684 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Fri, 15 Dec 2023 14:51:28 -0500 Subject: [PATCH 01/21] Rip out embargoed models --- dandiapi/analytics/tasks/__init__.py | 12 +- dandiapi/api/admin.py | 19 +- dandiapi/api/asset_paths.py | 27 +- dandiapi/api/migrations/0001_initial_v2.py | 8 +- ...ve_embargoedassetblob_dandiset_and_more.py | 91 ++++++ dandiapi/api/models/__init__.py | 6 +- dandiapi/api/models/asset.py | 90 ++---- dandiapi/api/models/upload.py | 98 ++---- dandiapi/api/services/asset/__init__.py | 21 +- dandiapi/api/services/embargo/__init__.py | 55 ++-- dandiapi/api/storage.py | 8 - dandiapi/api/tasks/__init__.py | 11 +- dandiapi/api/tasks/scheduled.py | 2 +- dandiapi/api/tests/factories.py | 21 +- dandiapi/api/tests/test_asset.py | 26 +- dandiapi/api/tests/test_dandiset.py | 300 +++++++++--------- dandiapi/api/tests/test_embargo.py | 2 +- dandiapi/api/tests/test_stats.py | 4 +- dandiapi/api/tests/test_tasks.py | 112 +++---- dandiapi/api/tests/test_unembargo.py | 70 ++-- dandiapi/api/tests/test_upload.py | 12 +- dandiapi/api/tests/test_version.py | 87 ++--- dandiapi/api/views/asset.py | 55 ++-- dandiapi/api/views/dandiset.py | 1 - dandiapi/api/views/serializers.py | 22 +- dandiapi/api/views/upload.py | 53 +--- dandiapi/conftest.py | 24 +- dandiapi/settings.py | 4 - dandiapi/zarr/models.py | 6 +- .../zarr/tests/test_ingest_zarr_archive.py | 2 +- dev/.env.docker-compose | 1 - dev/.env.docker-compose-native | 1 - 32 files changed, 555 insertions(+), 696 deletions(-) create mode 100644 dandiapi/api/migrations/0008_remove_embargoedassetblob_dandiset_and_more.py diff --git a/dandiapi/analytics/tasks/__init__.py b/dandiapi/analytics/tasks/__init__.py index fe0c3cdc7..d43e89d90 100644 --- a/dandiapi/analytics/tasks/__init__.py +++ b/dandiapi/analytics/tasks/__init__.py @@ -13,8 +13,8 @@ from s3logparse import s3logparse from dandiapi.analytics.models import ProcessedS3Log -from dandiapi.api.models.asset import AssetBlob, EmbargoedAssetBlob -from dandiapi.api.storage import get_boto_client, get_embargo_storage, get_storage +from dandiapi.api.models.asset import AssetBlob +from dandiapi.api.storage import get_boto_client, get_storage if TYPE_CHECKING: from collections.abc import Generator @@ -31,8 +31,7 @@ def _bucket_objects_after(bucket: str, after: str | None) -> Generator[dict, Non settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME, }: raise ValueError(f'Non-log bucket: {bucket}') - embargoed = bucket == settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME - s3 = get_boto_client(get_storage() if not embargoed else get_embargo_storage()) + s3 = get_boto_client(get_storage()) kwargs = {} if after: kwargs['StartAfter'] = after @@ -80,8 +79,7 @@ def process_s3_log_file_task(bucket: LogBucket, s3_log_key: str) -> None: if ProcessedS3Log.objects.filter(name=s3_log_key.split('/')[-1], embargoed=embargoed).exists(): return - s3 = get_boto_client(get_storage() if not embargoed else get_embargo_storage()) - BlobModel = AssetBlob if not embargoed else EmbargoedAssetBlob # noqa: N806 + s3 = get_boto_client(get_storage()) data = s3.get_object(Bucket=bucket, Key=s3_log_key) download_counts = Counter() @@ -107,6 +105,6 @@ def process_s3_log_file_task(bucket: LogBucket, s3_log_key: str) -> None: # multiple log files trying to update the same blobs. this serialization is enforced through # the task queue configuration. for blob, download_count in download_counts.items(): - BlobModel.objects.filter(blob=blob).update( + AssetBlob.objects.filter(blob=blob).update( download_count=F('download_count') + download_count ) diff --git a/dandiapi/api/admin.py b/dandiapi/api/admin.py index 06abb2e85..936c561ea 100644 --- a/dandiapi/api/admin.py +++ b/dandiapi/api/admin.py @@ -20,7 +20,6 @@ Asset, AssetBlob, Dandiset, - EmbargoedAssetBlob, Upload, UserMetadata, Version, @@ -188,33 +187,17 @@ def get_queryset(self, request): return super().get_queryset(request).prefetch_related('assets') -@admin.register(EmbargoedAssetBlob) -class EmbargoedAssetBlobAdmin(AssetBlobAdmin): - list_display = [ - 'id', - 'blob_id', - 'dandiset', - 'blob', - 'references', - 'size', - 'sha256', - 'modified', - 'created', - ] - - class AssetBlobInline(LimitedTabularInline): model = AssetBlob @admin.register(Asset) class AssetAdmin(admin.ModelAdmin): - autocomplete_fields = ['blob', 'embargoed_blob', 'zarr', 'versions'] + autocomplete_fields = ['blob', 'zarr', 'versions'] fields = [ 'asset_id', 'path', 'blob', - 'embargoed_blob', 'zarr', 'metadata', 'versions', diff --git a/dandiapi/api/asset_paths.py b/dandiapi/api/asset_paths.py index c4d5f607a..5bef62fbf 100644 --- a/dandiapi/api/asset_paths.py +++ b/dandiapi/api/asset_paths.py @@ -37,12 +37,7 @@ def get_root_paths_many(versions: QuerySet[Version], *, join_assets=False) -> Qu # Use prefetch_related here instead of select_related, # as otherwise the resulting join is very large if join_assets: - qs = qs.prefetch_related( - 'asset', - 'asset__blob', - 'asset__embargoed_blob', - 'asset__zarr', - ) + qs = qs.prefetch_related('asset', 'asset__blob', 'asset__zarr') return qs.filter(version__in=versions).exclude(path__contains='/').order_by('path') @@ -51,12 +46,7 @@ def get_root_paths(version: Version) -> QuerySet[AssetPath]: """Return all root paths for a version.""" # Use prefetch_related here instead of select_related, # as otherwise the resulting join is very large - qs = AssetPath.objects.prefetch_related( - 'asset', - 'asset__blob', - 'asset__embargoed_blob', - 'asset__zarr', - ) + qs = AssetPath.objects.prefetch_related('asset', 'asset__blob', 'asset__zarr') return qs.filter(version=version).exclude(path__contains='/').order_by('path') @@ -73,12 +63,7 @@ def get_path_children(path: AssetPath, depth: int | None = 1) -> QuerySet[AssetP path_ids = relation_qs.values_list('child', flat=True).distinct() return ( - AssetPath.objects.select_related( - 'asset', - 'asset__blob', - 'asset__embargoed_blob', - 'asset__zarr', - ) + AssetPath.objects.select_related('asset', 'asset__blob', 'asset__zarr') .filter(id__in=path_ids) .order_by('path') ) @@ -258,13 +243,11 @@ def add_version_asset_paths(version: Version): # Get all aggregates sizes = child_leaves.aggregate( - size=Coalesce(Sum('asset__blob__size'), 0), - esize=Coalesce(Sum('asset__embargoed_blob__size'), 0), - zsize=Coalesce(Sum('asset__zarr__size'), 0), + size=Coalesce(Sum('asset__blob__size'), 0), zsize=Coalesce(Sum('asset__zarr__size'), 0) ) node.aggregate_files += child_leaves.count() - node.aggregate_size += sizes['size'] + sizes['esize'] + sizes['zsize'] + node.aggregate_size += sizes['size'] + sizes['zsize'] node.save() diff --git a/dandiapi/api/migrations/0001_initial_v2.py b/dandiapi/api/migrations/0001_initial_v2.py index 351eb82aa..c72ae16b6 100644 --- a/dandiapi/api/migrations/0001_initial_v2.py +++ b/dandiapi/api/migrations/0001_initial_v2.py @@ -379,8 +379,8 @@ class Migration(migrations.Migration): 'blob', models.FileField( blank=True, - storage=dandiapi.api.storage.get_embargo_storage, - upload_to=dandiapi.api.storage.get_embargo_storage_prefix, + storage=dandiapi.api.storage.get_storage, + upload_to=dandiapi.api.storage.get_storage_prefix, ), ), ( @@ -442,8 +442,8 @@ class Migration(migrations.Migration): 'blob', models.FileField( blank=True, - storage=dandiapi.api.storage.get_embargo_storage, - upload_to=dandiapi.api.storage.get_embargo_storage_prefix, + storage=dandiapi.api.storage.get_storage, + upload_to=dandiapi.api.storage.get_storage_prefix, ), ), ( diff --git a/dandiapi/api/migrations/0008_remove_embargoedassetblob_dandiset_and_more.py b/dandiapi/api/migrations/0008_remove_embargoedassetblob_dandiset_and_more.py new file mode 100644 index 000000000..f8adc037b --- /dev/null +++ b/dandiapi/api/migrations/0008_remove_embargoedassetblob_dandiset_and_more.py @@ -0,0 +1,91 @@ +# Generated by Django 4.1.13 on 2024-01-16 18:31 +from __future__ import annotations + +from django.db import migrations, models + + +def migrate_embargoed_asset_blobs(apps, _): + Asset = apps.get_model('api.Asset') + AssetBlob = apps.get_model('api.AssetBlob') + + # We must include a filter to only include assets which are part of a version, as otherwise + # we may include an embargoed asset which has been previously updated. Since updating an asset + # results in a new asset which points to the same blob as the original asset, this would copy + # the same blob twice, resulting in an integrity error (same blob_id). + embargoed_assets = Asset.objects.filter( + embargoed_blob__isnull=False, versions__isnull=False + ).select_related('embargoed_blob') + + # For each relevant asset, create a new asset blob with embargoed=True, + # and point the asset to that + for asset in embargoed_assets.iterator(): + blob_id = str(asset.embargoed_blob.blob_id) + new_blob_location = f'blobs/{blob_id[0:3]}/{blob_id[3:6]}/{blob_id}' + new_asset_blob = AssetBlob.objects.create( + embargoed=True, + blob=new_blob_location, + blob_id=asset.embargoed_blob.blob_id, + created=asset.embargoed_blob.created, + modified=asset.embargoed_blob.modified, + sha256=asset.embargoed_blob.sha256, + etag=asset.embargoed_blob.etag, + size=asset.embargoed_blob.size, + download_count=asset.embargoed_blob.download_count, + ) + asset.blob = new_asset_blob + asset.embargoed_blob = None + asset.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0007_alter_asset_options_alter_version_options'), + ] + + operations = [ + migrations.AddField( + model_name='assetblob', + name='embargoed', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='upload', + name='embargoed', + field=models.BooleanField(default=False), + ), + # Migrate all embargoedassetblobs and embargoeduploads to other models with embargoed=True + migrations.RunPython(migrate_embargoed_asset_blobs), + migrations.RemoveField( + model_name='embargoedassetblob', + name='dandiset', + ), + migrations.RemoveField( + model_name='embargoedupload', + name='dandiset', + ), + migrations.RemoveConstraint( + model_name='asset', + name='exactly-one-blob', + ), + migrations.RemoveField( + model_name='asset', + name='embargoed_blob', + ), + migrations.AddConstraint( + model_name='asset', + constraint=models.CheckConstraint( + check=models.Q( + models.Q(('blob__isnull', True), ('zarr__isnull', False)), + models.Q(('blob__isnull', False), ('zarr__isnull', True)), + _connector='OR', + ), + name='blob-xor-zarr', + ), + ), + migrations.DeleteModel( + name='EmbargoedAssetBlob', + ), + migrations.DeleteModel( + name='EmbargoedUpload', + ), + ] diff --git a/dandiapi/api/models/__init__.py b/dandiapi/api/models/__init__.py index fdc3bd2b5..d3f0c7a6c 100644 --- a/dandiapi/api/models/__init__.py +++ b/dandiapi/api/models/__init__.py @@ -1,10 +1,10 @@ from __future__ import annotations -from .asset import Asset, AssetBlob, EmbargoedAssetBlob +from .asset import Asset, AssetBlob from .asset_paths import AssetPath, AssetPathRelation from .dandiset import Dandiset from .oauth import StagingApplication -from .upload import EmbargoedUpload, Upload +from .upload import Upload from .user import UserMetadata from .version import Version @@ -14,8 +14,6 @@ 'AssetPath', 'AssetPathRelation', 'Dandiset', - 'EmbargoedAssetBlob', - 'EmbargoedUpload', 'StagingApplication', 'Upload', 'UserMetadata', diff --git a/dandiapi/api/models/asset.py b/dandiapi/api/models/asset.py index 371f7923a..cc2bed6ad 100644 --- a/dandiapi/api/models/asset.py +++ b/dandiapi/api/models/asset.py @@ -16,14 +16,8 @@ from django_extensions.db.models import TimeStampedModel from dandiapi.api.models.metadata import PublishableMetadataMixin -from dandiapi.api.storage import ( - get_embargo_storage, - get_embargo_storage_prefix, - get_storage, - get_storage_prefix, -) - -from .dandiset import Dandiset +from dandiapi.api.storage import get_storage, get_storage_prefix + from .version import Version ASSET_CHARS_REGEX = r'[A-z0-9(),&\s#+~_=-]' @@ -50,13 +44,15 @@ def validate_asset_path(path: str): if TYPE_CHECKING: - from dandiapi.zarr.models import EmbargoedZarrArchive, ZarrArchive + from dandiapi.zarr.models import ZarrArchive -class BaseAssetBlob(TimeStampedModel): +class AssetBlob(TimeStampedModel): SHA256_REGEX = r'[0-9a-f]{64}' ETAG_REGEX = r'[0-9a-f]{32}(-[1-9][0-9]*)?' + embargoed = models.BooleanField(default=False) + blob = models.FileField(blank=True, storage=get_storage, upload_to=get_storage_prefix) blob_id = models.UUIDField(unique=True) sha256 = models.CharField( # noqa: DJ001 null=True, @@ -70,8 +66,13 @@ class BaseAssetBlob(TimeStampedModel): download_count = models.PositiveBigIntegerField(default=0) class Meta: - abstract = True indexes = [HashIndex(fields=['etag'])] + constraints = [ + models.UniqueConstraint( + name='unique-etag-size', + fields=['etag', 'size'], + ) + ] @property def references(self) -> int: @@ -95,35 +96,6 @@ def __str__(self) -> str: return self.blob.name -class AssetBlob(BaseAssetBlob): - blob = models.FileField(blank=True, storage=get_storage, upload_to=get_storage_prefix) - - class Meta(BaseAssetBlob.Meta): - constraints = [ - models.UniqueConstraint( - name='unique-etag-size', - fields=['etag', 'size'], - ) - ] - - -class EmbargoedAssetBlob(BaseAssetBlob): - blob = models.FileField( - blank=True, storage=get_embargo_storage, upload_to=get_embargo_storage_prefix - ) - dandiset = models.ForeignKey( - Dandiset, related_name='embargoed_asset_blobs', on_delete=models.CASCADE - ) - - class Meta(BaseAssetBlob.Meta): - constraints = [ - models.UniqueConstraint( - name='unique-embargo-etag-size', - fields=['dandiset', 'etag', 'size'], - ) - ] - - class Asset(PublishableMetadataMixin, TimeStampedModel): UUID_REGEX = r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}' @@ -138,9 +110,6 @@ class Status(models.TextChoices): blob = models.ForeignKey( AssetBlob, related_name='assets', on_delete=models.CASCADE, null=True, blank=True ) - embargoed_blob = models.ForeignKey( - EmbargoedAssetBlob, related_name='assets', on_delete=models.CASCADE, null=True, blank=True - ) zarr = models.ForeignKey( 'zarr.ZarrArchive', related_name='assets', on_delete=models.CASCADE, null=True, blank=True ) @@ -165,10 +134,11 @@ class Meta: ordering = ['created'] constraints = [ models.CheckConstraint( - name='exactly-one-blob', - check=Q(blob__isnull=True, embargoed_blob__isnull=True, zarr__isnull=False) - | Q(blob__isnull=True, embargoed_blob__isnull=False, zarr__isnull=True) - | Q(blob__isnull=False, embargoed_blob__isnull=True, zarr__isnull=True), + name='blob-xor-zarr', + check=( + Q(blob__isnull=True, zarr__isnull=False) + | Q(blob__isnull=False, zarr__isnull=True) + ), ), models.CheckConstraint( name='asset_metadata_has_schema_version', @@ -193,10 +163,6 @@ class Meta: def is_blob(self): return self.blob is not None - @property - def is_embargoed_blob(self): - return self.embargoed_blob is not None - @property def is_zarr(self): return self.zarr is not None @@ -205,39 +171,32 @@ def is_zarr(self): def size(self): if self.is_blob: return self.blob.size - if self.is_embargoed_blob: - return self.embargoed_blob.size + return self.zarr.size @property def sha256(self): if self.is_blob: return self.blob.sha256 - if self.is_embargoed_blob: - return self.embargoed_blob.sha256 raise RuntimeError('Zarr does not support SHA256') @property def digest(self) -> dict[str, str]: if self.is_blob: return self.blob.digest - if self.is_embargoed_blob: - return self.embargoed_blob.digest return self.zarr.digest @property def s3_url(self) -> str: if self.is_blob: return self.blob.s3_url - if self.is_embargoed_blob: - return self.embargoed_blob.s3_url return self.zarr.s3_url def is_different_from( self, *, - asset_blob: AssetBlob | EmbargoedAssetBlob | None = None, - zarr_archive: ZarrArchive | EmbargoedZarrArchive | None = None, + asset_blob: AssetBlob | None = None, + zarr_archive: ZarrArchive | None = None, metadata: dict, path: str, ) -> bool: @@ -246,13 +205,6 @@ def is_different_from( if isinstance(asset_blob, AssetBlob) and self.blob is not None and self.blob != asset_blob: return True - if ( - isinstance(asset_blob, EmbargoedAssetBlob) - and self.embargoed_blob is not None - and self.embargoed_blob != asset_blob - ): - return True - if ( isinstance(zarr_archive, ZarrArchive) and self.zarr is not None @@ -326,7 +278,7 @@ def total_size(cls): .distinct() .aggregate(size=models.Sum('size'))['size'] or 0 - for cls in (AssetBlob, EmbargoedAssetBlob, ZarrArchive) + for cls in (AssetBlob, ZarrArchive) # adding of Zarrs to embargoed dandisets is not supported # so no point of adding EmbargoedZarr here since would also result in error # TODO: add EmbagoedZarr whenever supported diff --git a/dandiapi/api/models/upload.py b/dandiapi/api/models/upload.py index 220e61b92..14a715485 100644 --- a/dandiapi/api/models/upload.py +++ b/dandiapi/api/models/upload.py @@ -1,6 +1,5 @@ from __future__ import annotations -from abc import abstractmethod from uuid import uuid4 from django.conf import settings @@ -8,22 +7,22 @@ from django.db import models from django_extensions.db.models import CreationDateTimeField -from dandiapi.api.storage import ( - get_embargo_storage, - get_embargo_storage_prefix, - get_storage, - get_storage_prefix, -) +from dandiapi.api.storage import get_storage, get_storage_prefix -from .asset import AssetBlob, EmbargoedAssetBlob +from .asset import AssetBlob from .dandiset import Dandiset -class BaseUpload(models.Model): +class Upload(models.Model): ETAG_REGEX = r'[0-9a-f]{32}(-[1-9][0-9]*)?' created = CreationDateTimeField() + dandiset = models.ForeignKey(Dandiset, related_name='uploads', on_delete=models.CASCADE) + + blob = models.FileField(blank=True, storage=get_storage, upload_to=get_storage_prefix) + embargoed = models.BooleanField(default=False) + # This is the key used to generate the object key, and the primary identifier for the upload. upload_id = models.UUIDField(unique=True, default=uuid4, db_index=True) etag = models.CharField( # noqa: DJ001 @@ -40,23 +39,28 @@ class BaseUpload(models.Model): class Meta: indexes = [models.Index(fields=['etag'])] - abstract = True @staticmethod - @abstractmethod - def object_key(upload_id, *, dandiset: Dandiset): - pass + def object_key(upload_id): + upload_id = str(upload_id) + return ( + f'{settings.DANDI_DANDISETS_BUCKET_PREFIX}' + f'blobs/{upload_id[0:3]}/{upload_id[3:6]}/{upload_id}' + ) @classmethod def initialize_multipart_upload(cls, etag, size, dandiset: Dandiset): upload_id = uuid4() - object_key = cls.object_key(upload_id, dandiset=dandiset) + object_key = cls.object_key(upload_id) + embargoed = dandiset.embargo_status == Dandiset.EmbargoStatus.EMBARGOED multipart_initialization = cls.blob.field.storage.multipart_manager.initialize_upload( object_key, size, # The upload HTTP API does not pass the file name or content type, and it would be a # breaking change to start requiring this. 'application/octet-stream', + # TODO: Pass embargoed + # embargoed ) upload = cls( @@ -65,72 +69,30 @@ def initialize_multipart_upload(cls, etag, size, dandiset: Dandiset): etag=etag, size=size, dandiset=dandiset, + embargoed=embargoed, multipart_upload_id=multipart_initialization.upload_id, ) - return upload, {'upload_id': upload.upload_id, 'parts': multipart_initialization.parts} - - def object_key_exists(self): - return self.blob.field.storage.exists(self.blob.name) - - def actual_size(self): - return self.blob.field.storage.size(self.blob.name) - - def actual_etag(self): - return self.blob.storage.etag_from_blob_name(self.blob.name) - - -class Upload(BaseUpload): - blob = models.FileField(blank=True, storage=get_storage, upload_to=get_storage_prefix) - dandiset = models.ForeignKey(Dandiset, related_name='uploads', on_delete=models.CASCADE) - - def __str__(self) -> str: - return self.upload_id - - @staticmethod - def object_key(upload_id, *, dandiset: Dandiset | None = None): # noqa: ARG004 - upload_id = str(upload_id) - return ( - f'{settings.DANDI_DANDISETS_BUCKET_PREFIX}' - f'blobs/{upload_id[0:3]}/{upload_id[3:6]}/{upload_id}' - ) + return upload, { + 'upload_id': upload.upload_id, + 'parts': multipart_initialization.parts, + } def to_asset_blob(self) -> AssetBlob: """Convert this upload into an AssetBlob.""" return AssetBlob( + embargoed=self.embargoed, blob_id=self.upload_id, blob=self.blob, etag=self.etag, size=self.size, ) + def object_key_exists(self): + return self.blob.field.storage.exists(self.blob.name) -class EmbargoedUpload(BaseUpload): - blob = models.FileField( - blank=True, storage=get_embargo_storage, upload_to=get_embargo_storage_prefix - ) - dandiset = models.ForeignKey( - Dandiset, related_name='embargoed_uploads', on_delete=models.CASCADE - ) - - def __str__(self) -> str: - return self.upload_id - - @staticmethod - def object_key(upload_id, *, dandiset: Dandiset): - upload_id = str(upload_id) - return ( - f'{settings.DANDI_DANDISETS_EMBARGO_BUCKET_PREFIX}' - f'{dandiset.identifier}/' - f'blobs/{upload_id[0:3]}/{upload_id[3:6]}/{upload_id}' - ) + def actual_size(self): + return self.blob.field.storage.size(self.blob.name) - def to_embargoed_asset_blob(self) -> EmbargoedAssetBlob: - """Convert this upload into an AssetBlob.""" - return EmbargoedAssetBlob( - blob_id=self.upload_id, - blob=self.blob, - etag=self.etag, - size=self.size, - dandiset=self.dandiset, - ) + def actual_etag(self): + return self.blob.storage.etag_from_blob_name(self.blob.name) diff --git a/dandiapi/api/services/asset/__init__.py b/dandiapi/api/services/asset/__init__.py index 8cedcd739..fcb88b270 100644 --- a/dandiapi/api/services/asset/__init__.py +++ b/dandiapi/api/services/asset/__init__.py @@ -5,7 +5,7 @@ from django.db import transaction from dandiapi.api.asset_paths import add_asset_paths, delete_asset_paths, get_conflicting_paths -from dandiapi.api.models.asset import Asset, AssetBlob, EmbargoedAssetBlob +from dandiapi.api.models.asset import Asset, AssetBlob from dandiapi.api.models.version import Version from dandiapi.api.services.asset.exceptions import ( AssetAlreadyExistsError, @@ -23,7 +23,6 @@ def _create_asset( *, path: str, asset_blob: AssetBlob | None = None, - embargoed_asset_blob: EmbargoedAssetBlob | None = None, zarr_archive: ZarrArchive | None = None, metadata: dict, ): @@ -32,7 +31,6 @@ def _create_asset( asset = Asset( path=path, blob=asset_blob, - embargoed_blob=embargoed_asset_blob, zarr=zarr_archive, metadata=metadata, status=Asset.Status.PENDING, @@ -48,7 +46,7 @@ def change_asset( # noqa: PLR0913 user, asset: Asset, version: Version, - new_asset_blob: AssetBlob | EmbargoedAssetBlob | None = None, + new_asset_blob: AssetBlob | None = None, new_zarr_archive: ZarrArchive | None = None, new_metadata: dict, ) -> tuple[Asset, bool]: @@ -104,7 +102,7 @@ def add_asset_to_version( *, user, version: Version, - asset_blob: AssetBlob | EmbargoedAssetBlob | None = None, + asset_blob: AssetBlob | None = None, zarr_archive: ZarrArchive | None = None, metadata: dict, ) -> Asset: @@ -135,20 +133,9 @@ def add_asset_to_version( if zarr_archive and zarr_archive.dandiset != version.dandiset: raise ZarrArchiveBelongsToDifferentDandisetError - if isinstance(asset_blob, EmbargoedAssetBlob): - embargoed_asset_blob = asset_blob - asset_blob = None - else: - embargoed_asset_blob = None - asset_blob = asset_blob # noqa: PLW0127 - with transaction.atomic(): asset = _create_asset( - path=path, - asset_blob=asset_blob, - embargoed_asset_blob=embargoed_asset_blob, - zarr_archive=zarr_archive, - metadata=metadata, + path=path, asset_blob=asset_blob, zarr_archive=zarr_archive, metadata=metadata ) version.assets.add(asset) add_asset_paths(asset, version) diff --git a/dandiapi/api/services/embargo/__init__.py b/dandiapi/api/services/embargo/__init__.py index 9b3e4425b..9905ab34c 100644 --- a/dandiapi/api/services/embargo/__init__.py +++ b/dandiapi/api/services/embargo/__init__.py @@ -18,47 +18,32 @@ def _unembargo_asset(asset: Asset): """Unembargo an asset by copying its blob to the public bucket.""" - if asset.embargoed_blob is None: + if not asset.blob.embargoed: raise AssetNotEmbargoedError - # Use existing AssetBlob if possible - etag = asset.embargoed_blob.etag - matching_blob = AssetBlob.objects.filter(etag=etag).first() - if matching_blob is not None: - asset.blob = matching_blob - else: - # Matching AssetBlob doesn't exist, copy blob to public bucket - resp = copy_object_multipart( - asset.embargoed_blob.blob.storage, - source_bucket=settings.DANDI_DANDISETS_EMBARGO_BUCKET_NAME, - source_key=asset.embargoed_blob.blob.name, - dest_bucket=settings.DANDI_DANDISETS_BUCKET_NAME, - dest_key=Upload.object_key( - asset.embargoed_blob.blob_id, dandiset=asset.embargoed_blob.dandiset - ), - ) - - if resp.etag != asset.embargoed_blob.etag: - raise RuntimeError('ETag mismatch between copied object and original embargoed object') - - # Assign blob (changing only blob) - asset.blob = AssetBlob( - blob=resp.key, - blob_id=asset.embargoed_blob.blob_id, - sha256=asset.embargoed_blob.sha256, - etag=asset.embargoed_blob.etag, - size=asset.embargoed_blob.size, - ) - asset.blob.save() - - # Save updated blob field - asset.embargoed_blob = None - asset.save() + # TODO: Replace this with object tag removal + # Matching AssetBlob doesn't exist, copy blob to public bucket + # resp = copy_object_multipart( + # asset.embargoed_blob.blob.storage, + # source_bucket=settings.DANDI_DANDISETS_EMBARGO_BUCKET_NAME, + # source_key=asset.embargoed_blob.blob.name, + # dest_bucket=settings.DANDI_DANDISETS_BUCKET_NAME, + # dest_key=Upload.object_key( + # asset.embargoed_blob.blob_id, dandiset=asset.embargoed_blob.dandiset + # ), + # ) + + # if resp.etag != asset.embargoed_blob.etag: + # raise RuntimeError('ETag mismatch between copied object and original embargoed object') + + # Update blob embargoed flag + asset.blob.embargoed = False + asset.blob.save() def _unembargo_dandiset(dandiset: Dandiset): draft_version: Version = dandiset.draft_version - embargoed_assets: QuerySet[Asset] = draft_version.assets.filter(embargoed_blob__isnull=False) + embargoed_assets: QuerySet[Asset] = draft_version.assets.filter(blob__embargoed=True) # Unembargo all assets for asset in embargoed_assets.iterator(): diff --git a/dandiapi/api/storage.py b/dandiapi/api/storage.py index ff0de3702..7663a5965 100644 --- a/dandiapi/api/storage.py +++ b/dandiapi/api/storage.py @@ -332,11 +332,3 @@ def get_storage() -> Storage: def get_storage_prefix(instance: Any, filename: str) -> str: return f'{settings.DANDI_DANDISETS_BUCKET_PREFIX}{filename}' - - -def get_embargo_storage() -> Storage: - return create_s3_storage(settings.DANDI_DANDISETS_EMBARGO_BUCKET_NAME) - - -def get_embargo_storage_prefix(instance: Any, filename: str) -> str: - return f'{settings.DANDI_DANDISETS_EMBARGO_BUCKET_PREFIX}{filename}' diff --git a/dandiapi/api/tasks/__init__.py b/dandiapi/api/tasks/__init__.py index 80f8ef87c..b5b9a02b1 100644 --- a/dandiapi/api/tasks/__init__.py +++ b/dandiapi/api/tasks/__init__.py @@ -11,20 +11,15 @@ write_dandiset_jsonld, write_dandiset_yaml, ) -from dandiapi.api.models import Asset, AssetBlob, Dandiset, EmbargoedAssetBlob, Version +from dandiapi.api.models import Asset, AssetBlob, Dandiset, Version logger = get_task_logger(__name__) @shared_task(queue='calculate_sha256', soft_time_limit=86_400) def calculate_sha256(blob_id: str) -> None: - try: - asset_blob = AssetBlob.objects.get(blob_id=blob_id) - logger.info('Found AssetBlob %s', blob_id) - except AssetBlob.DoesNotExist: - asset_blob = EmbargoedAssetBlob.objects.get(blob_id=blob_id) - logger.info('Found EmbargoedAssetBlob %s', blob_id) - + asset_blob = AssetBlob.objects.get(blob_id=blob_id) + logger.info('Found AssetBlob %s', blob_id) sha256 = asset_blob.blob.storage.sha256_checksum(asset_blob.blob.name) # TODO: Run dandi-cli validation diff --git a/dandiapi/api/tasks/scheduled.py b/dandiapi/api/tasks/scheduled.py index 34575b897..20af07efa 100644 --- a/dandiapi/api/tasks/scheduled.py +++ b/dandiapi/api/tasks/scheduled.py @@ -6,6 +6,7 @@ from __future__ import annotations +from collections.abc import Iterable from datetime import timedelta import time from typing import TYPE_CHECKING @@ -67,7 +68,6 @@ def validate_pending_asset_metadata(): Asset.objects.filter(status=Asset.Status.PENDING) .filter( (Q(blob__isnull=False) & Q(blob__sha256__isnull=False)) - | (Q(embargoed_blob__isnull=False) & Q(embargoed_blob__sha256__isnull=False)) | ( Q(zarr__isnull=False) & Q(zarr__checksum__isnull=False) diff --git a/dandiapi/api/tests/factories.py b/dandiapi/api/tests/factories.py index c0626d2be..6ed413db7 100644 --- a/dandiapi/api/tests/factories.py +++ b/dandiapi/api/tests/factories.py @@ -3,20 +3,18 @@ import datetime import hashlib +import factory +import faker from allauth.socialaccount.models import SocialAccount from dandischema.digests.dandietag import DandiETag from django.conf import settings from django.contrib.auth.models import User from django.core import files as django_files -import factory -import faker from dandiapi.api.models import ( Asset, AssetBlob, Dandiset, - EmbargoedAssetBlob, - EmbargoedUpload, Upload, UserMetadata, Version, @@ -171,16 +169,9 @@ def etag(self): class EmbargoedAssetBlobFactory(AssetBlobFactory): class Meta: - model = EmbargoedAssetBlob - - dandiset = factory.SubFactory(DandisetFactory) + model = AssetBlob - @factory.lazy_attribute - def blob(self): - return django_files.File( - file=django_files.base.ContentFile(faker.Faker().binary(self.size)).file, - name=EmbargoedUpload.object_key(self.blob_id, dandiset=self.dandiset), - ) + embargoed = True class DraftAssetFactory(factory.django.DjangoModelFactory): @@ -239,4 +230,6 @@ def etag(self): class EmbargoedUploadFactory(UploadFactory): class Meta: - model = EmbargoedUpload + model = Upload + + embargoed = True diff --git a/dandiapi/api/tests/test_asset.py b/dandiapi/api/tests/test_asset.py index f1f381ecc..949948519 100644 --- a/dandiapi/api/tests/test_asset.py +++ b/dandiapi/api/tests/test_asset.py @@ -3,15 +3,15 @@ import json from uuid import uuid4 +import pytest +import requests from django.conf import settings from django.db.utils import IntegrityError from django.urls import reverse from guardian.shortcuts import assign_perm -import pytest -import requests from dandiapi.api.asset_paths import add_asset_paths, extract_paths -from dandiapi.api.models import Asset, AssetBlob, EmbargoedAssetBlob, Version +from dandiapi.api.models import Asset, AssetBlob, Version from dandiapi.api.models.asset_paths import AssetPath from dandiapi.api.models.dandiset import Dandiset from dandiapi.api.services.asset import add_asset_to_version @@ -35,7 +35,7 @@ def test_asset_no_blob_zarr(draft_asset_factory): with pytest.raises(IntegrityError) as excinfo: asset.save() - assert 'exactly-one-blob' in str(excinfo.value) + assert 'blob-xor-zarr' in str(excinfo.value) @pytest.mark.django_db() @@ -45,7 +45,7 @@ def test_asset_blob_and_zarr(draft_asset, zarr_archive): with pytest.raises(IntegrityError) as excinfo: draft_asset.save() - assert 'exactly-one-blob' in str(excinfo.value) + assert 'blob-xor-zarr' in str(excinfo.value) @pytest.mark.django_db() @@ -1070,7 +1070,7 @@ def test_asset_rest_update_zarr( assign_perm('owner', user, draft_version.dandiset) api_client.force_authenticate(user=user) - asset = draft_asset_factory(blob=None, embargoed_blob=None, zarr=zarr_archive) + asset = draft_asset_factory(blob=None, zarr=zarr_archive) draft_version.assets.add(asset) add_asset_paths(asset=asset, version=draft_version) @@ -1241,7 +1241,7 @@ def test_asset_rest_delete_zarr( zarr_archive, zarr_file_factory, ): - asset = draft_asset_factory(blob=None, embargoed_blob=None, zarr=zarr_archive) + asset = draft_asset_factory(blob=None, zarr=zarr_archive) assign_perm('owner', user, draft_version.dandiset) draft_version.assets.add(asset) @@ -1383,10 +1383,11 @@ def test_asset_download_embargo( draft_version_factory, dandiset_factory, asset_factory, - embargoed_asset_blob_factory, + embargoed_asset_blob, + monkeypatch, ): - # Pretend like EmbargoedAssetBlob was defined with the given storage - EmbargoedAssetBlob.blob.field.storage = storage + # Pretend like AssetBlob was defined with the given storage + monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) # Set draft version as embargoed version = draft_version_factory( @@ -1398,8 +1399,7 @@ def test_asset_download_embargo( client = authenticated_api_client # Generate assets and blobs - embargoed_blob = embargoed_asset_blob_factory(dandiset=version.dandiset) - asset = asset_factory(blob=None, embargoed_blob=embargoed_blob) + asset = asset_factory(blob=embargoed_asset_blob) version.assets.add(asset) response = client.get( @@ -1417,7 +1417,7 @@ def test_asset_download_embargo( assert cd_header == f'attachment; filename="{asset.path.split("/")[-1]}"' - with asset.embargoed_blob.blob.file.open('rb') as reader: + with asset.blob.blob.file.open('rb') as reader: assert download.content == reader.read() diff --git a/dandiapi/api/tests/test_dandiset.py b/dandiapi/api/tests/test_dandiset.py index 5495b97d4..3eb1730b8 100644 --- a/dandiapi/api/tests/test_dandiset.py +++ b/dandiapi/api/tests/test_dandiset.py @@ -374,42 +374,45 @@ def test_dandiset_rest_create(api_client, user): # Verify that computed metadata was injected year = datetime.datetime.now(datetime.UTC).year url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}/draft' - assert dandiset.draft_version.metadata == { - **metadata, - 'manifestLocation': [ - f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' - ], - 'name': name, - 'identifier': DANDISET_SCHEMA_ID_RE, - 'id': f'DANDI:{dandiset.identifier}/draft', - 'version': 'draft', - 'url': url, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': ( - f'{user.last_name}, {user.first_name} ({year}) {name} ' - f'(Version draft) [Data set]. DANDI archive. {url}' - ), - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'schemaKey': 'Dandiset', - 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], - 'repository': settings.DANDI_WEB_APP_URL, - 'contributor': [ - { - 'name': 'Doe, John', - 'email': user.email, - 'roleName': ['dcite:ContactPerson'], - 'schemaKey': 'Person', - 'affiliation': [], - 'includeInCitation': True, - } - ], - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } + assert ( + dandiset.draft_version.metadata + == { + **metadata, + 'manifestLocation': [ + f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' # noqa: E501 + ], + 'name': name, + 'identifier': DANDISET_SCHEMA_ID_RE, + 'id': f'DANDI:{dandiset.identifier}/draft', + 'version': 'draft', + 'url': url, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'citation': ( + f'{user.last_name}, {user.first_name} ({year}) {name} ' + f'(Version draft) [Data set]. DANDI archive. {url}' + ), + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', # noqa: E501 + 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaKey': 'Dandiset', + 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], + 'repository': settings.DANDI_WEB_APP_URL, + 'contributor': [ + { + 'name': 'Doe, John', + 'email': user.email, + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + 'affiliation': [], + 'includeInCitation': True, + } + ], + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } + ) @pytest.mark.django_db() @@ -467,42 +470,45 @@ def test_dandiset_rest_create_with_identifier(api_client, admin_user): # Verify that computed metadata was injected year = datetime.datetime.now(datetime.UTC).year url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}/draft' - assert dandiset.draft_version.metadata == { - **metadata, - 'manifestLocation': [ - f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' - ], - 'name': name, - 'identifier': f'DANDI:{identifier}', - 'id': f'DANDI:{dandiset.identifier}/draft', - 'version': 'draft', - 'url': url, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': ( - f'{admin_user.last_name}, {admin_user.first_name} ({year}) {name} ' - f'(Version draft) [Data set]. DANDI archive. {url}' - ), - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'schemaKey': 'Dandiset', - 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], - 'repository': settings.DANDI_WEB_APP_URL, - 'contributor': [ - { - 'name': 'Doe, John', - 'email': admin_user.email, - 'roleName': ['dcite:ContactPerson'], - 'schemaKey': 'Person', - 'affiliation': [], - 'includeInCitation': True, - } - ], - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } + assert ( + dandiset.draft_version.metadata + == { + **metadata, + 'manifestLocation': [ + f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' # noqa: E501 + ], + 'name': name, + 'identifier': f'DANDI:{identifier}', + 'id': f'DANDI:{dandiset.identifier}/draft', + 'version': 'draft', + 'url': url, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'citation': ( + f'{admin_user.last_name}, {admin_user.first_name} ({year}) {name} ' + f'(Version draft) [Data set]. DANDI archive. {url}' + ), + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', # noqa: E501 + 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaKey': 'Dandiset', + 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], + 'repository': settings.DANDI_WEB_APP_URL, + 'contributor': [ + { + 'name': 'Doe, John', + 'email': admin_user.email, + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + 'affiliation': [], + 'includeInCitation': True, + } + ], + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } + ) @pytest.mark.django_db() @@ -574,41 +580,44 @@ def test_dandiset_rest_create_with_contributor(api_client, admin_user): # Verify that computed metadata was injected year = datetime.datetime.now(datetime.UTC).year url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}/draft' - assert dandiset.draft_version.metadata == { - **metadata, - 'manifestLocation': [ - f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' - ], - 'name': name, - 'identifier': f'DANDI:{identifier}', - 'id': f'DANDI:{dandiset.identifier}/draft', - 'version': 'draft', - 'url': url, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': ( - f'Jane Doe ({year}) {name} ' f'(Version draft) [Data set]. DANDI archive. {url}' - ), - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'schemaKey': 'Dandiset', - 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], - 'repository': settings.DANDI_WEB_APP_URL, - 'contributor': [ - { - 'name': 'Jane Doe', - 'email': 'jane.doe@kitware.com', - 'roleName': ['dcite:ContactPerson'], - 'schemaKey': 'Person', - 'affiliation': [], - 'includeInCitation': True, - } - ], - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } + assert ( + dandiset.draft_version.metadata + == { + **metadata, + 'manifestLocation': [ + f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' # noqa: E501 + ], + 'name': name, + 'identifier': f'DANDI:{identifier}', + 'id': f'DANDI:{dandiset.identifier}/draft', + 'version': 'draft', + 'url': url, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'citation': ( + f'Jane Doe ({year}) {name} ' f'(Version draft) [Data set]. DANDI archive. {url}' + ), + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', # noqa: E501 + 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaKey': 'Dandiset', + 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], + 'repository': settings.DANDI_WEB_APP_URL, + 'contributor': [ + { + 'name': 'Jane Doe', + 'email': 'jane.doe@kitware.com', + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + 'affiliation': [], + 'includeInCitation': True, + } + ], + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } + ) @pytest.mark.django_db() @@ -664,42 +673,45 @@ def test_dandiset_rest_create_embargoed(api_client, user): # Verify that computed metadata was injected year = datetime.datetime.now(datetime.UTC).year url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}/draft' - assert dandiset.draft_version.metadata == { - **metadata, - 'manifestLocation': [ - f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' - ], - 'name': name, - 'identifier': DANDISET_SCHEMA_ID_RE, - 'id': f'DANDI:{dandiset.identifier}/draft', - 'version': 'draft', - 'url': url, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': ( - f'{user.last_name}, {user.first_name} ({year}) {name} ' - f'(Version draft) [Data set]. DANDI archive. {url}' - ), - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'schemaKey': 'Dandiset', - 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:EmbargoedAccess'}], - 'repository': settings.DANDI_WEB_APP_URL, - 'contributor': [ - { - 'name': 'Doe, John', - 'email': user.email, - 'roleName': ['dcite:ContactPerson'], - 'schemaKey': 'Person', - 'affiliation': [], - 'includeInCitation': True, - } - ], - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } + assert ( + dandiset.draft_version.metadata + == { + **metadata, + 'manifestLocation': [ + f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' # noqa: E501 + ], + 'name': name, + 'identifier': DANDISET_SCHEMA_ID_RE, + 'id': f'DANDI:{dandiset.identifier}/draft', + 'version': 'draft', + 'url': url, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'citation': ( + f'{user.last_name}, {user.first_name} ({year}) {name} ' + f'(Version draft) [Data set]. DANDI archive. {url}' + ), + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', # noqa: E501 + 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaKey': 'Dandiset', + 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:EmbargoedAccess'}], + 'repository': settings.DANDI_WEB_APP_URL, + 'contributor': [ + { + 'name': 'Doe, John', + 'email': user.email, + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + 'affiliation': [], + 'includeInCitation': True, + } + ], + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } + ) @pytest.mark.django_db() @@ -762,7 +774,7 @@ def test_dandiset_rest_delete_with_zarrs( api_client.force_authenticate(user=user) assign_perm('owner', user, draft_version.dandiset) zarr = zarr_archive_factory(dandiset=draft_version.dandiset) - asset = draft_asset_factory(blob=None, embargoed_blob=None, zarr=zarr) + asset = draft_asset_factory(blob=None, zarr=zarr) # Add paths add_asset_paths(asset=asset, version=draft_version) diff --git a/dandiapi/api/tests/test_embargo.py b/dandiapi/api/tests/test_embargo.py index 191733c8a..7e8c6c4b7 100644 --- a/dandiapi/api/tests/test_embargo.py +++ b/dandiapi/api/tests/test_embargo.py @@ -74,7 +74,7 @@ def test_embargo_visibility( ): dandiset = dandiset_factory(embargo_status=embargo_status) version = draft_version_factory(dandiset=dandiset) - asset = draft_asset_factory(blob=None, embargoed_blob=embargoed_asset_blob) + asset = draft_asset_factory(blob=embargoed_asset_blob) version.assets.add(asset) url = url_format.format(dandiset=dandiset, asset=asset) diff --git a/dandiapi/api/tests/test_stats.py b/dandiapi/api/tests/test_stats.py index 5de16f0c8..546d36a70 100644 --- a/dandiapi/api/tests/test_stats.py +++ b/dandiapi/api/tests/test_stats.py @@ -50,7 +50,7 @@ def test_stats_asset(api_client, version, asset): @pytest.mark.django_db() def test_stats_embargoed_asset(api_client, version, asset_factory, embargoed_asset_blob_factory): embargoed_asset = asset_factory() - embargoed_asset.embargoed_blob = embargoed_asset_blob_factory() + embargoed_asset.blob = embargoed_asset_blob_factory() version.assets.add(embargoed_asset) stats = api_client.get('/api/stats/').data @@ -66,7 +66,7 @@ def test_stats_embargoed_and_regular_blobs( version.assets.add(asset) embargoed_asset = asset_factory() - embargoed_asset.embargoed_blob = embargoed_asset_blob_factory() + embargoed_asset.blob = embargoed_asset_blob_factory() version.assets.add(embargoed_asset) stats = api_client.get('/api/stats/').data diff --git a/dandiapi/api/tests/test_tasks.py b/dandiapi/api/tests/test_tasks.py index bfffb13f0..fe6a81c52 100644 --- a/dandiapi/api/tests/test_tasks.py +++ b/dandiapi/api/tests/test_tasks.py @@ -9,9 +9,10 @@ from django.utils import timezone from guardian.shortcuts import assign_perm import pytest +from rest_framework.test import APIClient from dandiapi.api import tasks -from dandiapi.api.models import Asset, AssetBlob, EmbargoedAssetBlob, Version +from dandiapi.api.models import Asset, AssetBlob, Version from .fuzzy import URN_RE, UTC_ISO_TIMESTAMP_RE @@ -40,9 +41,11 @@ def test_calculate_checksum_task(storage: Storage, asset_blob_factory): @pytest.mark.django_db() -def test_calculate_checksum_task_embargo(storage: Storage, embargoed_asset_blob_factory): - # Pretend like EmbargoedAssetBlob was defined with the given storage - EmbargoedAssetBlob.blob.field.storage = storage +def test_calculate_checksum_task_embargo( + storage: Storage, embargoed_asset_blob_factory, monkeypatch +): + # Pretend like AssetBlob was defined with the given storage + monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) asset_blob = embargoed_asset_blob_factory(sha256=None) @@ -333,56 +336,59 @@ def test_publish_task( published_version = draft_version.dandiset.versions.latest('created') - assert published_version.metadata == { - **draft_version.metadata, - 'publishedBy': { - 'id': URN_RE, - 'name': 'DANDI publish', - 'startDate': UTC_ISO_TIMESTAMP_RE, - 'endDate': UTC_ISO_TIMESTAMP_RE, - 'wasAssociatedWith': [ - { - 'id': URN_RE, - 'identifier': 'RRID:SCR_017571', - 'name': 'DANDI API', - # TODO: version the API - 'version': '0.1.0', - 'schemaKey': 'Software', - } - ], - 'schemaKey': 'PublishActivity', - }, - 'datePublished': UTC_ISO_TIMESTAMP_RE, - 'manifestLocation': [ - f'http://{settings.MINIO_STORAGE_ENDPOINT}/test-dandiapi-dandisets/test-prefix/dandisets/{draft_version.dandiset.identifier}/{published_version.version}/assets.yaml', - ], - 'identifier': f'DANDI:{draft_version.dandiset.identifier}', - 'version': published_version.version, - 'id': f'DANDI:{draft_version.dandiset.identifier}/{published_version.version}', - 'url': ( - f'{settings.DANDI_WEB_APP_URL}/dandiset/{draft_version.dandiset.identifier}' - f'/{published_version.version}' - ), - 'citation': published_version.citation(published_version.metadata), - 'doi': f'10.80507/dandi.{draft_version.dandiset.identifier}/{published_version.version}', - # Once the assets are linked, assetsSummary should be computed properly - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 200, - 'numberOfFiles': 2, - 'dataStandard': [ - { - 'schemaKey': 'StandardsType', - 'identifier': 'RRID:SCR_015242', - 'name': 'Neurodata Without Borders (NWB)', - } + assert ( + published_version.metadata + == { + **draft_version.metadata, + 'publishedBy': { + 'id': URN_RE, + 'name': 'DANDI publish', + 'startDate': UTC_ISO_TIMESTAMP_RE, + 'endDate': UTC_ISO_TIMESTAMP_RE, + 'wasAssociatedWith': [ + { + 'id': URN_RE, + 'identifier': 'RRID:SCR_017571', + 'name': 'DANDI API', + # TODO: version the API + 'version': '0.1.0', + 'schemaKey': 'Software', + } + ], + 'schemaKey': 'PublishActivity', + }, + 'datePublished': UTC_ISO_TIMESTAMP_RE, + 'manifestLocation': [ + f'http://{settings.MINIO_STORAGE_ENDPOINT}/test-dandiapi-dandisets/test-prefix/dandisets/{draft_version.dandiset.identifier}/{published_version.version}/assets.yaml', # noqa: E501 ], - 'approach': [], - 'measurementTechnique': [], - 'variableMeasured': [], - 'species': [], - }, - } + 'identifier': f'DANDI:{draft_version.dandiset.identifier}', + 'version': published_version.version, + 'id': f'DANDI:{draft_version.dandiset.identifier}/{published_version.version}', + 'url': ( + f'{settings.DANDI_WEB_APP_URL}/dandiset/{draft_version.dandiset.identifier}' + f'/{published_version.version}' + ), + 'citation': published_version.citation(published_version.metadata), + 'doi': f'10.80507/dandi.{draft_version.dandiset.identifier}/{published_version.version}', + # Once the assets are linked, assetsSummary should be computed properly + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 200, + 'numberOfFiles': 2, + 'dataStandard': [ + { + 'schemaKey': 'StandardsType', + 'identifier': 'RRID:SCR_015242', + 'name': 'Neurodata Without Borders (NWB)', + } + ], + 'approach': [], + 'measurementTechnique': [], + 'variableMeasured': [], + 'species': [], + }, + } + ) assert published_version.assets.count() == 2 new_draft_asset: Asset = published_version.assets.get(asset_id=old_draft_asset.asset_id) diff --git a/dandiapi/api/tests/test_unembargo.py b/dandiapi/api/tests/test_unembargo.py index 8f98d4775..bd941b456 100644 --- a/dandiapi/api/tests/test_unembargo.py +++ b/dandiapi/api/tests/test_unembargo.py @@ -3,35 +3,35 @@ import math import os +import factory +import pytest from dandischema.digests.dandietag import PartGenerator, mb from django.contrib.auth.models import AnonymousUser from django.core.files.uploadedfile import SimpleUploadedFile -import factory from guardian.shortcuts import assign_perm -import pytest from dandiapi.api import tasks -from dandiapi.api.models import Asset, AssetBlob, EmbargoedAssetBlob, Version +from dandiapi.api.models import Asset, AssetBlob, Version from dandiapi.api.models.dandiset import Dandiset from dandiapi.api.services.embargo import _unembargo_asset, unembargo_dandiset @pytest.mark.django_db() def test_asset_unembargo( - embargoed_storage, asset_factory, embargoed_asset_blob_factory, draft_version + storage, asset_factory, embargoed_asset_blob_factory, draft_version, monkeypatch ): - # Pretend like EmbargoedAssetBlob was defined with the given storage - EmbargoedAssetBlob.blob.field.storage = embargoed_storage + # Pretend like AssetBlob was defined with the given storage + monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) - embargoed_asset_blob: EmbargoedAssetBlob = embargoed_asset_blob_factory() - embargoed_asset: Asset = asset_factory(embargoed_blob=embargoed_asset_blob, blob=None) + embargoed_asset_blob: AssetBlob = embargoed_asset_blob_factory() + embargoed_asset: Asset = asset_factory(blob=embargoed_asset_blob) draft_version.assets.add(embargoed_asset) # Assert embargoed properties embargoed_sha256 = embargoed_asset.sha256 - embargoed_etag = embargoed_asset.embargoed_blob.etag - assert embargoed_asset.blob is None - assert embargoed_asset.embargoed_blob is not None + embargoed_etag = embargoed_asset.blob.etag + assert embargoed_asset.blob is not None + assert embargoed_asset.blob.embargoed assert embargoed_asset.sha256 is not None assert 'embargo' in embargoed_asset.full_metadata['contentUrl'][1] @@ -42,7 +42,7 @@ def test_asset_unembargo( # Assert unembargoed properties assert asset.blob is not None - assert asset.embargoed_blob is None + assert not asset.blob.embargoed assert asset.sha256 == embargoed_sha256 assert asset.blob.etag == embargoed_etag assert 'embargo' not in asset.full_metadata['contentUrl'][1] @@ -102,15 +102,13 @@ def test_unembargo_dandiset( draft_version_factory, asset_factory, embargoed_asset_blob_factory, - storage_tuple, + storage, file_size, part_size, monkeypatch, ): - # Pretend like AssetBlob/EmbargoedAssetBlob were defined with the given storage - storage, embargoed_storage = storage_tuple + # Pretend like AssetBlob was defined with the given storage monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) - monkeypatch.setattr(EmbargoedAssetBlob.blob.field, 'storage', embargoed_storage) # Monkey patch PartGenerator so that upload and copy use a smaller part size monkeypatch.setattr(PartGenerator, 'DEFAULT_PART_SIZE', part_size, raising=True) @@ -121,7 +119,7 @@ def test_unembargo_dandiset( assign_perm('owner', user, dandiset) # Create an embargoed asset blob - embargoed_asset_blob: EmbargoedAssetBlob = embargoed_asset_blob_factory( + embargoed_asset_blob: AssetBlob = embargoed_asset_blob_factory( size=file_size, blob=SimpleUploadedFile('test', content=os.urandom(file_size)) ) @@ -130,13 +128,12 @@ def test_unembargo_dandiset( assert embargoed_asset_blob.etag.endswith(f'-{num_parts}') # Create asset from embargoed blob - embargoed_asset: Asset = asset_factory(embargoed_blob=embargoed_asset_blob, blob=None) + embargoed_asset: Asset = asset_factory(blob=embargoed_asset_blob) draft_version.assets.add(embargoed_asset) # Assert properties before unembargo - assert embargoed_asset.embargoed_blob is not None - assert embargoed_asset.blob is None - assert embargoed_asset.embargoed_blob.etag != '' + assert embargoed_asset.blob.embargoed + assert embargoed_asset.blob.etag != '' # Run unembargo and validate version metadata unembargo_dandiset(user=user, dandiset=dandiset) @@ -156,8 +153,7 @@ def test_unembargo_dandiset( assert asset == embargoed_asset # Check blobs - assert asset.embargoed_blob is None - assert asset.blob is not None + assert not asset.blob.embargoed assert asset.blob.etag == embargoed_asset_blob.etag blob_id = str(asset.blob.blob_id) @@ -172,12 +168,11 @@ def test_unembargo_dandiset_existing_blobs( asset_factory, asset_blob_factory, embargoed_asset_blob_factory, - storage_tuple, + storage, + monkeypatch, ): - # Pretend like AssetBlob/EmbargoedAssetBlob were defined with the given storage - storage, embargoed_storage = storage_tuple - AssetBlob.blob.field.storage = storage - EmbargoedAssetBlob.blob.field.storage = embargoed_storage + # Pretend like AssetBlob was defined with the given storage + monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) # Create dandiset and version dandiset: Dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) @@ -185,8 +180,8 @@ def test_unembargo_dandiset_existing_blobs( assign_perm('owner', user, dandiset) # Create embargoed assets - embargoed_asset_blob: EmbargoedAssetBlob = embargoed_asset_blob_factory() - embargoed_asset: Asset = asset_factory(embargoed_blob=embargoed_asset_blob, blob=None) + embargoed_asset_blob: AssetBlob = embargoed_asset_blob_factory() + embargoed_asset: Asset = asset_factory(blob=embargoed_asset_blob) draft_version.assets.add(embargoed_asset) # Create unembargoed asset with identical data @@ -197,9 +192,8 @@ def test_unembargo_dandiset_existing_blobs( ) # Assert properties before unembargo - assert embargoed_asset.embargoed_blob is not None - assert embargoed_asset.blob is None - assert embargoed_asset.embargoed_blob.etag != '' + assert embargoed_asset.blob.embargoed + assert embargoed_asset.blob.etag != '' assert existing_asset_blob.etag != '' assert embargoed_asset_blob.etag == existing_asset_blob.etag @@ -221,8 +215,7 @@ def test_unembargo_dandiset_existing_blobs( assert asset == embargoed_asset # Check blobs - assert asset.embargoed_blob is None - assert asset.blob is not None + assert not asset.blob.embargoed assert asset.blob.etag == embargoed_asset_blob.etag assert asset.blob == existing_asset_blob @@ -246,12 +239,12 @@ def test_unembargo_dandiset_normal_asset_blob( # Create asset asset_blob: AssetBlob = asset_blob_factory() - asset: Asset = asset_factory(blob=asset_blob, embargoed_blob=None) + asset: Asset = asset_factory(blob=asset_blob) draft_version.assets.add(asset) # Assert properties before unembargo - assert asset.embargoed_blob is None assert asset.blob is not None + assert not asset.blob.embargoed # Run unembargo unembargo_dandiset(user=user, dandiset=dandiset) @@ -272,7 +265,6 @@ def test_unembargo_dandiset_normal_asset_blob( # Check that blob is unchanged assert fetched_asset.blob == asset_blob - assert asset.embargoed_blob is None assert asset.blob is not None + assert not asset.blob.embargoed assert asset.blob.etag - assert asset.blob diff --git a/dandiapi/api/tests/test_upload.py b/dandiapi/api/tests/test_upload.py index ee0be5e30..c77f4f782 100644 --- a/dandiapi/api/tests/test_upload.py +++ b/dandiapi/api/tests/test_upload.py @@ -2,12 +2,12 @@ import uuid -from django.core.files.base import ContentFile -from guardian.shortcuts import assign_perm import pytest import requests +from django.core.files.base import ContentFile +from guardian.shortcuts import assign_perm -from dandiapi.api.models import AssetBlob, Dandiset, EmbargoedAssetBlob, EmbargoedUpload, Upload +from dandiapi.api.models import AssetBlob, Dandiset, EmbargoedUpload, Upload from .fuzzy import HTTP_URL_RE, UUID_RE, Re @@ -236,7 +236,7 @@ def test_upload_initialize_embargo_existing_embargoed_asset_blob( api_client.force_authenticate(user=user) dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) assign_perm('owner', user, dandiset) - # This EmbargoedAssetBlob is in the same embargoed dandiset, so it should be deduplicated + # This embargoed AssetBlob is in the same embargoed dandiset, so it should be deduplicated embargoed_asset_blob = embargoed_asset_blob_factory(dandiset=dandiset) resp = api_client.post( @@ -609,7 +609,7 @@ def test_upload_validate_embargo_existing_assetblob( @pytest.mark.django_db(transaction=True) -def test_upload_validate_embargo_existing_embargoedassetblob( +def test_upload_validate_embargo_existing_embargoed_assetblob( api_client, user, dandiset_factory, embargoed_upload_factory, embargoed_asset_blob_factory ): api_client.force_authenticate(user=user) @@ -638,7 +638,7 @@ def test_upload_validate_embargo_existing_embargoedassetblob( @pytest.mark.django_db(transaction=True) -def test_upload_validate_embargo_existing_embargoedassetblob_wrong_dandiset( +def test_upload_validate_embargo_existing_embargoed_assetblob_wrong_dandiset( api_client, user, dandiset_factory, embargoed_upload_factory, embargoed_asset_blob_factory ): api_client.force_authenticate(user=user) diff --git a/dandiapi/api/tests/test_version.py b/dandiapi/api/tests/test_version.py index 142ebe23a..8e21b4fa0 100644 --- a/dandiapi/api/tests/test_version.py +++ b/dandiapi/api/tests/test_version.py @@ -309,47 +309,50 @@ def test_version_publish_version(draft_version, asset): publish_version.save() assert publish_version.dandiset == draft_version.dandiset - assert publish_version.metadata == { - **draft_version.metadata, - 'publishedBy': { - 'id': URN_RE, - 'name': 'DANDI publish', - 'startDate': UTC_ISO_TIMESTAMP_RE, - 'endDate': UTC_ISO_TIMESTAMP_RE, - 'wasAssociatedWith': [ - { - 'id': URN_RE, - 'identifier': 'RRID:SCR_017571', - 'name': 'DANDI API', - # TODO: version the API - 'version': '0.1.0', - 'schemaKey': 'Software', - } + assert ( + publish_version.metadata + == { + **draft_version.metadata, + 'publishedBy': { + 'id': URN_RE, + 'name': 'DANDI publish', + 'startDate': UTC_ISO_TIMESTAMP_RE, + 'endDate': UTC_ISO_TIMESTAMP_RE, + 'wasAssociatedWith': [ + { + 'id': URN_RE, + 'identifier': 'RRID:SCR_017571', + 'name': 'DANDI API', + # TODO: version the API + 'version': '0.1.0', + 'schemaKey': 'Software', + } + ], + 'schemaKey': 'PublishActivity', + }, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'datePublished': UTC_ISO_TIMESTAMP_RE, + 'manifestLocation': [ + f'http://{settings.MINIO_STORAGE_ENDPOINT}/test-dandiapi-dandisets/test-prefix/dandisets/{publish_version.dandiset.identifier}/{publish_version.version}/assets.yaml', # noqa: E501 ], - 'schemaKey': 'PublishActivity', - }, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'datePublished': UTC_ISO_TIMESTAMP_RE, - 'manifestLocation': [ - f'http://{settings.MINIO_STORAGE_ENDPOINT}/test-dandiapi-dandisets/test-prefix/dandisets/{publish_version.dandiset.identifier}/{publish_version.version}/assets.yaml', - ], - 'identifier': f'DANDI:{publish_version.dandiset.identifier}', - 'version': publish_version.version, - 'id': f'DANDI:{publish_version.dandiset.identifier}/{publish_version.version}', - 'url': ( - f'{settings.DANDI_WEB_APP_URL}/dandiset/{publish_version.dandiset.identifier}' - f'/{publish_version.version}' - ), - 'citation': publish_version.citation(publish_version.metadata), - 'doi': fake_doi, - # The published_version cannot have a properly defined assetsSummary yet, since that would - # require having created rows the Asset-to-Version join table, which is a side affect. - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } + 'identifier': f'DANDI:{publish_version.dandiset.identifier}', + 'version': publish_version.version, + 'id': f'DANDI:{publish_version.dandiset.identifier}/{publish_version.version}', + 'url': ( + f'{settings.DANDI_WEB_APP_URL}/dandiset/{publish_version.dandiset.identifier}' + f'/{publish_version.version}' + ), + 'citation': publish_version.citation(publish_version.metadata), + 'doi': fake_doi, + # The published_version cannot have a properly defined assetsSummary yet, since that would + # require having created rows the Asset-to-Version join table, which is a side affect. + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } + ) @pytest.mark.django_db() @@ -389,9 +392,7 @@ def test_version_size( zarr_archive_factory, ): version.assets.add(asset_factory(blob=asset_blob_factory(size=100))) - version.assets.add( - asset_factory(blob=None, embargoed_blob=embargoed_asset_blob_factory(size=200)) - ) + version.assets.add(asset_factory(blob=embargoed_asset_blob_factory(size=200))) version.assets.add(asset_factory(blob=None, zarr=zarr_archive_factory(size=400))) add_version_asset_paths(version=version) diff --git a/dandiapi/api/views/asset.py b/dandiapi/api/views/asset.py index 6cc37d37c..a8fe74f4f 100644 --- a/dandiapi/api/views/asset.py +++ b/dandiapi/api/views/asset.py @@ -30,6 +30,7 @@ from django_filters import rest_framework as filters from drf_yasg.utils import swagger_auto_schema from guardian.decorators import permission_required_or_403 +from guardian.shortcuts import get_objects_for_user from rest_framework import serializers, status from rest_framework.decorators import action from rest_framework.exceptions import NotAuthenticated, NotFound, PermissionDenied @@ -39,7 +40,7 @@ from rest_framework_extensions.mixins import DetailSerializerMixin, NestedViewSetMixin from dandiapi.api.models import Asset, AssetBlob, Dandiset, Version -from dandiapi.api.models.asset import EmbargoedAssetBlob, validate_asset_path +from dandiapi.api.models.asset import validate_asset_path from dandiapi.api.views.common import ( ASSET_ID_PARAM, VERSIONS_DANDISET_PK_PARAM, @@ -84,12 +85,18 @@ def raise_if_unauthorized(self): asset_id = self.kwargs.get('asset_id') if asset_id is not None: asset = get_object_or_404(Asset, asset_id=asset_id) - if asset.embargoed_blob is not None: + + # TODO: When EmbargoedZarrArchive is implemented, check that as well + if asset.blob and asset.blob.embargoed: if not self.request.user.is_authenticated: # Clients must be authenticated to access it raise NotAuthenticated - if not self.request.user.has_perm('owner', asset.embargoed_blob.dandiset): - # The user does not have ownership permission + + # User must be an owner on any of the dandisets this asset belongs to + asset_dandisets = Dandiset.objects.filter(versions__in=asset.versions.all()) + user_dandisets = get_objects_for_user(self.request.user, 'owner', Dandiset) + intersection = asset_dandisets & user_dandisets + if not intersection.exists(): raise PermissionDenied def get_queryset(self): @@ -129,10 +136,7 @@ def download(self, request, *args, **kwargs): ) # Assign asset_blob - if asset.is_blob: - asset_blob = asset.blob - elif asset.is_embargoed_blob: - asset_blob = asset.embargoed_blob + asset_blob = asset.blob # Redirect to correct presigned URL storage = asset_blob.blob.storage @@ -185,19 +189,12 @@ class AssetRequestSerializer(serializers.Serializer): blob_id = serializers.UUIDField(required=False) zarr_id = serializers.UUIDField(required=False) - def get_blob(self) -> AssetBlob | EmbargoedAssetBlob | None: + def get_blob(self) -> AssetBlob | None: asset_blob = None - embargoed_asset_blob = None - if 'blob_id' in self.validated_data: - try: - asset_blob = AssetBlob.objects.get(blob_id=self.validated_data['blob_id']) - except AssetBlob.DoesNotExist: - embargoed_asset_blob = get_object_or_404( - EmbargoedAssetBlob, blob_id=self.validated_data['blob_id'] - ) + asset_blob = get_object_or_404(AssetBlob, blob_id=self.validated_data['blob_id']) - return asset_blob or embargoed_asset_blob + return asset_blob def get_zarr_archive(self) -> ZarrArchive | None: zarr_archive = None @@ -254,7 +251,11 @@ def get_queryset(self): @swagger_auto_schema( method='GET', operation_summary='Django serialization of an asset', - manual_parameters=[ASSET_ID_PARAM, VERSIONS_DANDISET_PK_PARAM, VERSIONS_VERSION_PARAM], + manual_parameters=[ + ASSET_ID_PARAM, + VERSIONS_DANDISET_PK_PARAM, + VERSIONS_VERSION_PARAM, + ], responses={200: AssetDetailSerializer}, ) @action(detail=True, methods=['GET']) @@ -266,7 +267,11 @@ def info(self, *args, **kwargs): method='GET', operation_summary='Get the download link for an asset.', operation_description='', - manual_parameters=[ASSET_ID_PARAM, VERSIONS_DANDISET_PK_PARAM, VERSIONS_VERSION_PARAM], + manual_parameters=[ + ASSET_ID_PARAM, + VERSIONS_DANDISET_PK_PARAM, + VERSIONS_VERSION_PARAM, + ], responses={ 200: None, # This disables the auto-generated 200 response 301: 'Redirect to object store', @@ -280,7 +285,11 @@ def download(self, *args, **kwargs): @swagger_auto_schema( responses={200: AssetValidationSerializer}, - manual_parameters=[ASSET_ID_PARAM, VERSIONS_DANDISET_PK_PARAM, VERSIONS_VERSION_PARAM], + manual_parameters=[ + ASSET_ID_PARAM, + VERSIONS_DANDISET_PK_PARAM, + VERSIONS_VERSION_PARAM, + ], operation_summary='Get any validation errors associated with an asset', operation_description='', ) @@ -432,9 +441,7 @@ def list(self, request, *args, **kwargs): # Now we can retrieve the actual fully joined rows using the limited number of assets we're # going to return queryset = self.filter_queryset( - Asset.objects.filter(id__in=page_of_asset_ids).select_related( - 'blob', 'embargoed_blob', 'zarr' - ) + Asset.objects.filter(id__in=page_of_asset_ids).select_related('blob', 'zarr') ) # Must apply this to the main queryset, since it affects the data returned diff --git a/dandiapi/api/views/dandiset.py b/dandiapi/api/views/dandiset.py index 14b06e4ae..93bcef604 100644 --- a/dandiapi/api/views/dandiset.py +++ b/dandiapi/api/views/dandiset.py @@ -86,7 +86,6 @@ def filter_queryset(self, request, queryset, view): size=Subquery( latest_version.annotate( size=Coalesce(Sum('assets__blob__size'), 0) - + Coalesce(Sum('assets__embargoed_blob__size'), 0) + Coalesce(Sum('assets__zarr__size'), 0) ).values('size') ) diff --git a/dandiapi/api/views/serializers.py b/dandiapi/api/views/serializers.py index b6026eeb3..3f96c37b9 100644 --- a/dandiapi/api/views/serializers.py +++ b/dandiapi/api/views/serializers.py @@ -319,26 +319,6 @@ class AssetDownloadQueryParameterSerializer(serializers.Serializer): content_disposition = serializers.ChoiceField(['attachment', 'inline'], default='attachment') -class EmbargoedSlugRelatedField(serializers.SlugRelatedField): - """ - A Field for cleanly serializing embargoed model fields. - - Embargoed fields are paired with their non-embargoed equivalents, like "blob" and - "embargoed_blob", or "zarr" and "embargoed_zarr". There are DB constraints in place to ensure - that only one field is defined at a time. When serializing one of those pairs, we would like to - conceal the fact that the field might be embargoed by silently using the embargoed model field - in place of the normal field if it is defined. - """ - - def get_attribute(self, instance: Asset): - attr = super().get_attribute(instance) - if attr is None: - # The normal field was not defined on the model, try the embargoed_ variant instead - embargoed_source = f'embargoed_{self.source}' - attr = getattr(instance, embargoed_source, None) - return attr - - class AssetSerializer(serializers.ModelSerializer): class Meta: model = Asset @@ -354,7 +334,7 @@ class Meta: ] read_only_fields = ['created'] - blob = EmbargoedSlugRelatedField(slug_field='blob_id', read_only=True) + blob = serializers.SlugRelatedField(slug_field='blob_id', read_only=True) zarr = serializers.SlugRelatedField(slug_field='zarr_id', read_only=True) metadata = serializers.JSONField(source='full_metadata') diff --git a/dandiapi/api/views/upload.py b/dandiapi/api/views/upload.py index fb00f12e7..178d9ea16 100644 --- a/dandiapi/api/views/upload.py +++ b/dandiapi/api/views/upload.py @@ -15,8 +15,7 @@ from rest_framework.response import Response from s3_file_field._multipart import TransferredPart, TransferredParts -from dandiapi.api.models import AssetBlob, Dandiset, EmbargoedUpload, Upload -from dandiapi.api.models.asset import EmbargoedAssetBlob +from dandiapi.api.models import AssetBlob, Dandiset, Upload from dandiapi.api.permissions import IsApproved from dandiapi.api.tasks import calculate_sha256 from dandiapi.api.views.serializers import AssetBlobSerializer @@ -150,22 +149,10 @@ def upload_initialize_view(request: Request) -> HttpResponseBase: status=status.HTTP_409_CONFLICT, headers={'Location': asset_blobs.first().blob_id}, ) - if dandiset.embargo_status != Dandiset.EmbargoStatus.OPEN: - embargoed_asset_blobs = EmbargoedAssetBlob.objects.filter(dandiset=dandiset, etag=etag) - if embargoed_asset_blobs.exists(): - return Response( - 'Blob already exists.', - status=status.HTTP_409_CONFLICT, - headers={'Location': embargoed_asset_blobs.first().blob_id}, - ) + logging.info('Blob with ETag %s does not yet exist', etag) - if dandiset.embargo_status == Dandiset.EmbargoStatus.OPEN: - upload, initialization = Upload.initialize_multipart_upload(etag, content_size, dandiset) - else: - upload, initialization = EmbargoedUpload.initialize_multipart_upload( - etag, content_size, dandiset - ) + upload, initialization = Upload.initialize_multipart_upload(etag, content_size, dandiset) logging.info('Upload of ETag %s initialized', etag) upload.save() logging.info('Upload of ETag %s saved', etag) @@ -195,12 +182,9 @@ def upload_complete_view(request: Request, upload_id: str) -> HttpResponseBase: request_serializer.is_valid(raise_exception=True) parts: list[TransferredPart] = request_serializer.save() - try: - upload = Upload.objects.get(upload_id=upload_id) - except Upload.DoesNotExist: - upload = get_object_or_404(EmbargoedUpload, upload_id=upload_id) - if not request.user.has_perm('owner', upload.dandiset): - raise Http404 from None + upload: Upload = get_object_or_404(Upload, upload_id=upload_id) + if upload.embargoed and not request.user.has_perm('owner', upload.dandiset): + raise Http404 from None completion = TransferredParts( object_key=upload.blob.name, @@ -235,12 +219,9 @@ def upload_validate_view(request: Request, upload_id: str) -> HttpResponseBase: Also starts the asynchronous checksum calculation process. """ - try: - upload = Upload.objects.get(upload_id=upload_id) - except Upload.DoesNotExist: - upload = get_object_or_404(EmbargoedUpload, upload_id=upload_id) - if not request.user.has_perm('owner', upload.dandiset): - raise Http404 from None + upload = get_object_or_404(Upload, upload_id=upload_id) + if upload.embargoed and not request.user.has_perm('owner', upload.dandiset): + raise Http404 from None # Verify that the upload was successful if not upload.object_key_exists(): @@ -265,20 +246,8 @@ def upload_validate_view(request: Request, upload_id: str) -> HttpResponseBase: etag=upload.etag, size=upload.size ) except AssetBlob.DoesNotExist: - if type(upload) is EmbargoedUpload: - # Perhaps another embargoed upload completed before this one - try: - asset_blob = EmbargoedAssetBlob.objects.select_for_update(no_key=True).get( - etag=upload.etag, size=upload.size, dandiset=upload.dandiset - ) - except EmbargoedAssetBlob.DoesNotExist: - asset_blob = upload.to_embargoed_asset_blob() - asset_blob.save() - elif type(upload) is Upload: - asset_blob = upload.to_asset_blob() - asset_blob.save() - else: - raise ValueError(f'Unknown Upload type {type(upload)}') from None + asset_blob = upload.to_asset_blob() + asset_blob.save() # Clean up the upload upload.delete() diff --git a/dandiapi/conftest.py b/dandiapi/conftest.py index 4af84693b..300cd1cb3 100644 --- a/dandiapi/conftest.py +++ b/dandiapi/conftest.py @@ -3,6 +3,8 @@ from typing import TYPE_CHECKING from django.conf import settings +from django.core.files.storage import Storage +from minio_storage.storage import MinioStorage import pytest from pytest_factoryboy import register from rest_framework.test import APIClient @@ -34,7 +36,6 @@ register(AssetBlobFactory) register(EmbargoedAssetBlobFactory) register(DandisetFactory) -register(EmbargoedAssetBlobFactory) register(EmbargoedUploadFactory) register(PublishedVersionFactory, _name='published_version') register(DraftVersionFactory, _name='draft_version') @@ -156,24 +157,3 @@ def storage(request, settings) -> Storage: ) return storage_factory() - - -@pytest.fixture( - params=[embargoed_s3_storage_factory, embargoed_minio_storage_factory], - ids=['s3', 'minio'], -) -def embargoed_storage(request) -> Storage: - storage_factory = request.param - return storage_factory() - - -@pytest.fixture( - params=[ - (s3_storage_factory, embargoed_s3_storage_factory), - (minio_storage_factory, embargoed_minio_storage_factory), - ], - ids=['s3', 'minio'], -) -def storage_tuple(request) -> tuple[Storage, Storage]: - storage_factory, embargoed_storage_factory = request.param - return (storage_factory(), embargoed_storage_factory()) diff --git a/dandiapi/settings.py b/dandiapi/settings.py index 00de8f416..69a5196fd 100644 --- a/dandiapi/settings.py +++ b/dandiapi/settings.py @@ -88,8 +88,6 @@ def mutate_configuration(configuration: type[ComposedConfiguration]): DANDI_DANDISETS_BUCKET_NAME = values.Value(environ_required=True) DANDI_DANDISETS_BUCKET_PREFIX = values.Value(default='', environ=True) DANDI_DANDISETS_LOG_BUCKET_NAME = values.Value(environ_required=True) - DANDI_DANDISETS_EMBARGO_BUCKET_NAME = values.Value(environ_required=True) - DANDI_DANDISETS_EMBARGO_BUCKET_PREFIX = values.Value(default='', environ=True) DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME = values.Value(environ_required=True) DANDI_ZARR_PREFIX_NAME = values.Value(default='zarr', environ=True) @@ -150,8 +148,6 @@ class TestingConfiguration(DandiMixin, TestingBaseConfiguration): DANDI_DANDISETS_BUCKET_NAME = 'test-dandiapi-dandisets' DANDI_DANDISETS_BUCKET_PREFIX = 'test-prefix/' DANDI_DANDISETS_LOG_BUCKET_NAME = 'test-dandiapi-dandisets-logs' - DANDI_DANDISETS_EMBARGO_BUCKET_NAME = 'test--embargo-dandiapi-dandisets' - DANDI_DANDISETS_EMBARGO_BUCKET_PREFIX = 'test-embargo-prefix/' DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME = 'test-embargo-dandiapi-dandisets-logs' DANDI_ZARR_PREFIX_NAME = 'test-zarr' DANDI_JUPYTERHUB_URL = 'https://hub.dandiarchive.org/' diff --git a/dandiapi/zarr/models.py b/dandiapi/zarr/models.py index d57ff2e3a..9ffd6a55b 100644 --- a/dandiapi/zarr/models.py +++ b/dandiapi/zarr/models.py @@ -10,7 +10,7 @@ from rest_framework.exceptions import ValidationError from dandiapi.api.models import Dandiset -from dandiapi.api.storage import get_embargo_storage, get_storage +from dandiapi.api.storage import get_storage logger = logging.getLogger(name=__name__) @@ -109,7 +109,7 @@ def s3_path(self, zarr_path: str) -> str: class EmbargoedZarrArchive(BaseZarrArchive): - storage = get_embargo_storage() + storage = get_storage() dandiset = models.ForeignKey( Dandiset, related_name='embargoed_zarr_archives', on_delete=models.CASCADE ) @@ -117,6 +117,6 @@ class EmbargoedZarrArchive(BaseZarrArchive): def s3_path(self, zarr_path: str) -> str: """Generate a full S3 object path from a path in this zarr_archive.""" return ( - f'{settings.DANDI_DANDISETS_EMBARGO_BUCKET_PREFIX}{settings.DANDI_ZARR_PREFIX_NAME}/' + f'{settings.DANDI_ZARR_PREFIX_NAME}/' f'{self.dandiset.identifier}/{self.zarr_id}/{zarr_path}' ) diff --git a/dandiapi/zarr/tests/test_ingest_zarr_archive.py b/dandiapi/zarr/tests/test_ingest_zarr_archive.py index c14c3b8b6..249fec056 100644 --- a/dandiapi/zarr/tests/test_ingest_zarr_archive.py +++ b/dandiapi/zarr/tests/test_ingest_zarr_archive.py @@ -86,7 +86,7 @@ def test_ingest_zarr_archive_force(zarr_archive_factory, zarr_file_factory): def test_ingest_zarr_archive_assets(zarr_archive_factory, zarr_file_factory, draft_asset_factory): # Create zarr and asset zarr: ZarrArchive = zarr_archive_factory(status=ZarrArchiveStatus.UPLOADED) - asset = draft_asset_factory(zarr=zarr, blob=None, embargoed_blob=None) + asset = draft_asset_factory(zarr=zarr, blob=None) # Assert asset size, metadata assert asset.size == 0 diff --git a/dev/.env.docker-compose b/dev/.env.docker-compose index 2b939ca13..61ab9c0ca 100644 --- a/dev/.env.docker-compose +++ b/dev/.env.docker-compose @@ -8,7 +8,6 @@ DJANGO_STORAGE_BUCKET_NAME=django-storage DJANGO_MINIO_STORAGE_MEDIA_URL=http://localhost:9000/django-storage DJANGO_DANDI_DANDISETS_BUCKET_NAME=dandi-dandisets DJANGO_DANDI_DANDISETS_LOG_BUCKET_NAME=dandiapi-dandisets-logs -DJANGO_DANDI_DANDISETS_EMBARGO_BUCKET_NAME=dandi-embargo-dandisets DJANGO_DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME=dandiapi-embargo-dandisets-logs DJANGO_DANDI_WEB_APP_URL=http://localhost:8085 DJANGO_DANDI_API_URL=http://localhost:8000 diff --git a/dev/.env.docker-compose-native b/dev/.env.docker-compose-native index 57926fe72..3aed3d184 100644 --- a/dev/.env.docker-compose-native +++ b/dev/.env.docker-compose-native @@ -8,7 +8,6 @@ DJANGO_MINIO_STORAGE_MEDIA_URL=http://localhost:9000/django-storage DJANGO_STORAGE_BUCKET_NAME=django-storage DJANGO_DANDI_DANDISETS_BUCKET_NAME=dandi-dandisets DJANGO_DANDI_DANDISETS_LOG_BUCKET_NAME=dandiapi-dandisets-logs -DJANGO_DANDI_DANDISETS_EMBARGO_BUCKET_NAME=dandi-embargo-dandisets DJANGO_DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME=dandiapi-embargo-dandisets-logs DJANGO_DANDI_WEB_APP_URL=http://localhost:8085 DJANGO_DANDI_API_URL=http://localhost:8000 From 0f475c9ccb2e654b48d2b6e7bb824ae5836e82b3 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Wed, 21 Feb 2024 17:41:21 -0500 Subject: [PATCH 02/21] Fix tests --- .../analytics/tests/test_download_counts.py | 12 +- dandiapi/api/tests/test_unembargo.py | 270 ------------------ dandiapi/api/tests/test_upload.py | 152 ++-------- dandiapi/conftest.py | 20 +- 4 files changed, 30 insertions(+), 424 deletions(-) delete mode 100644 dandiapi/api/tests/test_unembargo.py diff --git a/dandiapi/analytics/tests/test_download_counts.py b/dandiapi/analytics/tests/test_download_counts.py index 4b1f6a480..ba7c0ec07 100644 --- a/dandiapi/analytics/tests/test_download_counts.py +++ b/dandiapi/analytics/tests/test_download_counts.py @@ -5,12 +5,7 @@ from dandiapi.analytics.models import ProcessedS3Log from dandiapi.analytics.tasks import collect_s3_log_records_task, process_s3_log_file_task -from dandiapi.api.storage import ( - create_s3_storage, - get_boto_client, - get_embargo_storage, - get_storage, -) +from dandiapi.api.storage import create_s3_storage, get_boto_client @pytest.fixture() @@ -20,8 +15,9 @@ def s3_log_bucket(): @pytest.fixture() def s3_log_file(s3_log_bucket, asset_blob): - embargoed = s3_log_bucket == settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME - s3 = get_boto_client(get_storage() if not embargoed else get_embargo_storage()) + # TODO: What to do when embargoed + # embargoed = s3_log_bucket == settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME + s3 = get_boto_client() log_file_name = '2019-02-06-00-00-38-5C5B0E0CA8F2B1B5' s3.put_object( diff --git a/dandiapi/api/tests/test_unembargo.py b/dandiapi/api/tests/test_unembargo.py deleted file mode 100644 index bd941b456..000000000 --- a/dandiapi/api/tests/test_unembargo.py +++ /dev/null @@ -1,270 +0,0 @@ -from __future__ import annotations - -import math -import os - -import factory -import pytest -from dandischema.digests.dandietag import PartGenerator, mb -from django.contrib.auth.models import AnonymousUser -from django.core.files.uploadedfile import SimpleUploadedFile -from guardian.shortcuts import assign_perm - -from dandiapi.api import tasks -from dandiapi.api.models import Asset, AssetBlob, Version -from dandiapi.api.models.dandiset import Dandiset -from dandiapi.api.services.embargo import _unembargo_asset, unembargo_dandiset - - -@pytest.mark.django_db() -def test_asset_unembargo( - storage, asset_factory, embargoed_asset_blob_factory, draft_version, monkeypatch -): - # Pretend like AssetBlob was defined with the given storage - monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) - - embargoed_asset_blob: AssetBlob = embargoed_asset_blob_factory() - embargoed_asset: Asset = asset_factory(blob=embargoed_asset_blob) - draft_version.assets.add(embargoed_asset) - - # Assert embargoed properties - embargoed_sha256 = embargoed_asset.sha256 - embargoed_etag = embargoed_asset.blob.etag - assert embargoed_asset.blob is not None - assert embargoed_asset.blob.embargoed - assert embargoed_asset.sha256 is not None - assert 'embargo' in embargoed_asset.full_metadata['contentUrl'][1] - - # Unembargo - _unembargo_asset(embargoed_asset) - asset = embargoed_asset - asset.refresh_from_db() - - # Assert unembargoed properties - assert asset.blob is not None - assert not asset.blob.embargoed - assert asset.sha256 == embargoed_sha256 - assert asset.blob.etag == embargoed_etag - assert 'embargo' not in asset.full_metadata['contentUrl'][1] - - -@pytest.mark.parametrize( - ('embargo_status', 'user_status', 'resp_code'), - [ - (Dandiset.EmbargoStatus.OPEN, 'owner', 400), - (Dandiset.EmbargoStatus.OPEN, 'anonymous', 401), - (Dandiset.EmbargoStatus.OPEN, 'not-owner', 403), - (Dandiset.EmbargoStatus.EMBARGOED, 'owner', 200), - (Dandiset.EmbargoStatus.EMBARGOED, 'anonymous', 401), - (Dandiset.EmbargoStatus.EMBARGOED, 'not-owner', 403), - (Dandiset.EmbargoStatus.UNEMBARGOING, 'owner', 400), - (Dandiset.EmbargoStatus.UNEMBARGOING, 'anonymous', 401), - (Dandiset.EmbargoStatus.UNEMBARGOING, 'not-owner', 403), - ], -) -@pytest.mark.django_db() -def test_dandiset_rest_unembargo( - api_client, - dandiset_factory, - draft_version_factory, - user_factory, - embargo_status, - user_status, - resp_code, -): - dandiset: Dandiset = dandiset_factory(embargo_status=embargo_status) - draft_version_factory(dandiset=dandiset) - - if user_status == 'anonymous': - user = AnonymousUser - else: - user = user_factory() - api_client.force_authenticate(user=user) - if user_status == 'owner': - assign_perm('owner', user, dandiset) - - response = api_client.post(f'/api/dandisets/{dandiset.identifier}/unembargo/') - assert response.status_code == resp_code - - -@pytest.mark.parametrize( - ('file_size', 'part_size'), - [ - (100, mb(64)), # Normal - (mb(30), mb(5)), # Few parts - (mb(100), mb(6)), # Many parts (tests concurrency) - ], -) -@pytest.mark.django_db() -def test_unembargo_dandiset( - user, - dandiset_factory, - draft_version_factory, - asset_factory, - embargoed_asset_blob_factory, - storage, - file_size, - part_size, - monkeypatch, -): - # Pretend like AssetBlob was defined with the given storage - monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) - - # Monkey patch PartGenerator so that upload and copy use a smaller part size - monkeypatch.setattr(PartGenerator, 'DEFAULT_PART_SIZE', part_size, raising=True) - - # Create dandiset and version - dandiset: Dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) - draft_version: Version = draft_version_factory(dandiset=dandiset) - assign_perm('owner', user, dandiset) - - # Create an embargoed asset blob - embargoed_asset_blob: AssetBlob = embargoed_asset_blob_factory( - size=file_size, blob=SimpleUploadedFile('test', content=os.urandom(file_size)) - ) - - # Assert multiple parts were used - num_parts = math.ceil(file_size / part_size) - assert embargoed_asset_blob.etag.endswith(f'-{num_parts}') - - # Create asset from embargoed blob - embargoed_asset: Asset = asset_factory(blob=embargoed_asset_blob) - draft_version.assets.add(embargoed_asset) - - # Assert properties before unembargo - assert embargoed_asset.blob.embargoed - assert embargoed_asset.blob.etag != '' - - # Run unembargo and validate version metadata - unembargo_dandiset(user=user, dandiset=dandiset) - tasks.validate_version_metadata_task(draft_version.pk) - dandiset.refresh_from_db() - draft_version.refresh_from_db() - - # Assert correct changes took place - assert dandiset.embargo_status == Dandiset.EmbargoStatus.OPEN - assert draft_version.status == Version.Status.VALID - assert draft_version.metadata['access'] == [ - {'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'} - ] - - # Assert no new asset created - asset: Asset = draft_version.assets.first() - assert asset == embargoed_asset - - # Check blobs - assert not asset.blob.embargoed - assert asset.blob.etag == embargoed_asset_blob.etag - - blob_id = str(asset.blob.blob_id) - assert asset.blob.blob.name == f'test-prefix/blobs/{blob_id[:3]}/{blob_id[3:6]}/{blob_id}' - - -@pytest.mark.django_db() -def test_unembargo_dandiset_existing_blobs( - user, - dandiset_factory, - draft_version_factory, - asset_factory, - asset_blob_factory, - embargoed_asset_blob_factory, - storage, - monkeypatch, -): - # Pretend like AssetBlob was defined with the given storage - monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) - - # Create dandiset and version - dandiset: Dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) - draft_version: Version = draft_version_factory(dandiset=dandiset) - assign_perm('owner', user, dandiset) - - # Create embargoed assets - embargoed_asset_blob: AssetBlob = embargoed_asset_blob_factory() - embargoed_asset: Asset = asset_factory(blob=embargoed_asset_blob) - draft_version.assets.add(embargoed_asset) - - # Create unembargoed asset with identical data - embargoed_asset_blob_data = embargoed_asset_blob.blob.read() - embargoed_asset_blob.blob.seek(0) - existing_asset_blob = asset_blob_factory( - blob=factory.django.FileField(data=embargoed_asset_blob_data) - ) - - # Assert properties before unembargo - assert embargoed_asset.blob.embargoed - assert embargoed_asset.blob.etag != '' - assert existing_asset_blob.etag != '' - assert embargoed_asset_blob.etag == existing_asset_blob.etag - - # Run unembargo - unembargo_dandiset(user=user, dandiset=dandiset) - tasks.validate_version_metadata_task(draft_version.pk) - dandiset.refresh_from_db() - draft_version.refresh_from_db() - - # Assert correct changes took place - assert dandiset.embargo_status == Dandiset.EmbargoStatus.OPEN - assert draft_version.status == Version.Status.VALID - assert draft_version.metadata['access'] == [ - {'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'} - ] - - # Assert no new asset created - asset: Asset = draft_version.assets.first() - assert asset == embargoed_asset - - # Check blobs - assert not asset.blob.embargoed - assert asset.blob.etag == embargoed_asset_blob.etag - assert asset.blob == existing_asset_blob - - -@pytest.mark.django_db() -def test_unembargo_dandiset_normal_asset_blob( - user, - dandiset_factory, - draft_version_factory, - asset_factory, - asset_blob_factory, - storage, -): - # Pretend like AssetBlob was defined with the given storage - AssetBlob.blob.field.storage = storage - - # Create dandiset and version - dandiset: Dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) - draft_version: Version = draft_version_factory(dandiset=dandiset) - assign_perm('owner', user, dandiset) - - # Create asset - asset_blob: AssetBlob = asset_blob_factory() - asset: Asset = asset_factory(blob=asset_blob) - draft_version.assets.add(asset) - - # Assert properties before unembargo - assert asset.blob is not None - assert not asset.blob.embargoed - - # Run unembargo - unembargo_dandiset(user=user, dandiset=dandiset) - tasks.validate_version_metadata_task(draft_version.pk) - dandiset.refresh_from_db() - draft_version.refresh_from_db() - - # Assert correct changes took place - assert dandiset.embargo_status == Dandiset.EmbargoStatus.OPEN - assert draft_version.status == Version.Status.VALID - assert draft_version.metadata['access'] == [ - {'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'} - ] - - # Assert no new asset created - fetched_asset: Asset = draft_version.assets.first() - assert asset == fetched_asset - - # Check that blob is unchanged - assert fetched_asset.blob == asset_blob - assert asset.blob is not None - assert not asset.blob.embargoed - assert asset.blob.etag diff --git a/dandiapi/api/tests/test_upload.py b/dandiapi/api/tests/test_upload.py index c77f4f782..e8a7493f8 100644 --- a/dandiapi/api/tests/test_upload.py +++ b/dandiapi/api/tests/test_upload.py @@ -2,12 +2,12 @@ import uuid -import pytest -import requests from django.core.files.base import ContentFile from guardian.shortcuts import assign_perm +import pytest +import requests -from dandiapi.api.models import AssetBlob, Dandiset, EmbargoedUpload, Upload +from dandiapi.api.models import AssetBlob, Dandiset, Upload from .fuzzy import HTTP_URL_RE, UUID_RE, Re @@ -66,7 +66,13 @@ def test_blob_read_does_not_exist(api_client): @pytest.mark.django_db() -def test_upload_initialize(api_client, user, dandiset): +@pytest.mark.parametrize('embargoed', [True, False]) +def test_upload_initialize(api_client, user, dandiset_factory, embargoed): + dandiset = dandiset_factory( + embargo_status=Dandiset.EmbargoStatus.EMBARGOED + if embargoed + else Dandiset.EmbargoStatus.OPEN + ) api_client.force_authenticate(user=user) assign_perm('owner', user, dandiset) @@ -97,6 +103,8 @@ def test_upload_initialize(api_client, user, dandiset): assert 'X-Amz-Expires=604800' in upload_url upload = Upload.objects.get(upload_id=resp.data['upload_id']) + assert upload.embargoed == embargoed + upload_id = str(upload.upload_id) assert upload.blob.name == f'test-prefix/blobs/{upload_id[:3]}/{upload_id[3:6]}/{upload_id}' @@ -138,48 +146,6 @@ def test_upload_initialize_not_an_owner(api_client, user, dandiset): ) assert resp.status_code == 403 assert not Upload.objects.all().exists() - assert not EmbargoedUpload.objects.all().exists() - - -@pytest.mark.django_db() -def test_upload_initialize_embargo(api_client, user, dandiset_factory): - api_client.force_authenticate(user=user) - dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) - assign_perm('owner', user, dandiset) - - content_size = 123 - - resp = api_client.post( - '/api/uploads/initialize/', - { - 'contentSize': content_size, - 'digest': {'algorithm': 'dandi:dandi-etag', 'value': 'f' * 32 + '-1'}, - 'dandiset': dandiset.identifier, - }, - format='json', - ) - assert resp.data == { - 'upload_id': UUID_RE, - 'parts': [ - { - 'part_number': 1, - 'size': content_size, - 'upload_url': HTTP_URL_RE, - } - ], - } - # Verify that the URL won't expire for a week - upload_url = resp.data['parts'][0]['upload_url'] - # 604800 seconds = 1 week - assert 'X-Amz-Expires=604800' in upload_url - - assert not Upload.objects.all().exists() - upload = EmbargoedUpload.objects.get(upload_id=resp.data['upload_id']) - upload_id = str(upload.upload_id) - assert upload.blob.name == ( - f'test-embargo-prefix/{dandiset.identifier}/blobs/' - f'{upload_id[:3]}/{upload_id[3:6]}/{upload_id}' - ) @pytest.mark.django_db() @@ -202,7 +168,6 @@ def test_upload_initialize_embargo_not_an_owner(api_client, user, dandiset_facto assert resp.status_code == 404 assert resp.json() == {'detail': 'Not found.'} assert not Upload.objects.all().exists() - assert not EmbargoedUpload.objects.all().exists() @pytest.mark.django_db() @@ -237,7 +202,7 @@ def test_upload_initialize_embargo_existing_embargoed_asset_blob( dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) assign_perm('owner', user, dandiset) # This embargoed AssetBlob is in the same embargoed dandiset, so it should be deduplicated - embargoed_asset_blob = embargoed_asset_blob_factory(dandiset=dandiset) + embargoed_asset_blob = embargoed_asset_blob_factory() resp = api_client.post( '/api/uploads/initialize/', @@ -254,31 +219,6 @@ def test_upload_initialize_embargo_existing_embargoed_asset_blob( assert not Upload.objects.all().exists() -@pytest.mark.django_db() -def test_upload_initialize_embargo_existing_embargoed_asset_blob_in_different_dandiset( - api_client, user, dandiset_factory, embargoed_asset_blob_factory -): - api_client.force_authenticate(user=user) - dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) - assign_perm('owner', user, dandiset) - other_dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) - # This EmbargoedAssetBlob is in a different dandiset, so it needs to be reuploaded, even - # though this user is an owner of both dandisets. - embargoed_asset_blob = embargoed_asset_blob_factory(dandiset=other_dandiset) - assign_perm('owner', user, other_dandiset) - - resp = api_client.post( - '/api/uploads/initialize/', - { - 'contentSize': embargoed_asset_blob.size, - 'digest': {'algorithm': 'dandi:dandi-etag', 'value': embargoed_asset_blob.etag}, - 'dandiset': dandiset.identifier, - }, - format='json', - ) - assert resp.status_code == 200 - - @pytest.mark.django_db() def test_upload_initialize_unauthorized(api_client): assert ( @@ -417,11 +357,11 @@ def test_upload_initialize_and_complete(api_client, user, dandiset, content_size @pytest.mark.django_db(transaction=True) @pytest.mark.parametrize('content_size', [10, mb(10), mb(12)], ids=['10B', '10MB', '12MB']) def test_upload_initialize_and_complete_embargo( - storage, api_client, user, dandiset_factory, content_size + storage, api_client, user, dandiset_factory, content_size, monkeypatch ): # Pretend like the blobs were defined with the given storage - EmbargoedUpload.blob.field.storage = storage - EmbargoedAssetBlob.blob.field.storage = storage + monkeypatch.setattr(Upload.blob.field, 'storage', storage) + monkeypatch.setattr(AssetBlob.blob.field, 'storage', storage) api_client.force_authenticate(user=user) dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) @@ -466,11 +406,9 @@ def test_upload_initialize_and_complete_embargo( assert completion_response.status_code == 200 # Verify object was uploaded - upload = EmbargoedUpload.objects.get(upload_id=upload_id) - assert EmbargoedAssetBlob.blob.field.storage.exists(upload.blob.name) - assert upload.blob.name.startswith(f'test-embargo-prefix/{dandiset.identifier}/blobs/') - # Verify nothing public was created - assert not Upload.objects.all().exists() + upload = Upload.objects.get(upload_id=upload_id) + assert AssetBlob.blob.field.storage.exists(upload.blob.name) + assert upload.blob.name.startswith('test-prefix/blobs/') @pytest.mark.django_db(transaction=True) @@ -510,12 +448,12 @@ def test_upload_validate_embargo(api_client, user, dandiset_factory, embargoed_u 'size': embargoed_upload.size, } - # Verify that a new EmbargoedAssetBlob was created - embargoed_asset_blob = EmbargoedAssetBlob.objects.get(blob_id=embargoed_upload.upload_id) + # Verify that a new embargoed AssetBlob was created + embargoed_asset_blob = AssetBlob.objects.get(blob_id=embargoed_upload.upload_id) assert embargoed_asset_blob.blob.name == embargoed_upload.blob.name # Verify that the Upload was deleted - assert not EmbargoedUpload.objects.all().exists() + assert not Upload.objects.all().exists() @pytest.mark.django_db(transaction=True) @@ -604,8 +542,6 @@ def test_upload_validate_embargo_existing_assetblob( } assert AssetBlob.objects.all().count() == 1 - assert not EmbargoedAssetBlob.objects.all().exists() - assert not EmbargoedUpload.objects.all().exists() @pytest.mark.django_db(transaction=True) @@ -617,10 +553,10 @@ def test_upload_validate_embargo_existing_embargoed_assetblob( assign_perm('owner', user, dandiset) embargoed_upload = embargoed_upload_factory(dandiset=dandiset) - # The upload should recognize this preexisting EmbargoedAssetBlob and use it instead + # The upload should recognize this preexisting embargoed AssetBlob and use it instead # This only works because the embargoed asset blob belongs to the same dandiset embargoed_asset_blob = embargoed_asset_blob_factory( - etag=embargoed_upload.etag, size=embargoed_upload.size, dandiset=dandiset + etag=embargoed_upload.etag, size=embargoed_upload.size ) resp = api_client.post(f'/api/uploads/{embargoed_upload.upload_id}/validate/') @@ -632,42 +568,4 @@ def test_upload_validate_embargo_existing_embargoed_assetblob( 'size': embargoed_asset_blob.size, } - assert EmbargoedAssetBlob.objects.all().count() == 1 - assert not AssetBlob.objects.all().exists() - assert not EmbargoedUpload.objects.all().exists() - - -@pytest.mark.django_db(transaction=True) -def test_upload_validate_embargo_existing_embargoed_assetblob_wrong_dandiset( - api_client, user, dandiset_factory, embargoed_upload_factory, embargoed_asset_blob_factory -): - api_client.force_authenticate(user=user) - dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) - assign_perm('owner', user, dandiset) - embargoed_upload = embargoed_upload_factory(dandiset=dandiset) - - # This should mint a new EmbargoedAssetBlob because the existing EmbargoedAssetBlob belongs to - # a different dandiset. - other_dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) - assign_perm('owner', user, other_dandiset) - other_embargoed_asset_blob = embargoed_asset_blob_factory( - etag=embargoed_upload.etag, size=embargoed_upload.size, dandiset=other_dandiset - ) - - resp = api_client.post(f'/api/uploads/{embargoed_upload.upload_id}/validate/') - assert resp.status_code == 200 - assert resp.data == { - 'blob_id': str(embargoed_upload.upload_id), - 'etag': embargoed_upload.etag, - 'sha256': None, - 'size': embargoed_upload.size, - } - - # Verify that a new EmbargoedAssetBlob was created - embargoed_asset_blob = EmbargoedAssetBlob.objects.get(blob_id=embargoed_upload.upload_id) - assert embargoed_asset_blob.blob.name == embargoed_upload.blob.name - assert embargoed_asset_blob.blob_id != other_embargoed_asset_blob.blob_id - assert EmbargoedAssetBlob.objects.count() == 2 - - # Verify that the Upload was deleted - assert not EmbargoedUpload.objects.all().exists() + assert AssetBlob.objects.all().count() == 1 diff --git a/dandiapi/conftest.py b/dandiapi/conftest.py index 300cd1cb3..50a6a2a65 100644 --- a/dandiapi/conftest.py +++ b/dandiapi/conftest.py @@ -34,7 +34,7 @@ register(PublishedAssetFactory, _name='published_asset') register(DraftAssetFactory, _name='draft_asset') register(AssetBlobFactory) -register(EmbargoedAssetBlobFactory) +register(EmbargoedAssetBlobFactory, _name='embargoed_asset_blob') register(DandisetFactory) register(EmbargoedUploadFactory) register(PublishedVersionFactory, _name='published_version') @@ -99,10 +99,6 @@ def s3_storage_factory(): return base_s3_storage_factory(settings.DANDI_DANDISETS_BUCKET_NAME) -def embargoed_s3_storage_factory(): - return base_s3_storage_factory(settings.DANDI_DANDISETS_EMBARGO_BUCKET_NAME) - - def base_minio_storage_factory(bucket_name: str) -> MinioStorage: return create_s3_storage(bucket_name) @@ -111,30 +107,16 @@ def minio_storage_factory() -> MinioStorage: return base_minio_storage_factory(settings.DANDI_DANDISETS_BUCKET_NAME) -def embargoed_minio_storage_factory() -> MinioStorage: - return base_minio_storage_factory(settings.DANDI_DANDISETS_EMBARGO_BUCKET_NAME) - - @pytest.fixture() def s3_storage() -> S3Storage: return s3_storage_factory() -@pytest.fixture() -def embargoed_s3_storage() -> S3Storage: - return s3_storage_factory() - - @pytest.fixture() def minio_storage() -> MinioStorage: return minio_storage_factory() -@pytest.fixture() -def embargoed_minio_storage() -> MinioStorage: - return minio_storage_factory() - - @pytest.fixture(params=[s3_storage_factory, minio_storage_factory], ids=['s3', 'minio']) def storage(request, settings) -> Storage: storage_factory = request.param From 8fe9b1c01d1625a16684c8fb7f47a5d1e95aa3d7 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Wed, 21 Feb 2024 18:00:00 -0500 Subject: [PATCH 03/21] Update upload process to include embargoed tags --- dandiapi/api/models/upload.py | 3 +-- dandiapi/api/storage.py | 46 +++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/dandiapi/api/models/upload.py b/dandiapi/api/models/upload.py index 14a715485..e22ddf1eb 100644 --- a/dandiapi/api/models/upload.py +++ b/dandiapi/api/models/upload.py @@ -59,8 +59,7 @@ def initialize_multipart_upload(cls, etag, size, dandiset: Dandiset): # The upload HTTP API does not pass the file name or content type, and it would be a # breaking change to start requiring this. 'application/octet-stream', - # TODO: Pass embargoed - # embargoed + tagging='embargoed=true' if embargoed else None, ) upload = cls( diff --git a/dandiapi/api/storage.py b/dandiapi/api/storage.py index 7663a5965..1223db9af 100644 --- a/dandiapi/api/storage.py +++ b/dandiapi/api/storage.py @@ -14,6 +14,7 @@ from minio import S3Error from minio_storage.policy import Policy from minio_storage.storage import MinioStorage, create_minio_client_from_settings +from s3_file_field._multipart import PresignedPartTransfer, PresignedTransfer, UploadTooLargeError from s3_file_field._multipart_minio import MinioMultipartManager from s3_file_field._multipart_s3 import S3MultipartManager from storages.backends.s3 import S3Storage @@ -47,14 +48,44 @@ def _iter_part_sizes(file_size: int) -> Iterator[tuple[int, int]]: class DandiS3MultipartManager(DandiMultipartMixin, S3MultipartManager): - """A custom multipart manager for passing ACL information.""" + """ + A custom multipart manager. - def _create_upload_id(self, object_key: str, content_type: str) -> str: + This custom multipart manager does the following: + 1. Passes ACL information to multipart upload creation + 2. Allows for passing tags to multipart uploads + """ + + def initialize_upload( + self, + object_key: str, + file_size: int, + content_type: str, + tagging: str | None = None, + ) -> PresignedTransfer: + if file_size > self.max_object_size: + raise UploadTooLargeError('File is larger than the S3 maximum object size.') + + upload_id = self._create_upload_id(object_key, content_type, tagging) + parts = [ + PresignedPartTransfer( + part_number=part_number, + size=part_size, + upload_url=self._generate_presigned_part_url( + object_key, upload_id, part_number, part_size + ), + ) + for part_number, part_size in self._iter_part_sizes(file_size) + ] + return PresignedTransfer(object_key=object_key, upload_id=upload_id, parts=parts) + + def _create_upload_id(self, object_key: str, content_type: str, tagging: str | None) -> str: resp = self._client.create_multipart_upload( Bucket=self._bucket_name, Key=object_key, ContentType=content_type, ACL='bucket-owner-full-control', + Tagging=tagging, ) return resp['UploadId'] @@ -62,6 +93,17 @@ def _create_upload_id(self, object_key: str, content_type: str) -> str: class DandiMinioMultipartManager(DandiMultipartMixin, MinioMultipartManager): """A custom multipart manager for passing ACL information.""" + # Override this method for interoperability with DandiS3MultipartManager + def initialize_upload( + self, + object_key: str, + file_size: int, + content_type: str, + *args, + **kwargs, + ) -> PresignedTransfer: + return super().initialize_upload(object_key, file_size, content_type) + def _create_upload_id(self, object_key: str, content_type: str) -> str: return self._client._create_multipart_upload( # noqa: SLF001 bucket_name=self._bucket_name, From dd18a49382dbc2e23bc11925fc7b5e4b66f8a9af Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 22 Feb 2024 17:36:58 -0500 Subject: [PATCH 04/21] Remove automatic un-embargo process --- dandiapi/api/services/embargo/__init__.py | 43 ++++----------------- dandiapi/api/services/embargo/exceptions.py | 5 --- dandiapi/api/tasks/__init__.py | 8 ---- 3 files changed, 8 insertions(+), 48 deletions(-) diff --git a/dandiapi/api/services/embargo/__init__.py b/dandiapi/api/services/embargo/__init__.py index 9905ab34c..16a5c9ddb 100644 --- a/dandiapi/api/services/embargo/__init__.py +++ b/dandiapi/api/services/embargo/__init__.py @@ -2,52 +2,25 @@ from typing import TYPE_CHECKING -from django.conf import settings +from django.db import transaction -from dandiapi.api.copy import copy_object_multipart -from dandiapi.api.models import Asset, AssetBlob, Dandiset, Upload, Version +from dandiapi.api.models import Asset, AssetBlob, Dandiset, Version from dandiapi.api.services.asset.exceptions import DandisetOwnerRequiredError -from dandiapi.api.tasks import unembargo_dandiset_task -from .exceptions import AssetNotEmbargoedError, DandisetNotEmbargoedError +from .exceptions import DandisetNotEmbargoedError if TYPE_CHECKING: from django.contrib.auth.models import User from django.db.models import QuerySet -def _unembargo_asset(asset: Asset): - """Unembargo an asset by copying its blob to the public bucket.""" - if not asset.blob.embargoed: - raise AssetNotEmbargoedError - - # TODO: Replace this with object tag removal - # Matching AssetBlob doesn't exist, copy blob to public bucket - # resp = copy_object_multipart( - # asset.embargoed_blob.blob.storage, - # source_bucket=settings.DANDI_DANDISETS_EMBARGO_BUCKET_NAME, - # source_key=asset.embargoed_blob.blob.name, - # dest_bucket=settings.DANDI_DANDISETS_BUCKET_NAME, - # dest_key=Upload.object_key( - # asset.embargoed_blob.blob_id, dandiset=asset.embargoed_blob.dandiset - # ), - # ) - - # if resp.etag != asset.embargoed_blob.etag: - # raise RuntimeError('ETag mismatch between copied object and original embargoed object') - - # Update blob embargoed flag - asset.blob.embargoed = False - asset.blob.save() - - +@transaction.atomic() def _unembargo_dandiset(dandiset: Dandiset): + # TODO: Remove embargoed tags from objects in s3 + draft_version: Version = dandiset.draft_version embargoed_assets: QuerySet[Asset] = draft_version.assets.filter(blob__embargoed=True) - - # Unembargo all assets - for asset in embargoed_assets.iterator(): - _unembargo_asset(asset) + AssetBlob.objects.filter(assets__in=embargoed_assets).update(embargoed=False) # Update draft version metadata draft_version.metadata['access'] = [ @@ -68,4 +41,4 @@ def unembargo_dandiset(*, user: User, dandiset: Dandiset): if not user.has_perm('owner', dandiset): raise DandisetOwnerRequiredError - unembargo_dandiset_task.delay(dandiset.id) + # TODO: Send email to admins? diff --git a/dandiapi/api/services/embargo/exceptions.py b/dandiapi/api/services/embargo/exceptions.py index 736dedfb7..671603ae6 100644 --- a/dandiapi/api/services/embargo/exceptions.py +++ b/dandiapi/api/services/embargo/exceptions.py @@ -5,11 +5,6 @@ from dandiapi.api.services.exceptions import DandiError -class AssetNotEmbargoedError(DandiError): - http_status_code = status.HTTP_400_BAD_REQUEST - message = 'Only embargoed assets can be unembargoed.' - - class DandisetNotEmbargoedError(DandiError): http_status_code = status.HTTP_400_BAD_REQUEST message = 'Dandiset not embargoed' diff --git a/dandiapi/api/tasks/__init__.py b/dandiapi/api/tasks/__init__.py index b5b9a02b1..2aa413b9e 100644 --- a/dandiapi/api/tasks/__init__.py +++ b/dandiapi/api/tasks/__init__.py @@ -62,14 +62,6 @@ def delete_doi_task(doi: str) -> None: delete_doi(doi) -@shared_task -def unembargo_dandiset_task(dandiset_id: int): - from dandiapi.api.services.embargo import _unembargo_dandiset - - dandiset = Dandiset.objects.get(id=dandiset_id) - _unembargo_dandiset(dandiset) - - @shared_task def publish_dandiset_task(dandiset_id: int): from dandiapi.api.services.publish import _publish_dandiset From 7bc4c1378e312610536b57ca52b19b6e6412254d Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 22 Feb 2024 19:16:21 -0500 Subject: [PATCH 05/21] Remove all use of embargoed log bucket This excludes the DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME setting, as that is used for historical embargoed objects. --- .github/workflows/frontend-ci.yml | 2 - ...seds3log_unique_name_embargoed_and_more.py | 34 ++++++++++++++ dandiapi/analytics/models.py | 9 ++-- dandiapi/analytics/tasks/__init__.py | 47 +++++-------------- .../analytics/tests/test_download_counts.py | 18 ++++--- dandiapi/api/tasks/scheduled.py | 9 +--- 6 files changed, 62 insertions(+), 57 deletions(-) create mode 100644 dandiapi/analytics/migrations/0002_remove_processeds3log_analytics_processeds3log_unique_name_embargoed_and_more.py diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index e7e132baf..ac3779841 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -62,7 +62,6 @@ jobs: DJANGO_STORAGE_BUCKET_NAME: dandi-bucket DJANGO_DANDI_DANDISETS_BUCKET_NAME: dandi-bucket DJANGO_DANDI_DANDISETS_LOG_BUCKET_NAME: dandiapi-dandisets-logs - DJANGO_DANDI_DANDISETS_EMBARGO_BUCKET_NAME: dandi-embargo-dandisets DJANGO_DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME: dandiapi-embargo-dandisets-logs DJANGO_DANDI_WEB_APP_URL: http://localhost:8085 DJANGO_DANDI_API_URL: http://localhost:8000 @@ -174,7 +173,6 @@ jobs: DJANGO_STORAGE_BUCKET_NAME: dandi-bucket DJANGO_DANDI_DANDISETS_BUCKET_NAME: dandi-bucket DJANGO_DANDI_DANDISETS_LOG_BUCKET_NAME: dandiapi-dandisets-logs - DJANGO_DANDI_DANDISETS_EMBARGO_BUCKET_NAME: dandi-embargo-dandisets DJANGO_DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME: dandiapi-embargo-dandisets-logs DJANGO_DANDI_WEB_APP_URL: http://localhost:8085 DJANGO_DANDI_API_URL: http://localhost:8000 diff --git a/dandiapi/analytics/migrations/0002_remove_processeds3log_analytics_processeds3log_unique_name_embargoed_and_more.py b/dandiapi/analytics/migrations/0002_remove_processeds3log_analytics_processeds3log_unique_name_embargoed_and_more.py new file mode 100644 index 000000000..e8dfb37c1 --- /dev/null +++ b/dandiapi/analytics/migrations/0002_remove_processeds3log_analytics_processeds3log_unique_name_embargoed_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 4.1.13 on 2024-03-20 15:59 +from __future__ import annotations + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('analytics', '0001_initial_v2'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='processeds3log', + name='analytics_processeds3log_unique_name_embargoed', + ), + migrations.RenameField( + model_name='processeds3log', + old_name='embargoed', + new_name='historically_embargoed', + ), + migrations.AlterField( + model_name='processeds3log', + name='historically_embargoed', + field=models.BooleanField(default=False), + ), + migrations.AddConstraint( + model_name='processeds3log', + constraint=models.UniqueConstraint( + fields=('name', 'historically_embargoed'), + name='analytics_processeds3log_unique_name_embargoed', + ), + ), + ] diff --git a/dandiapi/analytics/models.py b/dandiapi/analytics/models.py index 40b2996d2..ee2fa5586 100644 --- a/dandiapi/analytics/models.py +++ b/dandiapi/analytics/models.py @@ -12,13 +12,16 @@ class ProcessedS3Log(models.Model): RegexValidator(r'^\d{4}-(\d{2}-){5}[A-F0-9]{16}$') ], ) - # This is necessary to determine which bucket the logfile corresponds to - embargoed = models.BooleanField() + + # Represents if this s3 log file was embargoed prior to the embargo re-design. + # If this field is True, the log file lives in the S3 bucket pointed to by the + # DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME setting. + historically_embargoed = models.BooleanField(default=False) class Meta: constraints = [ models.UniqueConstraint( - fields=['name', 'embargoed'], + fields=['name', 'historically_embargoed'], name='%(app_label)s_%(class)s_unique_name_embargoed', ) ] diff --git a/dandiapi/analytics/tasks/__init__.py b/dandiapi/analytics/tasks/__init__.py index d43e89d90..1e1ba3d16 100644 --- a/dandiapi/analytics/tasks/__init__.py +++ b/dandiapi/analytics/tasks/__init__.py @@ -25,62 +25,41 @@ LogBucket = str -def _bucket_objects_after(bucket: str, after: str | None) -> Generator[dict, None, None]: - if bucket not in { - settings.DANDI_DANDISETS_LOG_BUCKET_NAME, - settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME, - }: - raise ValueError(f'Non-log bucket: {bucket}') +def _bucket_objects_after(after: str | None) -> Generator[dict, None, None]: s3 = get_boto_client(get_storage()) kwargs = {} if after: kwargs['StartAfter'] = after paginator = s3.get_paginator('list_objects_v2') - for page in paginator.paginate(Bucket=bucket, **kwargs): + for page in paginator.paginate(Bucket=settings.DANDI_DANDISETS_LOG_BUCKET_NAME, **kwargs): yield from page.get('Contents', []) @shared_task(queue='s3-log-processing', soft_time_limit=60, time_limit=80) -def collect_s3_log_records_task(bucket: LogBucket) -> None: +def collect_s3_log_records_task() -> None: """Dispatch a task per S3 log file to process for download counts.""" - if bucket not in { - settings.DANDI_DANDISETS_LOG_BUCKET_NAME, - settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME, - }: - raise RuntimeError - embargoed = bucket == settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME - after = ProcessedS3Log.objects.filter(embargoed=embargoed).aggregate(last_log=Max('name'))[ - 'last_log' - ] + after = ProcessedS3Log.objects.aggregate(last_log=Max('name'))['last_log'] - for s3_log_object in _bucket_objects_after(bucket, after): - process_s3_log_file_task.delay(bucket, s3_log_object['Key']) + for s3_log_object in _bucket_objects_after(after): + process_s3_log_file_task.delay(s3_log_object['Key']) @shared_task(queue='s3-log-processing', soft_time_limit=120, time_limit=140) -def process_s3_log_file_task(bucket: LogBucket, s3_log_key: str) -> None: +def process_s3_log_file_task(s3_log_key: str) -> None: """ Process a single S3 log file for download counts. Creates a ProcessedS3Log entry and updates the download counts for the relevant - asset blobs. Prevents duplicate processing with a unique constraint on the ProcessedS3Log name - and embargoed fields. + asset blobs. Prevents duplicate processing with a unique constraint on the ProcessedS3Log name. """ - if bucket not in { - settings.DANDI_DANDISETS_LOG_BUCKET_NAME, - settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME, - }: - raise RuntimeError - embargoed = bucket == settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME - # short circuit if the log file has already been processed. note that this doesn't guarantee # exactly once processing, that's what the unique constraint on ProcessedS3Log is for. - if ProcessedS3Log.objects.filter(name=s3_log_key.split('/')[-1], embargoed=embargoed).exists(): + if ProcessedS3Log.objects.filter(name=s3_log_key.split('/')[-1]).exists(): return s3 = get_boto_client(get_storage()) - data = s3.get_object(Bucket=bucket, Key=s3_log_key) + data = s3.get_object(Bucket=settings.DANDI_DANDISETS_LOG_BUCKET_NAME, Key=s3_log_key) download_counts = Counter() for log_entry in s3logparse.parse_log_lines( @@ -91,14 +70,14 @@ def process_s3_log_file_task(bucket: LogBucket, s3_log_key: str) -> None: with transaction.atomic(): try: - log = ProcessedS3Log(name=s3_log_key.split('/')[-1], embargoed=embargoed) + log = ProcessedS3Log(name=s3_log_key.split('/')[-1]) # disable constraint validation checking so duplicate errors can be detected and # ignored. the rest of the full_clean errors should still be raised. log.full_clean(validate_constraints=False) log.save() except IntegrityError as e: - if 'unique_name_embargoed' in str(e): - logger.info('Already processed log file %s, embargo: %s', s3_log_key, embargoed) + if '_unique_name' in str(e): + logger.info('Already processed log file %s', s3_log_key) return # note this task is run serially per log file. this is to avoid the contention between diff --git a/dandiapi/analytics/tests/test_download_counts.py b/dandiapi/analytics/tests/test_download_counts.py index ba7c0ec07..efde429c7 100644 --- a/dandiapi/analytics/tests/test_download_counts.py +++ b/dandiapi/analytics/tests/test_download_counts.py @@ -15,8 +15,6 @@ def s3_log_bucket(): @pytest.fixture() def s3_log_file(s3_log_bucket, asset_blob): - # TODO: What to do when embargoed - # embargoed = s3_log_bucket == settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME s3 = get_boto_client() log_file_name = '2019-02-06-00-00-38-5C5B0E0CA8F2B1B5' @@ -47,8 +45,8 @@ def s3_log_file(s3_log_bucket, asset_blob): @pytest.mark.django_db() -def test_processing_s3_log_files(s3_log_bucket, s3_log_file, asset_blob): - collect_s3_log_records_task(s3_log_bucket) +def test_processing_s3_log_files(s3_log_file, asset_blob): + collect_s3_log_records_task() asset_blob.refresh_from_db() assert ProcessedS3Log.objects.count() == 1 @@ -56,12 +54,12 @@ def test_processing_s3_log_files(s3_log_bucket, s3_log_file, asset_blob): @pytest.mark.django_db() -def test_processing_s3_log_files_idempotent(s3_log_bucket, s3_log_file, asset_blob): +def test_processing_s3_log_files_idempotent(s3_log_file, asset_blob): # this tests that the outer task which collects the log files to process is # idempotent, in other words, it uses StartAfter correctly. - collect_s3_log_records_task(s3_log_bucket) + collect_s3_log_records_task() # run the task again, it should skip the existing log record - collect_s3_log_records_task(s3_log_bucket) + collect_s3_log_records_task() asset_blob.refresh_from_db() assert ProcessedS3Log.objects.count() == 1 @@ -69,12 +67,12 @@ def test_processing_s3_log_files_idempotent(s3_log_bucket, s3_log_file, asset_bl @pytest.mark.django_db() -def test_processing_s3_log_file_task_idempotent(s3_log_bucket, s3_log_file, asset_blob): +def test_processing_s3_log_file_task_idempotent(s3_log_file, asset_blob): # this tests that the inner task which processes a single log file is # idempotent, utilizing the unique constraint on ProcessedS3Log correctly. - process_s3_log_file_task(s3_log_bucket, s3_log_file) + process_s3_log_file_task(s3_log_file) # run the task again, it should ignore the new log - process_s3_log_file_task(s3_log_bucket, s3_log_file) + process_s3_log_file_task(s3_log_file) asset_blob.refresh_from_db() assert ProcessedS3Log.objects.count() == 1 diff --git a/dandiapi/api/tasks/scheduled.py b/dandiapi/api/tasks/scheduled.py index 20af07efa..2b4a100e9 100644 --- a/dandiapi/api/tasks/scheduled.py +++ b/dandiapi/api/tasks/scheduled.py @@ -143,11 +143,4 @@ def register_scheduled_tasks(sender: Celery, **kwargs): sender.add_periodic_task(timedelta(minutes=10), refresh_materialized_view_search.s()) # Process new S3 logs every hour - for log_bucket in [ - settings.DANDI_DANDISETS_LOG_BUCKET_NAME, - settings.DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME, - ]: - sender.add_periodic_task( - timedelta(hours=1), - collect_s3_log_records_task.s(log_bucket), - ) + sender.add_periodic_task(timedelta(hours=1), collect_s3_log_records_task.s()) From 8d3765e8f94496caa858a19221b39c51b998df3f Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 22 Feb 2024 20:28:01 -0500 Subject: [PATCH 06/21] Fix migrations to account for duplicate assetblobs --- ...more.py => 0008_migrate_embargoed_data.py} | 70 ++++++++----------- ...ve_embargoedassetblob_dandiset_and_more.py | 46 ++++++++++++ 2 files changed, 74 insertions(+), 42 deletions(-) rename dandiapi/api/migrations/{0008_remove_embargoedassetblob_dandiset_and_more.py => 0008_migrate_embargoed_data.py} (50%) create mode 100644 dandiapi/api/migrations/0009_remove_embargoedassetblob_dandiset_and_more.py diff --git a/dandiapi/api/migrations/0008_remove_embargoedassetblob_dandiset_and_more.py b/dandiapi/api/migrations/0008_migrate_embargoed_data.py similarity index 50% rename from dandiapi/api/migrations/0008_remove_embargoedassetblob_dandiset_and_more.py rename to dandiapi/api/migrations/0008_migrate_embargoed_data.py index f8adc037b..d0ad94b2d 100644 --- a/dandiapi/api/migrations/0008_remove_embargoedassetblob_dandiset_and_more.py +++ b/dandiapi/api/migrations/0008_migrate_embargoed_data.py @@ -7,18 +7,35 @@ def migrate_embargoed_asset_blobs(apps, _): Asset = apps.get_model('api.Asset') AssetBlob = apps.get_model('api.AssetBlob') - - # We must include a filter to only include assets which are part of a version, as otherwise - # we may include an embargoed asset which has been previously updated. Since updating an asset - # results in a new asset which points to the same blob as the original asset, this would copy - # the same blob twice, resulting in an integrity error (same blob_id). - embargoed_assets = Asset.objects.filter( - embargoed_blob__isnull=False, versions__isnull=False - ).select_related('embargoed_blob') + embargoed_assets = Asset.objects.filter(embargoed_blob__isnull=False).select_related( + 'embargoed_blob' + ) # For each relevant asset, create a new asset blob with embargoed=True, # and point the asset to that for asset in embargoed_assets.iterator(): + # Check if the blob we care about already exists (possibly under a different blob_id due to + # de-duplication). + # This will handle the following cases: + # 1. This asset is part of an "asset chain", where multiple assets point to the same blob + # 2. This blob this asset points to exists across multiple embargoed dandisets under + # different blob_ids, due to the lack of cross-dandiset embargo de-duplication. + # 3. This blob this asset points to already exists as a normal AssetBlob, due to the lack + # of de-duplication between open and embargoed dandisets. This is essentially the same + # as the above case, but between an embargoed and open dandiset, instead of two + # embargoed dandisets. + # + # In case #3, the asset will effectively be un-embargoed. + existing_blob = AssetBlob.objects.filter( + etag=asset.embargoed_blob.etag, size=asset.embargoed_blob.size + ).first() + if existing_blob: + asset.blob = existing_blob + asset.embargoed_blob = None + asset.save() + continue + + # This asset's blob hasn't been transitioned yet blob_id = str(asset.embargoed_blob.blob_id) new_blob_location = f'blobs/{blob_id[0:3]}/{blob_id[3:6]}/{blob_id}' new_asset_blob = AssetBlob.objects.create( @@ -36,6 +53,8 @@ def migrate_embargoed_asset_blobs(apps, _): asset.embargoed_blob = None asset.save() + assert not Asset.objects.filter(embargoed_blob__isnull=False).exists() # noqa: S101 + class Migration(migrations.Migration): dependencies = [ @@ -53,39 +72,6 @@ class Migration(migrations.Migration): name='embargoed', field=models.BooleanField(default=False), ), - # Migrate all embargoedassetblobs and embargoeduploads to other models with embargoed=True + # Migrate all embargoedassetblobs to assetblobs with embargoed=True migrations.RunPython(migrate_embargoed_asset_blobs), - migrations.RemoveField( - model_name='embargoedassetblob', - name='dandiset', - ), - migrations.RemoveField( - model_name='embargoedupload', - name='dandiset', - ), - migrations.RemoveConstraint( - model_name='asset', - name='exactly-one-blob', - ), - migrations.RemoveField( - model_name='asset', - name='embargoed_blob', - ), - migrations.AddConstraint( - model_name='asset', - constraint=models.CheckConstraint( - check=models.Q( - models.Q(('blob__isnull', True), ('zarr__isnull', False)), - models.Q(('blob__isnull', False), ('zarr__isnull', True)), - _connector='OR', - ), - name='blob-xor-zarr', - ), - ), - migrations.DeleteModel( - name='EmbargoedAssetBlob', - ), - migrations.DeleteModel( - name='EmbargoedUpload', - ), ] diff --git a/dandiapi/api/migrations/0009_remove_embargoedassetblob_dandiset_and_more.py b/dandiapi/api/migrations/0009_remove_embargoedassetblob_dandiset_and_more.py new file mode 100644 index 000000000..70f07d682 --- /dev/null +++ b/dandiapi/api/migrations/0009_remove_embargoedassetblob_dandiset_and_more.py @@ -0,0 +1,46 @@ +# Generated by Django 4.1.13 on 2024-01-16 18:31 +from __future__ import annotations + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0008_migrate_embargoed_data'), + ] + + operations = [ + migrations.RemoveField( + model_name='embargoedassetblob', + name='dandiset', + ), + migrations.RemoveField( + model_name='embargoedupload', + name='dandiset', + ), + migrations.RemoveConstraint( + model_name='asset', + name='exactly-one-blob', + ), + migrations.RemoveField( + model_name='asset', + name='embargoed_blob', + ), + migrations.AddConstraint( + model_name='asset', + constraint=models.CheckConstraint( + check=models.Q( + models.Q(('blob__isnull', True), ('zarr__isnull', False)), + models.Q(('blob__isnull', False), ('zarr__isnull', True)), + _connector='OR', + ), + name='blob-xor-zarr', + ), + ), + migrations.DeleteModel( + name='EmbargoedAssetBlob', + ), + migrations.DeleteModel( + name='EmbargoedUpload', + ), + ] From 8aa46d15ebe8951081b11b5cd87afca1fedbe3d0 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Mon, 26 Feb 2024 11:17:02 -0500 Subject: [PATCH 07/21] Un-embargo asset blob when added to open asset --- dandiapi/api/services/asset/__init__.py | 18 ++++++ dandiapi/api/services/embargo/__init__.py | 15 ++++- dandiapi/api/services/embargo/exceptions.py | 5 ++ dandiapi/api/tasks/__init__.py | 10 ++- dandiapi/api/tests/test_asset.py | 70 +++++++++++++++------ dandiapi/api/tests/test_embargo.py | 10 +++ 6 files changed, 108 insertions(+), 20 deletions(-) diff --git a/dandiapi/api/services/asset/__init__.py b/dandiapi/api/services/asset/__init__.py index fcb88b270..69a633af4 100644 --- a/dandiapi/api/services/asset/__init__.py +++ b/dandiapi/api/services/asset/__init__.py @@ -6,6 +6,7 @@ from dandiapi.api.asset_paths import add_asset_paths, delete_asset_paths, get_conflicting_paths from dandiapi.api.models.asset import Asset, AssetBlob +from dandiapi.api.models.dandiset import Dandiset from dandiapi.api.models.version import Version from dandiapi.api.services.asset.exceptions import ( AssetAlreadyExistsError, @@ -14,6 +15,7 @@ DraftDandisetNotModifiableError, ZarrArchiveBelongsToDifferentDandisetError, ) +from dandiapi.api.tasks import remove_asset_blob_embargoed_tag_task if TYPE_CHECKING: from dandiapi.zarr.models import ZarrArchive @@ -133,7 +135,18 @@ def add_asset_to_version( if zarr_archive and zarr_archive.dandiset != version.dandiset: raise ZarrArchiveBelongsToDifferentDandisetError + # Creating an asset in an OPEN dandiset that points to an embargoed blob results in that + # blob being un-embargoed + unembargo_asset_blob = ( + asset_blob is not None + and asset_blob.embargoed + and version.dandiset.embargo_status == Dandiset.EmbargoStatus.OPEN + ) with transaction.atomic(): + if unembargo_asset_blob: + asset_blob.embargoed = False + asset_blob.save() + asset = _create_asset( path=path, asset_blob=asset_blob, zarr_archive=zarr_archive, metadata=metadata ) @@ -145,6 +158,11 @@ def add_asset_to_version( # Save the version so that the modified field is updated version.save() + # Perform this after the above transaction has finished, to ensure we only operate on + # un-embargoed asset blobs + if unembargo_asset_blob: + remove_asset_blob_embargoed_tag_task.delay(blob_id=asset_blob.blob_id) + return asset diff --git a/dandiapi/api/services/embargo/__init__.py b/dandiapi/api/services/embargo/__init__.py index 16a5c9ddb..20a13b73d 100644 --- a/dandiapi/api/services/embargo/__init__.py +++ b/dandiapi/api/services/embargo/__init__.py @@ -2,18 +2,31 @@ from typing import TYPE_CHECKING +from django.conf import settings from django.db import transaction from dandiapi.api.models import Asset, AssetBlob, Dandiset, Version from dandiapi.api.services.asset.exceptions import DandisetOwnerRequiredError +from dandiapi.api.storage import get_boto_client -from .exceptions import DandisetNotEmbargoedError +from .exceptions import AssetBlobEmbargoedError, DandisetNotEmbargoedError if TYPE_CHECKING: from django.contrib.auth.models import User from django.db.models import QuerySet +def remove_asset_blob_embargoed_tag(asset_blob: AssetBlob) -> None: + if asset_blob.embargoed: + raise AssetBlobEmbargoedError + + client = get_boto_client() + client.delete_object_tagging( + Bucket=settings.DANDI_DANDISETS_BUCKET_NAME, + Key=asset_blob.blob.name, + ) + + @transaction.atomic() def _unembargo_dandiset(dandiset: Dandiset): # TODO: Remove embargoed tags from objects in s3 diff --git a/dandiapi/api/services/embargo/exceptions.py b/dandiapi/api/services/embargo/exceptions.py index 671603ae6..b9d58aa2f 100644 --- a/dandiapi/api/services/embargo/exceptions.py +++ b/dandiapi/api/services/embargo/exceptions.py @@ -5,6 +5,11 @@ from dandiapi.api.services.exceptions import DandiError +class AssetBlobEmbargoedError(DandiError): + http_status_code = status.HTTP_400_BAD_REQUEST + message = 'This operation cannot be performed on embargoed assets blobs.' + + class DandisetNotEmbargoedError(DandiError): http_status_code = status.HTTP_400_BAD_REQUEST message = 'Dandiset not embargoed' diff --git a/dandiapi/api/tasks/__init__.py b/dandiapi/api/tasks/__init__.py index 2aa413b9e..eb3a6ef99 100644 --- a/dandiapi/api/tasks/__init__.py +++ b/dandiapi/api/tasks/__init__.py @@ -11,11 +11,19 @@ write_dandiset_jsonld, write_dandiset_yaml, ) -from dandiapi.api.models import Asset, AssetBlob, Dandiset, Version +from dandiapi.api.models import Asset, AssetBlob, Version logger = get_task_logger(__name__) +@shared_task(soft_time_limit=60) +def remove_asset_blob_embargoed_tag_task(blob_id: str) -> None: + from dandiapi.api.services.embargo import remove_asset_blob_embargoed_tag + + asset_blob = AssetBlob.objects.get(blob_id=blob_id) + remove_asset_blob_embargoed_tag(asset_blob) + + @shared_task(queue='calculate_sha256', soft_time_limit=86_400) def calculate_sha256(blob_id: str) -> None: asset_blob = AssetBlob.objects.get(blob_id=blob_id) diff --git a/dandiapi/api/tests/test_asset.py b/dandiapi/api/tests/test_asset.py index 949948519..ea70ad969 100644 --- a/dandiapi/api/tests/test_asset.py +++ b/dandiapi/api/tests/test_asset.py @@ -3,12 +3,12 @@ import json from uuid import uuid4 -import pytest -import requests from django.conf import settings from django.db.utils import IntegrityError from django.urls import reverse from guardian.shortcuts import assign_perm +import pytest +import requests from dandiapi.api.asset_paths import add_asset_paths, extract_paths from dandiapi.api.models import Asset, AssetBlob, Version @@ -602,9 +602,15 @@ def test_asset_create_conflicting_path(api_client, user, draft_version, asset_bl @pytest.mark.django_db() -def test_asset_create_embargo(api_client, user, draft_version, embargoed_asset_blob): +def test_asset_create_embargo( + api_client, user, draft_version_factory, dandiset_factory, embargoed_asset_blob +): + dandiset = dandiset_factory(embargo_status=Dandiset.EmbargoStatus.EMBARGOED) + draft_version = draft_version_factory(dandiset=dandiset) + assign_perm('owner', user, draft_version.dandiset) api_client.force_authenticate(user=user) + assert draft_version.dandiset.embargo_status == Dandiset.EmbargoStatus.EMBARGOED path = 'test/create/asset.txt' metadata = { @@ -622,24 +628,52 @@ def test_asset_create_embargo(api_client, user, draft_version, embargoed_asset_b format='json', ).json() new_asset = Asset.objects.get(asset_id=resp['asset_id']) - assert resp == { - 'asset_id': UUID_RE, + + assert new_asset.blob.embargoed + assert new_asset.zarr is None + + # Adding an Asset should trigger a revalidation + assert draft_version.status == Version.Status.PENDING + + +@pytest.mark.django_db() +def test_asset_create_embargoed_asset_blob_open_dandiset( + api_client, user, draft_version, embargoed_asset_blob, mocker +): + # Ensure that creating an asset in an open dandiset that points to an embargoed asset blob + # results in that asset blob being un-embargoed + assert draft_version.dandiset.embargo_status == Dandiset.EmbargoStatus.OPEN + assert embargoed_asset_blob.embargoed + + assign_perm('owner', user, draft_version.dandiset) + api_client.force_authenticate(user=user) + + path = 'test/create/asset.txt' + metadata = { + 'encodingFormat': 'application/x-nwb', 'path': path, - 'size': embargoed_asset_blob.size, - 'blob': embargoed_asset_blob.blob_id, - 'zarr': None, - 'created': TIMESTAMP_RE, - 'modified': TIMESTAMP_RE, - 'metadata': new_asset.full_metadata, + 'meta': 'data', + 'foo': ['bar', 'baz'], + '1': 2, } - for key in metadata: - assert resp['metadata'][key] == metadata[key] - # The version modified date should be updated - start_time = draft_version.modified - draft_version.refresh_from_db() - end_time = draft_version.modified - assert start_time < end_time + # Mock this so we can check that it's been called later + mocked_func = mocker.patch('dandiapi.api.services.embargo.remove_asset_blob_embargoed_tag') + + resp = api_client.post( + f'/api/dandisets/{draft_version.dandiset.identifier}' + f'/versions/{draft_version.version}/assets/', + {'metadata': metadata, 'blob_id': embargoed_asset_blob.blob_id}, + format='json', + ).json() + new_asset = Asset.objects.get(asset_id=resp['asset_id']) + + assert new_asset.blob == embargoed_asset_blob + assert not new_asset.blob.embargoed + + # We can't test that the tags were correctly removed in a testing env, but we can test that the + # function which removes the tags was correctly invoked + mocked_func.assert_called_once() # Adding an Asset should trigger a revalidation assert draft_version.status == Version.Status.PENDING diff --git a/dandiapi/api/tests/test_embargo.py b/dandiapi/api/tests/test_embargo.py index 7e8c6c4b7..ff249c30e 100644 --- a/dandiapi/api/tests/test_embargo.py +++ b/dandiapi/api/tests/test_embargo.py @@ -3,6 +3,7 @@ import pytest from dandiapi.api.models import Dandiset +from dandiapi.api.services.embargo import AssetBlobEmbargoedError, remove_asset_blob_embargoed_tag @pytest.fixture( @@ -86,3 +87,12 @@ def test_embargo_visibility( response = getattr(api_client, method)(url) # The client is now authenticated but not an owner, so all response codes should be 403 assert response.status_code == 403 + + +@pytest.mark.django_db() +def test_remove_asset_blob_embargoed_tag_fails_on_embargod(embargoed_asset_blob, asset_blob): + with pytest.raises(AssetBlobEmbargoedError): + remove_asset_blob_embargoed_tag(embargoed_asset_blob) + + # Test that error not raised on non-embargoed asset blob + remove_asset_blob_embargoed_tag(asset_blob) From 46336323527d20c6257dd8c90a5b65dc654b01f0 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Mon, 26 Feb 2024 12:44:35 -0500 Subject: [PATCH 08/21] Send email to admins when un-embargo requested --- dandiapi/api/mail.py | 27 ++++++++++++ dandiapi/api/services/embargo/__init__.py | 5 ++- dandiapi/api/tasks/scheduled.py | 14 ++++++- .../api/mail/dandisets_to_unembargo.txt | 10 +++++ dandiapi/api/tests/test_unembargo.py | 41 +++++++++++++++++++ 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 dandiapi/api/templates/api/mail/dandisets_to_unembargo.txt create mode 100644 dandiapi/api/tests/test_unembargo.py diff --git a/dandiapi/api/mail.py b/dandiapi/api/mail.py index d786b3201..46904bc06 100644 --- a/dandiapi/api/mail.py +++ b/dandiapi/api/mail.py @@ -13,6 +13,8 @@ from allauth.socialaccount.models import SocialAccount from django.contrib.auth.models import User + from dandiapi.api.models.dandiset import Dandiset + logger = logging.getLogger(__name__) BASE_RENDER_CONTEXT = { @@ -177,3 +179,28 @@ def send_pending_users_message(users: Iterable[User]): messages = [build_pending_users_message(users)] with mail.get_connection() as connection: connection.send_messages(messages) + + +def build_dandisets_to_unembargo_message(dandisets: Iterable[Dandiset]): + dandiset_context = [ + { + 'identifier': ds.identifier, + 'owners': [user.username for user in ds.owners], + 'asset_count': ds.draft_version.asset_count, + 'size': ds.draft_version.size, + } + for ds in dandisets + ] + render_context = {**BASE_RENDER_CONTEXT, 'dandisets': dandiset_context} + return build_message( + subject='DANDI: new dandisets to un-embargo', + message=render_to_string('api/mail/dandisets_to_unembargo.txt', render_context), + to=[ADMIN_EMAIL], + ) + + +def send_dandisets_to_unembargo_message(dandisets: Iterable[Dandiset]): + logger.info('Sending dandisets to un-embargo message to admins at %s', ADMIN_EMAIL) + messages = [build_dandisets_to_unembargo_message(dandisets)] + with mail.get_connection() as connection: + connection.send_messages(messages) diff --git a/dandiapi/api/services/embargo/__init__.py b/dandiapi/api/services/embargo/__init__.py index 20a13b73d..f01885c6f 100644 --- a/dandiapi/api/services/embargo/__init__.py +++ b/dandiapi/api/services/embargo/__init__.py @@ -54,4 +54,7 @@ def unembargo_dandiset(*, user: User, dandiset: Dandiset): if not user.has_perm('owner', dandiset): raise DandisetOwnerRequiredError - # TODO: Send email to admins? + # A scheduled task will pick up any new dandisets with this status and email the admins to + # initiate the un-embargo process + dandiset.embargo_status = Dandiset.EmbargoStatus.UNEMBARGOING + dandiset.save() diff --git a/dandiapi/api/tasks/scheduled.py b/dandiapi/api/tasks/scheduled.py index 2b4a100e9..bba10340d 100644 --- a/dandiapi/api/tasks/scheduled.py +++ b/dandiapi/api/tasks/scheduled.py @@ -20,9 +20,10 @@ from django.db.models.query_utils import Q from dandiapi.analytics.tasks import collect_s3_log_records_task -from dandiapi.api.mail import send_pending_users_message +from dandiapi.api.mail import send_dandisets_to_unembargo_message, send_pending_users_message from dandiapi.api.models import UserMetadata, Version from dandiapi.api.models.asset import Asset +from dandiapi.api.models.dandiset import Dandiset from dandiapi.api.services.metadata import version_aggregate_assets_summary from dandiapi.api.services.metadata.exceptions import VersionMetadataConcurrentlyModifiedError from dandiapi.api.tasks import ( @@ -111,6 +112,14 @@ def send_pending_users_email() -> None: send_pending_users_message(pending_users) +@shared_task(soft_time_limit=20) +def send_dandisets_to_unembargo_email() -> None: + """Send an email to admins listing dandisets that have requested un-embargo.""" + dandisets = Dandiset.objects.filter(embargo_status=Dandiset.EmbargoStatus.UNEMBARGOING) + if dandisets.exists(): + send_dandisets_to_unembargo_message(dandisets) + + @shared_task(soft_time_limit=60) def refresh_materialized_view_search() -> None: """ @@ -139,6 +148,9 @@ def register_scheduled_tasks(sender: Celery, **kwargs): # Send daily email to admins containing a list of users awaiting approval sender.add_periodic_task(crontab(hour=0, minute=0), send_pending_users_email.s()) + # Send daily email to admins containing a list of dandisets to un-embargo + sender.add_periodic_task(crontab(hour=0, minute=0), send_dandisets_to_unembargo_email.s()) + # Refresh the materialized view used by asset search every 10 mins. sender.add_periodic_task(timedelta(minutes=10), refresh_materialized_view_search.s()) diff --git a/dandiapi/api/templates/api/mail/dandisets_to_unembargo.txt b/dandiapi/api/templates/api/mail/dandisets_to_unembargo.txt new file mode 100644 index 000000000..3676f7721 --- /dev/null +++ b/dandiapi/api/templates/api/mail/dandisets_to_unembargo.txt @@ -0,0 +1,10 @@ +{% autoescape off %} +The following new dandisets are awaiting un-embargo: + +{% for ds in dandisets %} +Dandiset ID: {{ ds.identifier }} +Owners: {{ ds.owners }} +Number of Assets: {{ ds.asset_count }} +Total data size: {{ ds.size }} +{% endfor %} +{% endautoescape %} diff --git a/dandiapi/api/tests/test_unembargo.py b/dandiapi/api/tests/test_unembargo.py new file mode 100644 index 000000000..ac41a6c41 --- /dev/null +++ b/dandiapi/api/tests/test_unembargo.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from guardian.shortcuts import assign_perm +import pytest + +from dandiapi.api.models.dandiset import Dandiset +from dandiapi.api.services.embargo import AssetBlobEmbargoedError, remove_asset_blob_embargoed_tag +from dandiapi.api.tasks.scheduled import send_dandisets_to_unembargo_email + + +@pytest.mark.django_db() +def test_remove_asset_blob_embargoed_tag_fails_on_embargod(embargoed_asset_blob, asset_blob): + with pytest.raises(AssetBlobEmbargoedError): + remove_asset_blob_embargoed_tag(embargoed_asset_blob) + + # Test that error not raised on non-embargoed asset blob + remove_asset_blob_embargoed_tag(asset_blob) + + +@pytest.mark.django_db() +def test_unembargo_dandiset_sends_emails( + api_client, user, dandiset, draft_version_factory, mailoutbox +): + draft_version_factory(dandiset=dandiset) + + assign_perm('owner', user, dandiset) + api_client.force_authenticate(user=user) + + dandiset.embargo_status = Dandiset.EmbargoStatus.EMBARGOED + dandiset.save() + + resp = api_client.post(f'/api/dandisets/{dandiset.identifier}/unembargo/') + assert resp.status_code == 200 + + # Simulate the scheduled task calling this function + send_dandisets_to_unembargo_email() + + assert mailoutbox + assert 'un-embargo' in mailoutbox[0].subject + assert dandiset.identifier in mailoutbox[0].message().get_payload() + assert user.username in mailoutbox[0].message().get_payload() From ee993ac212e948610c6526b4a5b81a8f4b7248a6 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Wed, 6 Mar 2024 14:15:27 -0500 Subject: [PATCH 09/21] Update client un-embargo message --- web/src/views/DandisetLandingView/DandisetUnembargo.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/views/DandisetLandingView/DandisetUnembargo.vue b/web/src/views/DandisetLandingView/DandisetUnembargo.vue index 940416375..516f3cf77 100644 --- a/web/src/views/DandisetLandingView/DandisetUnembargo.vue +++ b/web/src/views/DandisetLandingView/DandisetUnembargo.vue @@ -17,14 +17,14 @@ - This action will unembargo this dandiset. Note that this is a + This action will un-embargo this dandiset. Note that this is a permanent - action and is not undoable. Once a dandiset has been unembargoed, + action and cannot be undone. Once a dandiset has been un-embargoed, it cannot be re-embargoed.

- Note: this may take a while if your dandiset is large. + Note: this may take several days to complete.
From c94d878772102b8340c0e5869814acbeacca1f2f Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Fri, 8 Mar 2024 18:32:24 -0500 Subject: [PATCH 10/21] Accept config argument to get_boto_client --- dandiapi/api/storage.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/dandiapi/api/storage.py b/dandiapi/api/storage.py index 1223db9af..6477bed14 100644 --- a/dandiapi/api/storage.py +++ b/dandiapi/api/storage.py @@ -337,20 +337,19 @@ def create_s3_storage(bucket_name: str) -> Storage: return storage -def get_boto_client(storage: Storage | None = None): +def get_boto_client(storage: Storage | None = None, config: Config | None = None): """Return an s3 client from the current storage.""" storage = storage if storage else get_storage() - if isinstance(storage, MinioStorage): - storage_params = get_storage_params(storage) - return boto3.client( - 's3', - endpoint_url=storage_params['endpoint_url'], - aws_access_key_id=storage_params['access_key'], - aws_secret_access_key=storage_params['secret_key'], - region_name='us-east-1', - ) - - return storage.connection.meta.client + storage_params = get_storage_params(storage) + region_name = 'us-east-1' if isinstance(storage, MinioStorage) else 'us-east-2' + return boto3.client( + 's3', + endpoint_url=storage_params['endpoint_url'], + aws_access_key_id=storage_params['access_key'], + aws_secret_access_key=storage_params['secret_key'], + region_name=region_name, + config=config, + ) def get_storage_params(storage: Storage): From a5f8d1f5e50b6403a4245cf9ae6087632287770e Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Fri, 8 Mar 2024 12:34:27 -0500 Subject: [PATCH 11/21] Implement basic object tag removal function --- dandiapi/api/services/embargo/__init__.py | 34 ++++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/dandiapi/api/services/embargo/__init__.py b/dandiapi/api/services/embargo/__init__.py index f01885c6f..3ca8f22b8 100644 --- a/dandiapi/api/services/embargo/__init__.py +++ b/dandiapi/api/services/embargo/__init__.py @@ -1,7 +1,9 @@ from __future__ import annotations +from concurrent.futures import ThreadPoolExecutor from typing import TYPE_CHECKING +from botocore.config import Config from django.conf import settings from django.db import transaction @@ -14,22 +16,32 @@ if TYPE_CHECKING: from django.contrib.auth.models import User from django.db.models import QuerySet + from mypy_boto3_s3 import S3Client -def remove_asset_blob_embargoed_tag(asset_blob: AssetBlob) -> None: - if asset_blob.embargoed: - raise AssetBlobEmbargoedError - - client = get_boto_client() +def _delete_asset_blob_tags(client: S3Client, blob: str): client.delete_object_tagging( Bucket=settings.DANDI_DANDISETS_BUCKET_NAME, - Key=asset_blob.blob.name, + Key=blob, ) +# NOTE: In testing this took ~2 minutes for 100,000 files +def _remove_dandiset_asset_blob_embargo_tags(dandiset: Dandiset): + # First we need to generate a CSV manifest containing all asset blobs that need to be untaged + embargoed_asset_blobs = AssetBlob.objects.filter( + embargoed=True, assets__versions__dandiset=dandiset + ).values_list('blob', flat=True) + + client = get_boto_client(config=Config(max_pool_connections=100)) + with ThreadPoolExecutor(max_workers=100) as e: + for blob in embargoed_asset_blobs: + e.submit(_delete_asset_blob_tags, client=client, blob=blob) + + @transaction.atomic() def _unembargo_dandiset(dandiset: Dandiset): - # TODO: Remove embargoed tags from objects in s3 + # NOTE: Before proceeding, all asset blobs must have their embargoed tags removed in s3 draft_version: Version = dandiset.draft_version embargoed_assets: QuerySet[Asset] = draft_version.assets.filter(blob__embargoed=True) @@ -46,6 +58,14 @@ def _unembargo_dandiset(dandiset: Dandiset): dandiset.save() +def remove_asset_blob_embargoed_tag(asset_blob: AssetBlob) -> None: + """Remove the embargoed tag of an asset blob.""" + if asset_blob.embargoed: + raise AssetBlobEmbargoedError + + _delete_asset_blob_tags(client=get_boto_client(), blob=asset_blob.blob.name) + + def unembargo_dandiset(*, user: User, dandiset: Dandiset): """Unembargo a dandiset by copying all embargoed asset blobs to the public bucket.""" if dandiset.embargo_status != Dandiset.EmbargoStatus.EMBARGOED: From 8e1fa1b5c3e9f7389fe4a28434927c1b93be1d4f Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Mon, 11 Mar 2024 12:32:04 -0400 Subject: [PATCH 12/21] Fix typing errors --- dandiapi/api/services/asset/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dandiapi/api/services/asset/__init__.py b/dandiapi/api/services/asset/__init__.py index 69a633af4..6c0ee60ed 100644 --- a/dandiapi/api/services/asset/__init__.py +++ b/dandiapi/api/services/asset/__init__.py @@ -143,7 +143,7 @@ def add_asset_to_version( and version.dandiset.embargo_status == Dandiset.EmbargoStatus.OPEN ) with transaction.atomic(): - if unembargo_asset_blob: + if asset_blob and unembargo_asset_blob: asset_blob.embargoed = False asset_blob.save() @@ -160,7 +160,7 @@ def add_asset_to_version( # Perform this after the above transaction has finished, to ensure we only operate on # un-embargoed asset blobs - if unembargo_asset_blob: + if asset_blob and unembargo_asset_blob: remove_asset_blob_embargoed_tag_task.delay(blob_id=asset_blob.blob_id) return asset From c25dcad6bc000c50a5d6c3bedcb31d0d68064457 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Mon, 11 Mar 2024 12:32:48 -0400 Subject: [PATCH 13/21] Fix formatting/linting errors --- dandiapi/api/models/upload.py | 2 +- dandiapi/api/tests/factories.py | 4 +- dandiapi/api/tests/test_dandiset.py | 298 +++++++++++++--------------- dandiapi/api/tests/test_tasks.py | 101 +++++----- dandiapi/api/tests/test_version.py | 83 ++++---- 5 files changed, 235 insertions(+), 253 deletions(-) diff --git a/dandiapi/api/models/upload.py b/dandiapi/api/models/upload.py index e22ddf1eb..a77b5ff6e 100644 --- a/dandiapi/api/models/upload.py +++ b/dandiapi/api/models/upload.py @@ -13,7 +13,7 @@ from .dandiset import Dandiset -class Upload(models.Model): +class Upload(models.Model): # noqa: DJ008 ETAG_REGEX = r'[0-9a-f]{32}(-[1-9][0-9]*)?' created = CreationDateTimeField() diff --git a/dandiapi/api/tests/factories.py b/dandiapi/api/tests/factories.py index 6ed413db7..a32573e6e 100644 --- a/dandiapi/api/tests/factories.py +++ b/dandiapi/api/tests/factories.py @@ -3,13 +3,13 @@ import datetime import hashlib -import factory -import faker from allauth.socialaccount.models import SocialAccount from dandischema.digests.dandietag import DandiETag from django.conf import settings from django.contrib.auth.models import User from django.core import files as django_files +import factory +import faker from dandiapi.api.models import ( Asset, diff --git a/dandiapi/api/tests/test_dandiset.py b/dandiapi/api/tests/test_dandiset.py index 3eb1730b8..34e656d2d 100644 --- a/dandiapi/api/tests/test_dandiset.py +++ b/dandiapi/api/tests/test_dandiset.py @@ -374,45 +374,42 @@ def test_dandiset_rest_create(api_client, user): # Verify that computed metadata was injected year = datetime.datetime.now(datetime.UTC).year url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}/draft' - assert ( - dandiset.draft_version.metadata - == { - **metadata, - 'manifestLocation': [ - f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' # noqa: E501 - ], - 'name': name, - 'identifier': DANDISET_SCHEMA_ID_RE, - 'id': f'DANDI:{dandiset.identifier}/draft', - 'version': 'draft', - 'url': url, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': ( - f'{user.last_name}, {user.first_name} ({year}) {name} ' - f'(Version draft) [Data set]. DANDI archive. {url}' - ), - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', # noqa: E501 - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'schemaKey': 'Dandiset', - 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], - 'repository': settings.DANDI_WEB_APP_URL, - 'contributor': [ - { - 'name': 'Doe, John', - 'email': user.email, - 'roleName': ['dcite:ContactPerson'], - 'schemaKey': 'Person', - 'affiliation': [], - 'includeInCitation': True, - } - ], - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } - ) + assert dandiset.draft_version.metadata == { + **metadata, + 'manifestLocation': [ + f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' + ], + 'name': name, + 'identifier': DANDISET_SCHEMA_ID_RE, + 'id': f'DANDI:{dandiset.identifier}/draft', + 'version': 'draft', + 'url': url, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'citation': ( + f'{user.last_name}, {user.first_name} ({year}) {name} ' + f'(Version draft) [Data set]. DANDI archive. {url}' + ), + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', + 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaKey': 'Dandiset', + 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], + 'repository': settings.DANDI_WEB_APP_URL, + 'contributor': [ + { + 'name': 'Doe, John', + 'email': user.email, + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + 'affiliation': [], + 'includeInCitation': True, + } + ], + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } @pytest.mark.django_db() @@ -470,45 +467,42 @@ def test_dandiset_rest_create_with_identifier(api_client, admin_user): # Verify that computed metadata was injected year = datetime.datetime.now(datetime.UTC).year url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}/draft' - assert ( - dandiset.draft_version.metadata - == { - **metadata, - 'manifestLocation': [ - f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' # noqa: E501 - ], - 'name': name, - 'identifier': f'DANDI:{identifier}', - 'id': f'DANDI:{dandiset.identifier}/draft', - 'version': 'draft', - 'url': url, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': ( - f'{admin_user.last_name}, {admin_user.first_name} ({year}) {name} ' - f'(Version draft) [Data set]. DANDI archive. {url}' - ), - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', # noqa: E501 - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'schemaKey': 'Dandiset', - 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], - 'repository': settings.DANDI_WEB_APP_URL, - 'contributor': [ - { - 'name': 'Doe, John', - 'email': admin_user.email, - 'roleName': ['dcite:ContactPerson'], - 'schemaKey': 'Person', - 'affiliation': [], - 'includeInCitation': True, - } - ], - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } - ) + assert dandiset.draft_version.metadata == { + **metadata, + 'manifestLocation': [ + f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' + ], + 'name': name, + 'identifier': f'DANDI:{identifier}', + 'id': f'DANDI:{dandiset.identifier}/draft', + 'version': 'draft', + 'url': url, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'citation': ( + f'{admin_user.last_name}, {admin_user.first_name} ({year}) {name} ' + f'(Version draft) [Data set]. DANDI archive. {url}' + ), + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', + 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaKey': 'Dandiset', + 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], + 'repository': settings.DANDI_WEB_APP_URL, + 'contributor': [ + { + 'name': 'Doe, John', + 'email': admin_user.email, + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + 'affiliation': [], + 'includeInCitation': True, + } + ], + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } @pytest.mark.django_db() @@ -580,44 +574,41 @@ def test_dandiset_rest_create_with_contributor(api_client, admin_user): # Verify that computed metadata was injected year = datetime.datetime.now(datetime.UTC).year url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}/draft' - assert ( - dandiset.draft_version.metadata - == { - **metadata, - 'manifestLocation': [ - f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' # noqa: E501 - ], - 'name': name, - 'identifier': f'DANDI:{identifier}', - 'id': f'DANDI:{dandiset.identifier}/draft', - 'version': 'draft', - 'url': url, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': ( - f'Jane Doe ({year}) {name} ' f'(Version draft) [Data set]. DANDI archive. {url}' - ), - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', # noqa: E501 - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'schemaKey': 'Dandiset', - 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], - 'repository': settings.DANDI_WEB_APP_URL, - 'contributor': [ - { - 'name': 'Jane Doe', - 'email': 'jane.doe@kitware.com', - 'roleName': ['dcite:ContactPerson'], - 'schemaKey': 'Person', - 'affiliation': [], - 'includeInCitation': True, - } - ], - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } - ) + assert dandiset.draft_version.metadata == { + **metadata, + 'manifestLocation': [ + f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' + ], + 'name': name, + 'identifier': f'DANDI:{identifier}', + 'id': f'DANDI:{dandiset.identifier}/draft', + 'version': 'draft', + 'url': url, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'citation': ( + f'Jane Doe ({year}) {name} ' f'(Version draft) [Data set]. DANDI archive. {url}' + ), + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', + 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaKey': 'Dandiset', + 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], + 'repository': settings.DANDI_WEB_APP_URL, + 'contributor': [ + { + 'name': 'Jane Doe', + 'email': 'jane.doe@kitware.com', + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + 'affiliation': [], + 'includeInCitation': True, + } + ], + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } @pytest.mark.django_db() @@ -673,45 +664,42 @@ def test_dandiset_rest_create_embargoed(api_client, user): # Verify that computed metadata was injected year = datetime.datetime.now(datetime.UTC).year url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}/draft' - assert ( - dandiset.draft_version.metadata - == { - **metadata, - 'manifestLocation': [ - f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' # noqa: E501 - ], - 'name': name, - 'identifier': DANDISET_SCHEMA_ID_RE, - 'id': f'DANDI:{dandiset.identifier}/draft', - 'version': 'draft', - 'url': url, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'citation': ( - f'{user.last_name}, {user.first_name} ({year}) {name} ' - f'(Version draft) [Data set]. DANDI archive. {url}' - ), - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', # noqa: E501 - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, - 'schemaKey': 'Dandiset', - 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:EmbargoedAccess'}], - 'repository': settings.DANDI_WEB_APP_URL, - 'contributor': [ - { - 'name': 'Doe, John', - 'email': user.email, - 'roleName': ['dcite:ContactPerson'], - 'schemaKey': 'Person', - 'affiliation': [], - 'includeInCitation': True, - } - ], - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } - ) + assert dandiset.draft_version.metadata == { + **metadata, + 'manifestLocation': [ + f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' + ], + 'name': name, + 'identifier': DANDISET_SCHEMA_ID_RE, + 'id': f'DANDI:{dandiset.identifier}/draft', + 'version': 'draft', + 'url': url, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'citation': ( + f'{user.last_name}, {user.first_name} ({year}) {name} ' + f'(Version draft) [Data set]. DANDI archive. {url}' + ), + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', + 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaKey': 'Dandiset', + 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:EmbargoedAccess'}], + 'repository': settings.DANDI_WEB_APP_URL, + 'contributor': [ + { + 'name': 'Doe, John', + 'email': user.email, + 'roleName': ['dcite:ContactPerson'], + 'schemaKey': 'Person', + 'affiliation': [], + 'includeInCitation': True, + } + ], + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } @pytest.mark.django_db() diff --git a/dandiapi/api/tests/test_tasks.py b/dandiapi/api/tests/test_tasks.py index fe6a81c52..8e5fe6aa8 100644 --- a/dandiapi/api/tests/test_tasks.py +++ b/dandiapi/api/tests/test_tasks.py @@ -336,59 +336,56 @@ def test_publish_task( published_version = draft_version.dandiset.versions.latest('created') - assert ( - published_version.metadata - == { - **draft_version.metadata, - 'publishedBy': { - 'id': URN_RE, - 'name': 'DANDI publish', - 'startDate': UTC_ISO_TIMESTAMP_RE, - 'endDate': UTC_ISO_TIMESTAMP_RE, - 'wasAssociatedWith': [ - { - 'id': URN_RE, - 'identifier': 'RRID:SCR_017571', - 'name': 'DANDI API', - # TODO: version the API - 'version': '0.1.0', - 'schemaKey': 'Software', - } - ], - 'schemaKey': 'PublishActivity', - }, - 'datePublished': UTC_ISO_TIMESTAMP_RE, - 'manifestLocation': [ - f'http://{settings.MINIO_STORAGE_ENDPOINT}/test-dandiapi-dandisets/test-prefix/dandisets/{draft_version.dandiset.identifier}/{published_version.version}/assets.yaml', # noqa: E501 + assert published_version.metadata == { + **draft_version.metadata, + 'publishedBy': { + 'id': URN_RE, + 'name': 'DANDI publish', + 'startDate': UTC_ISO_TIMESTAMP_RE, + 'endDate': UTC_ISO_TIMESTAMP_RE, + 'wasAssociatedWith': [ + { + 'id': URN_RE, + 'identifier': 'RRID:SCR_017571', + 'name': 'DANDI API', + # TODO: version the API + 'version': '0.1.0', + 'schemaKey': 'Software', + } ], - 'identifier': f'DANDI:{draft_version.dandiset.identifier}', - 'version': published_version.version, - 'id': f'DANDI:{draft_version.dandiset.identifier}/{published_version.version}', - 'url': ( - f'{settings.DANDI_WEB_APP_URL}/dandiset/{draft_version.dandiset.identifier}' - f'/{published_version.version}' - ), - 'citation': published_version.citation(published_version.metadata), - 'doi': f'10.80507/dandi.{draft_version.dandiset.identifier}/{published_version.version}', - # Once the assets are linked, assetsSummary should be computed properly - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 200, - 'numberOfFiles': 2, - 'dataStandard': [ - { - 'schemaKey': 'StandardsType', - 'identifier': 'RRID:SCR_015242', - 'name': 'Neurodata Without Borders (NWB)', - } - ], - 'approach': [], - 'measurementTechnique': [], - 'variableMeasured': [], - 'species': [], - }, - } - ) + 'schemaKey': 'PublishActivity', + }, + 'datePublished': UTC_ISO_TIMESTAMP_RE, + 'manifestLocation': [ + f'http://{settings.MINIO_STORAGE_ENDPOINT}/test-dandiapi-dandisets/test-prefix/dandisets/{draft_version.dandiset.identifier}/{published_version.version}/assets.yaml', + ], + 'identifier': f'DANDI:{draft_version.dandiset.identifier}', + 'version': published_version.version, + 'id': f'DANDI:{draft_version.dandiset.identifier}/{published_version.version}', + 'url': ( + f'{settings.DANDI_WEB_APP_URL}/dandiset/{draft_version.dandiset.identifier}' + f'/{published_version.version}' + ), + 'citation': published_version.citation(published_version.metadata), + 'doi': f'10.80507/dandi.{draft_version.dandiset.identifier}/{published_version.version}', + # Once the assets are linked, assetsSummary should be computed properly + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 200, + 'numberOfFiles': 2, + 'dataStandard': [ + { + 'schemaKey': 'StandardsType', + 'identifier': 'RRID:SCR_015242', + 'name': 'Neurodata Without Borders (NWB)', + } + ], + 'approach': [], + 'measurementTechnique': [], + 'variableMeasured': [], + 'species': [], + }, + } assert published_version.assets.count() == 2 new_draft_asset: Asset = published_version.assets.get(asset_id=old_draft_asset.asset_id) diff --git a/dandiapi/api/tests/test_version.py b/dandiapi/api/tests/test_version.py index 8e21b4fa0..9c530999d 100644 --- a/dandiapi/api/tests/test_version.py +++ b/dandiapi/api/tests/test_version.py @@ -309,50 +309,47 @@ def test_version_publish_version(draft_version, asset): publish_version.save() assert publish_version.dandiset == draft_version.dandiset - assert ( - publish_version.metadata - == { - **draft_version.metadata, - 'publishedBy': { - 'id': URN_RE, - 'name': 'DANDI publish', - 'startDate': UTC_ISO_TIMESTAMP_RE, - 'endDate': UTC_ISO_TIMESTAMP_RE, - 'wasAssociatedWith': [ - { - 'id': URN_RE, - 'identifier': 'RRID:SCR_017571', - 'name': 'DANDI API', - # TODO: version the API - 'version': '0.1.0', - 'schemaKey': 'Software', - } - ], - 'schemaKey': 'PublishActivity', - }, - 'dateCreated': UTC_ISO_TIMESTAMP_RE, - 'datePublished': UTC_ISO_TIMESTAMP_RE, - 'manifestLocation': [ - f'http://{settings.MINIO_STORAGE_ENDPOINT}/test-dandiapi-dandisets/test-prefix/dandisets/{publish_version.dandiset.identifier}/{publish_version.version}/assets.yaml', # noqa: E501 + assert publish_version.metadata == { + **draft_version.metadata, + 'publishedBy': { + 'id': URN_RE, + 'name': 'DANDI publish', + 'startDate': UTC_ISO_TIMESTAMP_RE, + 'endDate': UTC_ISO_TIMESTAMP_RE, + 'wasAssociatedWith': [ + { + 'id': URN_RE, + 'identifier': 'RRID:SCR_017571', + 'name': 'DANDI API', + # TODO: version the API + 'version': '0.1.0', + 'schemaKey': 'Software', + } ], - 'identifier': f'DANDI:{publish_version.dandiset.identifier}', - 'version': publish_version.version, - 'id': f'DANDI:{publish_version.dandiset.identifier}/{publish_version.version}', - 'url': ( - f'{settings.DANDI_WEB_APP_URL}/dandiset/{publish_version.dandiset.identifier}' - f'/{publish_version.version}' - ), - 'citation': publish_version.citation(publish_version.metadata), - 'doi': fake_doi, - # The published_version cannot have a properly defined assetsSummary yet, since that would - # require having created rows the Asset-to-Version join table, which is a side affect. - 'assetsSummary': { - 'schemaKey': 'AssetsSummary', - 'numberOfBytes': 0, - 'numberOfFiles': 0, - }, - } - ) + 'schemaKey': 'PublishActivity', + }, + 'dateCreated': UTC_ISO_TIMESTAMP_RE, + 'datePublished': UTC_ISO_TIMESTAMP_RE, + 'manifestLocation': [ + f'http://{settings.MINIO_STORAGE_ENDPOINT}/test-dandiapi-dandisets/test-prefix/dandisets/{publish_version.dandiset.identifier}/{publish_version.version}/assets.yaml', + ], + 'identifier': f'DANDI:{publish_version.dandiset.identifier}', + 'version': publish_version.version, + 'id': f'DANDI:{publish_version.dandiset.identifier}/{publish_version.version}', + 'url': ( + f'{settings.DANDI_WEB_APP_URL}/dandiset/{publish_version.dandiset.identifier}' + f'/{publish_version.version}' + ), + 'citation': publish_version.citation(publish_version.metadata), + 'doi': fake_doi, + # The published_version cannot have a properly defined assetsSummary yet, since that would + # require having created rows the Asset-to-Version join table, which is a side affect. + 'assetsSummary': { + 'schemaKey': 'AssetsSummary', + 'numberOfBytes': 0, + 'numberOfFiles': 0, + }, + } @pytest.mark.django_db() From 177f0816871543907af295a6592390c208bd9ae3 Mon Sep 17 00:00:00 2001 From: Mike VanDenburgh Date: Mon, 1 Apr 2024 12:25:06 -0400 Subject: [PATCH 14/21] Update playwright test data with new migrations --- dandiapi/api/fixtures/playwright.json.xz | Bin 148968 -> 151184 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dandiapi/api/fixtures/playwright.json.xz b/dandiapi/api/fixtures/playwright.json.xz index 345080fb61e97dfce3b7f4cc8be782007dfc87a0..3fae1cf6ba01fbc20289f261cee6428479f78002 100644 GIT binary patch literal 151184 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 z zWeo3*;;+0In2ZleboWhTG$L)PLR#oKuDLGEMG+DSCT2Ozh?Kehvkz?)*&EqeP&Xk+ z*x)kMh4_;O!9P_S68eD$X@ex77o7`H8M<%&*e2W+A#4V$W61YD*Us_Jpm{PM6sq|8 zuV$1=e1a*J>DW;v@q_<{a*%U3Da#4tf*K^JvV?x8E7bLOBF^=1+tF*oA~~NlZI>ie}%V{%^4f5!JjJS$9M??O@pBxk$zjwwzsneQmoFu zqs=K&5H=`tx!$Dp1F* zJ}vT(^eH$JbGI+>@U)B8_f6(H?UmkmpMvF%8F6w~eq?Y&%pmdrfCX|IHA7LR1u0;u zTH-3JuCJqVf~~*6gouFulRQ%ND~be;(~9NvLjv62KPtH9d4)(Gi9MV~<$g80;8y*5p=VjLHhlT&@r&-AW1~mK? z;NBGB7#f2G2lwD>1Jx>-jJkjaLsrLWnxR9jy`Mps zv*aTEx$*B$bROp5Ht{SsV*DoadNa(_8sKhYcqDt^_YG5#`Y?7kv=RuBs_a2hQ$ za@%b)o_YG*AXv84v!X+&F*R`)-Om#`>8V(JmIQhRW72EpFW_0y34oof&4qorfPQFh zkUIbQ)8@iLRy++AfYXl`qZww}n-9Kv+VSkzw8!a?e2tJ7Z*(2nj#@4~55VQzAVr?w zR3M#cr{G-4hfPy8?4oRoTAz#kip3To5`>dWedMx7gnt0Wn)y_upCO>WxZ8+L_lCf3 zGCiqi{(wRsga@-@Sz&29UjoH?{4>8aaAK-q3*Uk_3WWGjxK>eeHJY5PN_jAO;Hm_S z#2`8DBGSI&jfVbS%KLJ3oa#E+TERX%Wyx|FVJ+GAy@Xw^!Cc6;2=~8n3^)Z4`UjQcG;;X$ey%dcm>g@eR(! zAC5(*qIlBo_3gx8!~jFx4(>@#&$b}|sdXX@pNPq@K<3>7UrHOqR-dSowUcl#9cr+O zWDBnB_}tNUfxcAKH+5$iqZAI7xR&7N3`W@7^In7()#1)6Bqg|Va3=YjEI)V|3_mqU zNC!sf%G{fk1*JgxKPwmKnJB=Kj+@kHD`xXuWXQ{6c7IebSle#WFe6A2S&Lfy7sDyL zk+&)#Y#Bj4#7qgym!8hpykSKJ?!9<|vhtD*I?-$VlU*5dMLZI)RXHL-Nr6?FDDH_? z`w1CW*s>Ka53~aL5)>l@?auSaQUDSZX$`C0V*^+o6m%DPKYv)yV+k6?{McvM8kPH6 z8C)$Eh4w7vn9!beL%POnYf|SY%*HH{^);6{&L6F>n#><~wvK{FV}A9C24n9(VC<+J zzwx8%M5NV6T^NG}|3V8>9wIW>_ZQ9&+%_2=Xz0cK6+yU9oJ8gJs?@DE)KQlDxWR4> zT;O^&>hDyxQrkfTRUj`x6Ck!&+)kHp0&i0+(5f4ETc8tJOHRHZEbfN(i{BwQ zK+=ypIIqu<#$M-3Uh(UM0I@`+^(WH99-&O3D}$hsu3aX*y;*1%8E1m}dL#gX`nF$K zR3%PixnR2#l16Tb-HaImRrhsjl1JrN?qH77=mZ2Bylw3h3UWwjk8&lkpby90u@g4H zGu+9mcC>5KT@wdS&QJMs6Qt{DBZCs8(sYsAOau}V7G!APABwbbw-0R!+3$dF!3(&h zm%JnpoVJZkRlnE8(Wl4kBrjnZ9Dz(Q8L-T;(u{J$3+Y9g~$Dn?|3^ys;)|emrL>5wFkd%woiNWR_ zEX5xSA0PqbVcdgup;FbSd$XK>RC$Z2&`96neUW_7o8jZBe|HFNk7p~2I}w&c^(E3S zPl&29Bm6hHxe^$_mXiTKTs;8PnVSO}eU43!Xs}prTH;fZ4KTdUQS|%U08tfpV$xup zkH(*ebKUXZb0tR6Xk;xhKD^OY9FcK#tca43uU@lcqAxz!z)a!q>Vg9Zj>Hz6~v$s z{tFHr&Mzs({5jiU&W_~xiRzNBkq;pHNjuZe!}_@^HZ+EN&}>v;Pa`32l>0fY%Pkyi zoKT!h+(v#~ni>rJLIw31G>J!<`$L=ZpPL$4m)PFf8)iDFr#zkP%3?xHUvirSga1k7 zf^LCMjc*p2DTf--XC}jB!-)(x-?O9xIQa$I+ zfn9zng}8DkkZ?IW;ZlhX$kwF3qpM!!59ORRpw42>d>dk>d`oZFuEBr3wsgZK5;zn# z;n7e0+}eMM;^$6dpzss;pw2ie^9=wCSONf^Zx((7owArsD{_bs-{M82_uefY26|iEFe< z(GBxHqIzab-d(9|f+pwq1Z|C~k(g#h0d($v_dz%yh_g4JkW@psJl>q#S#wP6acSG2 zNg0zrsNJ^A3FOGo>4u~=717RDTNkP^b2LkTDFG4v@BAfTYg!}Ta62O=8|!n7$2=~t zNfN2wNecUSYae2Vnt-&*wgmY4)?Kt3lYEYY6wgEB0?<%UqtuAe6zFGjVbnIrOJa2y zBXdkdlbKXI5xL>JQX03%Vl|q}{ez0sFRE&xg+8+04vwrIv(R<@cTA%WfYedU_U%gu zzjt@QA3q3vG}X{*SR>%AqwU#nEA)QR=d*_i=?rg;~kbqpkDCo)PH8CyFf*-o6mIi#)T&NFo_04ckK z)g?|&GB1y<;>g+}!sV2GL^)~k7ySkwjdWAkEd-2HT>T>_*`jCl(|Gii6niAjhMzS; zZ=&h_#%_gh`Y)4kR=q=bANrIjs~FcC6v}tv41pqHmT3dPTZrS1ScU7!7QF0%3qIV z4mq<|AhxFL4yXC<}mTn3~%Wj zgU3}TSj8lT5wa4sgT=^fxFTZQNMKqF&e=jh7S5u?KYZrC+%UzIi8n1DF$@=mC;jza zYqVS92_NK?KX4Or%z51QS3w7PVb9%fuw`;fhIQaHy^tyK=KIM?Xez#rOxAI- z*JX1XM7Qs%BxV))by|uOt?0!&!XAg)4~7VLO}%`w&iq365v2wkHl zuF979*@Y`K=*z(;*u-~e;4{{(pj%04O3b?Cr-#kxUq~_2-$u^`9r%`0G}91(mjQH7 zyKhDscPK(BS@Z&a3+~FB8PMleAaL#Ro5)#wxk zS@X}5W0{~j{u^j0it4=sH2431Zcj}Pg^*v*1=e11n}2_P#l{+?KjC4v&0RFQxnVlW zl9+3K`Yx+8N)r3`s2l;Tdtjxh^62hAC0MC0@{!$GRDnj#WMPMvzwRdO1eL1zJcR`a1_vz%o>rCmG++Tq+l!b!Zr;NkEY zKZ7W^^VBY4PtFDG-jxe*AWv~{5A^tl21Yh@+3~`4h&?6qpG5@rMU9qB0q{k5#BDt| z0$rmrn=Kq!fNf+W;)#)YqM;r()jOHkc$X4B%CJaJK&Fd|6%d`QWIxii3OJTAqM{sU z8w4Tb0GsTHWD*RoX|iZ&KOXOy?MkIvbu#;fJ#@wB7>dw8*cjaA_aW6BAf$7x$iODI z5lpbQNUNKvf7-^}O=Ne|Z(I8{ov1;8NI>-zU`KQt70Xuj18Xz<21!T)pJ_=MJ z?U7SesX$c*FPJM65{e_3!Pq zH4A7c#=(k!IbT1>lveC@&dv_Ycpb?x!pBv{fz~Sg$ldJ~w4{~WGo@Qz#l=NUbxH5? zfBum~+ykgJ;EU5P`a>ZjOL15*Wx;oG-L#+5TBop6e#hDOOL}n=*1UiK>&EoTEJ561 zM#NSqq5+=OVr10+_M`>wOa98WePZLgQSb#Rh~f2hsZ)un znTgY5(UpE>eH=z}bRbSlUbX|Qx+ zR7IECayST;t`?uD-57U>(V82v$+VbzU19bzb6>IAfV;qqTJ|?@4FSJoq~%}Jh)t<5 zqhvh}E7X*_kkrjyfxeb_>zIc61E4G4`6rlA+te)Wiv^TmyjW2xZ@3cY z=lCDSEflfE>6%b45sy(L0Bzq|wKRB|NiK-%r}Hmo56gAZn2bQ`5BghK6;r#JM5L`Q zAPZ6xIIXO6Fd`m6%&3b(c`5qxb-(@fR?`u}OF1LOQ8)Wm-md9xQgc&jgHMr3oyx@P z?rd>xhw?F)cjt!~>@ruxK`hXcQlVaxyf|+cAwP{9!|PN>&!hW%JRWi*t?xBjX$r+D zD$u)2|95}68dqj}eRnII^>}?tWHT4J31Ey+ndX%bzXx#R&meR~Y4ZVgN-hvl3nzsC zqmYU;@1buo-@YtU0>Y8{K`BK|sR*5r4>#AS#Rk7F*q^s6OvR3CM1d2GE4>IY zaWVuEV5bZbDSjA4>$kl(&yK7hoM?$+aLpuzU&L)&j+XI_MCl|s-bW+ftl4ssank>@&Cx<>i7*173h%UZj%pT|7~)~IWmzn; z0ilkzg!);pq*W)j=Dyrbfh z9xxmkk15k!Vx!FCtV>bj&(HDuP+?8?-|j`LmR z`Oj2i^WVrW(UMMJ6P%XCvtILI4GF;WqKO0lR?W`rBL_o<_%G20QHdt8|7j4mVp{uCg4 zh@G1^^c%F|Bs+DAHy0lnnk7}q^nt_mo6I!L6{+~btHB)ZUpyppsQi-(C37TkySz$h zw5zJ2Evh*YNAg&apE?b6Rl5t43TnA%$zt1u`%a&-&)uiW^4Mo1hs>*BoFId5G1s*E zlOAg26Qe`6cEie}7YRc6>xDvm<>C&3zSL`^G+8dIa>9sOYX;~lIaJk(YN@-#?b-V7 zoGR7_&YdmHrc0uO!IKGy`fv;<4Ua%UNWbHQZv{|Po;c1zzOw53gpqVu829qG93H4gICbGq{Zw}9qk>8mhUn;6irKrDO%>Sb!(OC zTr%XEiiXNh+r>$hb=TW>$!H957O+8noS-MEsUDoTlJbJi6CccJ+{B{G=~}K~nlj3g zF1V%n#j`ZD_n1}!KI)F%kei>cdkYq#ZywerLzO?3BZk7w5-SMJ_M#3@SW{haAb5x( z5o@w1Pkd{yskQHJ0GlNU#=hwW1byNU1nS)v%3I9f^GNFwK0y(w`MBH9$TA?p zD_>seisosYdpmO28`$r` zf6f#skwSt1QK{K_#omMnNM*MYK?Yc#*2223JdN7K!u7NAtN$a>l$5SDDI^0okskCa zblye8$Fnn{HBlt#in1hanvMTBekx5kd>UXol1~im?nLEba`@?2h1AK!1FH5Mavydy z$wl~S->AKx;8ED)Xizb<-}xDJ1j#+A0NVrJ#HV79NZ=d7@NNQ{N zR{#(la-UJ`djiji%4h5#QuYh-_Z(EgATYsi!aE0Rm#peAy6N-@MMYx%QXluB$w%?p z*!!ZsUhPv`N{+E0Y*VLs8nLkQ%H=Q3-pRQU2ir8~1-FBAX*vBz^-~C()NpWgqAOal zSMm4UKvwq&`)(SkD?Z@7sLojBQ81QeJ~-wXX|hL%O8hfctlkvsebU3Y3+~=t16Mz; z*bFFcv#$O}VH{lWqVTg6X>*dnhbI}y!C;oQfxkJ`hMXM@_R6OWN@c*`DGa|7@t@Mb z=|eLTe>LFF;JH+{86@rr}2G2bc1ptDt&7wkh(e(lUC{qG-^xe)&f zaPZk_tBWRAWBc^nJLOEA<>S~|&Z02K=h13aR(d^n%Zzr?23yE$jTk+YjRjPO2$*?m zTda^V>?=XYZA!+UV$*Q)=wX)baa%5jrcEbi%+4+NL*yEVMz1n$N2UBakcD1lby{71 zV)3_!3kFuiV!~7ACvN#uXvQdO(~oqnd*hYwLIL39yZM0GKeXh2m(B?#pK`S68k09k z%nga9@erIJ*Ung?GyHxmeNbJ%Y8KQZ%Xg`)=7C$`^C2AD%}4sgFLBhWT<3%MC+2kh zS7H2bh*O@ALpi$HYo9RwI$EktKGlzWjWa8jCG?{!{PL^vJon?4f^LOp#n152ocNEk zYG=P|H>X0#X~gdfsvZc*EuO@?;0k0oSl>k_KLaU7GpDqYNfwRWNba9^=dc#tdpoU* z^P()_7lg&bT-4b|;oI;%{O&{G2IrAV$asC4k7>pp3Py*1IYW-7DsYp|5nvd5Lz+~c z+-=h#PNBkO`xk<&S+BNhJ+}D9!w;WLq`#CvBzpiL=jT=a*oU5aGLu*kT*bqp?9Pwd zhsdU~bpC{JfL?EAjUk8VpX!XTTnUW69t<64HS#@^>P1q!`=qVDe+8zlHk*#{&RMqK zP}OYtJ=CxBV$*%L$#R1;ej}4UdBA^k1c3m`Gfmi<)$%Q|;?f+!eaAp!JIl%v0AGs* z^ag|kq~rJDVC*t9F_RuS$@@{wm-Y2mXFY@;)X7_Q9B7sxPi#jOZI-j7m#^6Z4yhyV z1`=hWZb&~-X^Gjo(+OFzAnJRwOqYNe{pwxpg}Qt@>95e9lcUIp&wmpq{GkL+Q*|BY zv+u#K#*v#Ajw!lsPJ~L;SJde$Rt4XaCN1+4@ve7g?WQ9Uu>Pk%u9ftIz3jQ?M)O7% zGoi;*reYObwv-rmyeF5rQgdnd zu__{SG>Z&FHH7lK>YZ_vz35*y28vWU&mD)nS3?KoVq#aq&G_#au;c7WgM5X9$0KC%?v)#ZzQ+<2ngJ8Sd@4ua!u4tn)0f#D}b~oYE}u!k4#(G zkozAGY4!px%TKxP5}{E6Xuk+!i4AUOnk~ug7En#6)$O(1IlfIRqn0&0>v?*dW}Khv znZU6L0b-3Y5i0}HWPf=|8v&QMVOR^Gw%I8O1Bn-xMoUVOP;6ZCF8L41dHaf^gvp?N zl$Z~5oizVTLAjj9r@YznAVpaB@{wZ|_ms|@WXn&Y%~7Sm0(}yO77G5lK%GVv2qKog z-(8S-@HPbZ?~#f_JgG+BX;wu9q@lDzt{uS}0Hk&q&4H*y zDw`!m`1Ym_euDQ-Zsp#^bONr5)SA`=Ddy25V=CFFu6l3QWue}?8sRrU47V?@iS6&5 z`WMmzGPJRKo8k4(*D?frZ9TCIJGe!x@)V8^zCH6HuH_J%M)w8EwP%g*q3j5{VA>3R&8EJ#fT>UQV%^svJ9TMi&>xsPZyEil5+S$VbCz-^=P-h8HzX1(*mV4*PJl~CHhhbtH;Obt zU&rw9wm-zONiSJ=*Te?|oKN4)VPUQ$oQBe;N$q`$ysN3*tM=moYG0*@=y^y)hC>xe zkdFf!a6 zwh5{eBaO0Yk<@>xyWHySGy>P%O{tZ~GXWX3Fe$Uh_lto{rDhmjwLg#BO9>O<=>c>y zlFSAnULxmax+fX1rGRkN4D<+m!{N2hkR>V}ZSFDKZ0H+TV_<3c_q=YW*(F8Vemr(n z-&+}$G+`j#$WKdyOr5GqXPd4;lrKmpza#o8CuCkRjWQHB;y%A!bJKt|VLlSYz+gNu zen^uJVf!9ymk)%_zoCyKz9f9ejEG(OaagD!%a-IWnop(3IoOs!0CD!er&*EQSsHKs z-ob?~V#mpt`WUV}o*cNR7Qwx~%^|Qy^R#y@qE3Em4EDo2xCFhWcgPAgwWcTt5=_@)2a%P z;6@-)pP}4uGwj>T96x}In$werMy%l9n9(FhWR>^Dn5;|2$O-eH8`tXo!jY(A$7==Z zXKC>44vL}_uG)NlIkO;hpd3dJk?V(w_F-e}AdO8eEW;nw7wA9d$&S!S0LFE2eCKjO zyL9p06-^GP57!dg^=WgSU_(UnBx+qL^qOdBC&pXdso=aHQe$Mc);~Tq8t?fFD4my* z|B;vPvJ@6{DcIzEP2nv3&fRg9a5};6JG1@7B5SX+2JHD_iLmARH-5YCibeF+0pJD| z74V=MBKX3K`d;O*%VNlXmbdSFJ37Z*)>qy6+Q6*@z^NpVmW~f~~bB$P`{1u5Z-T@kv-xsR9Dv}L~cFHfe{uDaFuBzr$kwrmc; z7J0@Bd*z5KkrKEZchgjq2arlzPt>@^&PnPAeJRB7po1A=PZ7fKkGB}&Dpy}oa&6QF z2U{gWrw?Drb$Q%X%_c$#!vSsO-KGn(G%>u|E9CslRsM((SQ>wfn))(8 z_8g!?#~_ojW-{fctaK3nFXTOA0p~iBW~CUV<84B-O?VgjS!rY_*!sb)HJjs!x2dD3vC0R-j!=%eo{ z=X}$(^T#K((`e>q9(>XH`cm!4RZb*0rAM>Ry{>+bc^ZqH0wqL2MjsF)BAw(4V|$J% zndM4CrSd@I!T7^+55e!%7kv?q;=e3Lki#b#r5LR&tEMSoPNKss%Qus7AG~E@Bncyo z3QvDGJ}kA4qOMLLs3+(UCKv}x{A0!ilX1fvq4He?;A9E&cS_J6roi)cTN8P!23j`h|gR=Df} z9%nbsE-mEU0?11(jXqQUXNx47DL!`1)Gk8C)fNDYXS&uD%mQ5rGd8U~%KTA4)=)?p zXQ+<(+J`?G$GO@>8`7D=I8=XZ>*z*WaPs8HHQ(Jh#(*tYUh$7yxX``7SA0K4ZdCuM z_(tb7;!S2Hl^Qs-@0*!K1GG2v z2j37-DkAblW)*1tVS|BqyPd0#kI<_S5Wu_F%q3|+Xb3*YdY^FMUQ4+p&wdT<>_b*5 zbM>M)Qo`dZqrES6w9Bpb50tdQyT5qqf7YDol==~%0Bk_ouHyz-s{d zVjLMf8H)El%YY$8;EM;84adO$RDTCn&_mXXF$?Gj4%0jp)ZZR4+zt<6lOOm=K{$WQ z4ehh9cj=KVobexfp3DaS=hz>A$3(>$P-uDQ0pDhplwfYh~0rV~++vY*U}Jrx+im^dIj zgC`-_OUuS~fv!6uPP`bPzO(rX>Lp|da9WZL8a*wV)q6s=ZY6ELb${E^&BcD#L{n>- zwSw}MSELj>!IxCdx~h^n>JiYAofKVz?&}!+uen4y&!KGEZCYFlqiCck0LkBE+VT^o zeSHW5w1?6Q6^Lt*7D_)x7myuKKEuivT5&ppqrC&BK;vG_$>v@U?>q2>nceZ&lycZD zxDczoV_UI38~Y^8ENpvfX&x>eCbY-9k}3!d>6~p_z8hEd;PCKE{^W_!!wC~e_sr=O zk1NU}Ox)s4h>SdRdH=|srEg`-4_&Y=o7U{+@`L|Y%-ppNnz9T6@KOIiLb>POc@1?@ z=*ZJ6fqSzpCZcI7p&mEp#(uS`q;r^u?tX%Q9u|=OHh0$Az%;Km2b7;RSG&rxl~-F~ zsHKO_`0(xExg%5B3i$1PzIVBdhJP^!KKhV0Kzlxg7&xEY{riTNl#-Ac;dksvAnBM%Z?Q9Xg|RaOnq<{;(bdReE@D>o86C)v@Mok3yNX2)2C4?L>)e zV@Gc(duATlYda0caOGT$1@NTN$1vFuFm>Ey*sx4Rv#jVInO|_J;!Q8$Sq|VtwIWh0 ztNC6UN?6e7Aj{r}dPsSiNJWJ_6`TfX9yh2|>M`~J$`_=KrfpPD#B&2J;XKc0z!k9p z&V9)ikFvxIa$_OtdZyQT!wr_e=tM5jDNDB_Yqryz$buy`3>ZXoO}Zh5Mjd@n(yPBJ z9Km;J`~%h=3R(jz?H5-!BcSopbfs)0XI{i&3RgQ&n<4XqZqFaHb{P*g} zsN{$gS}g0+;HRgEw-Kl!c9LADe`GrDaaam$;@pBS}nH_mDE1bES=^A5Otg zktiE)u{b~{O_$AOYC=lYCxQ8TvOt&XfXz-Ofd$mYp&TXkMCh=Qb|G@x=M}P1D08bV{5&5kqC7C9xDJ&s6m4Zj& znIf}vG=W$AD3-Iy7TTEdKiG?WcL}9^S4=dAYj1{w*X2zEJ@f?mcv1K0HXS|2Eff_J zDi;&+*xZ;JotL+_-4Lwt(FLVitka ztj!vI(m2@`9I@)hY_13}FK1mO;?VfUG1GT0nc*ydHyF2JQ7SgS00aay!`OTY2!B)i zHTx-M>c{o@PVqHO?Y~TEX-^8wXLJaqn{}g2Ce4uq8ZDE-whI7a$_x|nF@n`OD?CCZ zuVg@-P{r*0n51E!wMg$@n)3Z9-2rc{cRMBSSM{@Om8C4dq8WKWtVMS9CLk*RT7XZ>6OIpHi%#65gF+4`L5N3x($h_qKuw)D)xKmZqGTJmQc)`dfEwRIRs_ zqiQEr33eU)e(5{#d1`ek5NahjYX_;sPgOoqf^HG!M{%6aA@plxvmZyobD%&L29e7`&&*69gJnoXefvvWX zU`a`$zc9_RyA_wIT=3);_XQ05C48<&qMj(1;IEf69=i@z0eA}TuOJ`Xxgv?8Mw)Nb z&Uqm5tVezRoW{0!XnD~gqkn$V34WH%ZYx3aB3!twgk3q_@~3B~;vo)KX~xgdvWsZSmD#18U+=Y1LPrn6$pwG+WmSQSZ?xGU zNl*2xFyk2f3(426G(HF8P#-~l=_y%3ksrTi@1-TRy&}b$HYz;73>mhOklwTlXa{P? z|IQ;Lpo7}f)Hba9xZdLJAu-#3IOr@;o`eX*ZJ+%R8!tx(3AKDjfufrG)MWC%e=0vg zrb@1dj-I9IM$ju`@OW>RAxU+5+c7{Y9mt<+YXVa#DmRvATM^4uCfyPW##HAr!0R4) z5Va?V3O?IxF_jfBH(stb?pe!@013jBDigwH4E=()X^kS-jW?DpI-*DrBGlXSyAQ4qEJ93_-qdcA1FTVs^| zX|efiiQZ}bXsJ?42p0p!K`MC*_dAO(&4vN$>>_QiPtKY{bLcK#GefzodMvz zwkIVPgqP8j%xM2F$PJc+2EKLwk2e#Tcin3dF!Z}rtb{7PMxL9yv%TiiZn7ds54Rp= zRLg2}<9pN?X{pO4V?xOdb8J9nX7>$KApIw>P^lXw1rZ7>@!Dl1`|Yx4s1}F~DBY*T zR{*8d2`{h8?+6>BqZVu+Lu0}*^|@cqXD>-{Hl&h|A-=uV&X^oFJr490yc#e^ z!%~%!cgx?c56s*1R0pn|B@se*?=RI1O_K@j2Z;Tz{>M}7H4C-7-$|2#as*{o+lgYq z*cH>cm4JDfw=|6GCOvhuC*EHUZK_J;@TRwcX{X*F<3{W#*Fs&75HAs3vt-8wqnY7l zcD5Fx;F98HIX49i0}?eSm;)vhMEz{7$_YSt+uuEJPzr4$Y9sli zT{OYEIH~pzD@lO?;;Zrz(dZyyy?N~*6asE6g;Y8$T-4D-yYxW`{InFI?E7!zG1_?NoyCqMnh*$~(npW4dgta}zA^MU#w?x^Pw9Pv*)*JO zUu(^kFb}XzX;fO==yI3jWmf17ZMbYC%zx0+G#M!E@P;*&-@MfZO@e{ktI>DxR0kMM z8sE1UC`T88{Dy|3zV*3}hP{YT=(Y4=!M;eWbfkt3(dhjR>f)wWvU4AXS6X&TW(?za z1aMc|-jVVg*kvE*ItlSiZdG)-?v0}d%S<%Y54e9=m zPfLMA*GY{*bLVP>W97EvZ8K0U?H1IuuW354+0ce9J(fVFsF8hvjo6Vxv7;)1RgaI= z$A@Z=Tpi+3qIfZ6?7*&(;HlKs0N8|R-h+v{5L0|&RnR^-wn!Ci_H-*bR>IJPEp{95 zz7ZW&OztH)Ody%~HtFo$O+e3JW05g@L?4bF)jIH2N9?=)j-`{)A5d_u4d&^ z$L$DGT*xUQVC$nirQFjf<$$q~&ypoh69hqQ1li%*LgANjI5~Zup(VusMU(pj?-EjK z$^WC^TEEsrPd$r%NIdcLJk)F+8_tOJZ$-*1$n&&~N%z5iug`OKE(RPcbJueBDy8T_ zp*l9SYyQEdL4yM9A9xk)fAr~DRoZ3rqc8loO%U*GPmVkC&5!EWJ9(UI|{|GZ~)bdT)W6J4>b~k$_cIj&k zK&FmF-BNT1Wx{!&t|)8iIR3wyDCY)u^hGEUdQl-d7Gwbm$`0Qk|GVbWs&rH5UyofH z!}l#&v_ED|Wloz$&H<2&FpqKifP}pp8puVihtx3iJ(!L=>wBQpEi@oPasZ>U}Sm$Oa{47Pp zpqhRz08z4g`-7wf!fcn<1AaC#qc5bRr~i;^M%C*A~Uhm0)B=F}|3D9kgwW3X?H*LOI2^kk-cA>FUs`%+Y&0R?2T^7jcu{eN~Gksa= z)MmNwMQ7KVgk|l^u7lKrmv7*bgjikQ6{NKxKY?}Lj5&0L7=lrS5t$fUJ!(T;A7)ew zE24`FjjG}75i*thbrEqIOK_ErsyiR$EE9N=fomR{|&kz1ZjfTELj6u%$5_j zhrxRpQ%oMS1w6@^1H2QfyZEKi2A9Jo5vlaZ#S%8=lbtS#OO1vEtof z6J9{+y}Y z97LD;*_6j?)Zya~@=S!x@)w7ywGqz!e&~e9H@cJqfm9}bvzJL;4$3E~d ziv>~Dr`mk<=)lrL$CXn|cx>-5bYt0N$u`IRXx+gXGLh@e+$Kp;^@?Dw@4LX zEb*0s2?@GTmM9D$;w3@Q$P;2U-3~692?+S~RpQ4%-xvwt1k=7x?)m6d#yCQ9rrK9S z|2d%G7gI)4gi*H&vy6%KQhL@yEaiyEDLhP83!vwTmh>uEKemFi|D-RsSFGU|N}0^V zW&c!k;&ffxIGU{jtOa|08FPW@-wQ6!bOi~~N_`m9L5{o9b#9G^cDXRn#w*A@bE`e- z$un*pkIq_k2M89W$4B~B99Uq^%!DYjkOnzK$9AS#)vEnSEq)VoaIr}igwmy)%WMLa z_+pxL7F}su@j~yO$gW{~7Ls~H8UMLK=IZAFgNVg`uv03LkK@ypnM{7WD;1qjfX&*x z=H!R_E>D*jQQ%*Q5z{6rTTVkv$%|wF6%+m!g8qfma-0AXuE=fr&$tE1qeInM72N1z z{i>Y^bquTC^CVrLC|c>WnospwpZBDdmcTJSMgWZ^JdI)FLE}jYsNg3vc6;*2hV?nt z_Tdo$9j}>sI^KY*CHKLL_<+oYYY5RmP1akQk3?=;!F^(QcLP!`@+wm72iPp&;%o&{ z5sZXiY)$HN9-+X$9A_)Hm|~W7`S8YiJ8gre!B^H580$nbQf>5) zYiLN2)M;b8rh{gm0`bx zyEA8-!eN9LI~GuPQFBZtiq$a06+?u@^D4rnRIn1XqhlkUMwm(HVsc`&Igxk@P<9vt z%^+9U&n?dPU)%5dS&I>xhm2?2iF-8t4BF5fpx{ocCr9_w}*XIz1 z7ld?RvG!kW44EgJxM#l|!?j^pA`Hs&4m|uyQjcC6QUBT3%~uv`V&uK-w28?>SPZj} zfo$&}R{>6FppZNUZ2ZV-Gn_DFr(>{+u|I&~)*}(EmGY+?j@5Rr2krA>ygIMtRKR8mu&Xttxkt_j>esZGQt4`Yk}X73_)dF$BR?`@7?YHv%B z66|ka#cE?*MIA)4A35xN05(9$zZlFbXz3vFI$NlpH<5`-Y_rlOaN!Gu0S~d(-FtXROZ4CA{Ndo1iSjM|q%oDwi@<=+%DqZL zn56dkookmbY}_2amO{}D0eGu2%zdH>xBgTVDIAZ=Wx09;E6MEFbE>#vFd6Zr#@Xne zr`z{@ZpKap9$=-5+s9qw0nrhWhBn-?@4$n|qt+L9*#glmwg4OOAHV<;JAsc2d}LkB z#Uykim&s$z??-UnNrJCnDLBYlLjfEbjxKIpeJ6SKIsazG@ON_p8;HA`+Y9=99&OH! zgvs0%NGjJnzH0_JgmSAd>|!Bpt1EavJ(jBAXacu2S5wGeq2?_RXyT8fBOfe?4%qY` zl6_C@ISXciRNoKxzWA_suPu^huVbw8%~mu(ZAaX8AIEU^t@ATh05-fu*|`67`;zT) zhQE`=TSk`B1LfNMDgnEQG+un&~JFe7^Y&O+dU^0HcL=l&fuubzF8!dCw&2px3b(^u|jLa zMlS<1X*Icxn7wTUf9;Ei*($$8b4obSARHcG20a>uOkbu5kQr>@QH3+!tt!@ zymdX0T%GpLGhIpSDaAPfovqEszpd@mCETK7mYsfhSg^^Q1@M8y ze%}2c$Kn)-S9;*d8CTp7AY?e)87ye<#b%$NB1l(HE|{Ln);LT+f4O% z%sT(B_=KgJbmi<>v>D_ZDr7_El<|or&R8#dp{BA{UZ^}7k5eq7WoS|wf?MXxTZT8P zaKr-}!E{5;nspuU>}HLBM_Cr6mVTY7WH{X9&uR;s12dP%QG(;HZtMzo@Rj zE_*LYj+>D5-Siv7Jyh-#>S_llk$Xqu4Yf~ZB9iZbpIz4+lb}YP=!@o%GqbT1$PdzJ|-2|v~+x}MKK`&a$g2B5J zz!dV?RncLPC;4CB!(^*TF3jdV@RXifCOk@rXCO7t^KY@(-V1md1}KI_=3=sf-R=it zIAHe7ex4FJ9Mx}2#^M`?_c_#w9d@v1#d&iygXRu++Syn@+d>x;1FtG!kP~it_+H)L zyYE+03|kauJZXEVjwVl&{}K0Qn=B5=#1!ZzTfMwHFYi6AwWEWMbb!zhM{kfesbIZ; zH0>Mz0rk~(c~3-F8FSh~hc6fu)tphH9kt9zqRfJZyB28#tHbp|n(a>;l;DPq@T1Nc z194RrN0*Z%pR`v6f@n+gGR3KBbqry|xv-DbkoR{G zo)Q*VBypTyc^w^Q3 zS7P5HEf&&YPeaAX!mOd$>&>UrhzoBwjt2&3D5J6=(wE;x&xCwR@|wGBpEJgqMlH3L zd2vAGDy)P2%rxF44R=7H>E`Kn_4{X@qS!_cI}Ty?0i>K5t_LC5E6nI*!Vndv)Q=Y1 zneEr5(9vZVX2-STH*M)vzpZ3c6tnu&yU~7#+MkE8ebHGx0J3gaoNee&bk3xzrDhaJ z^T-q%?hHbqONkd1(bwgzub-!uhmIAt*qxYO3S$_}5%q&a{_EAo#Cdc14-_U_l^?j+ zD6^6sSZ(P=QmK?#WUnZf8staub&gMla?n!_5<~-~K=tYn)^20ONDYDzcqSF=UtRhw zo3Q0e)}NmWFpnFjX$~771Q4e_p&TLY*~8Ms>PS8SOq*}K52~GGOo?;I8~+-*K;UW; z%}ATY0qo(0S-OcQXCF2h-D~N?EleVu5$qa34GX8ImF)Y5&(bEq`M(nDMCqF^WwO7`O7vVd++*^U6{-frQ9AD1;2noCj}O5B@5&Sbj9tU;t{y zZG;D97-vLWUK>U9Va#6ctv||~@k?MRw4M+Nt&l8|{(EA*7 z4Hq#@rmy5zW)U##1?4TfjdVVU@Lb-cv((HU$`jR$L^QXv$9Fbh5$45Z-_cYMCqUM- z^%>V^jPF^UX*<9TYFYon=Gg<%bQ&(IV0MF|B(-`OV}umG3rp9E@JEY+@T^QWR((@o zrQ~Bx?#V+m9TWn5(eA7tW+K5}DxHGQCP8cJFAZ5TvcFfNnugWYJ8?pCVg3FsnS+wv z#~)cpPu(4|-j2jm+fyHGl#>UulU45%9hFr9dr8>hY4 z&M!JKrVVY^JU5Y@p zr2Sh!PDQc#MBR*K-$9DG71m2e-Ks#!!-A<8aL z`;S6xTU~l!Xi>4hEDIZu#YZ45R?{fX@PCKVoDr{7^8CR*YIa7y)Q)MgivOI$gr-a$ ze}zW;LR~%W$3AyADAgxs4)XnJb;Vb(wpm-25;iDT8NP(Ha zXQw_{QO=mMxuiN0PH7b)2hv&yO2-$SGuAd+g^2m405o5VYDO;h6e_0?5&H9p;y80N z?`iTjPx(7A@B2m*E4Qi!5*z67YDas>AjXp4H{rO$Z}GxXZ06qLSMY!`$6qNoZrGD);g9;=%Fe4;lF|6zp%Au}V+}h0<_!|m^MY1wXK0YMbK!!s zjIpK6J=l|ht(F|Tav;SPN;~&)RTF;7ycwSs+QvXw^WJ0cO8{GFzQ+v@X`vZq=g+)f zc7Nq`h2Z_jP+4|W%wet90dCw5z1t^F5+%wj>;S_u6rW4Mj_z5->=@>nM#NTG1avuv*kc!Dy=4W_tjU%&1z5jh*TQK$l4_jjkV)=mbFhyFa{W2M1*|^6)H(t`iy|n0(3%l>Wb{0JcY>A zuLPLZWi~|b{Ii*#7pmg00qUS4!ZWNpngmNws$Cm3HkkCarzuOuh#Idq6{&VVP%Mdc zHMtjWl75pFN(rcWJYk;r`2rCDzRZdZ#e-!pli!l(9qMY>eR(FpBUS; zBq`;FYiYueXd8s?V(HL6O(;Fxmjzl%(1*L-D^zn2j17;f1VM?HnyvHKy1WUJtYyv! zis|_T%XzTgDGitYr!5M0CRFmy(m%~Vxdr6N=R==N()i;gNfTTwDJ*{lRKsrD%<@!w z#D@KkJ3H}AET;t0dK6zT$ZDcA{orOM`n>ww(pD$xCIttRsgSeVzTb)tCX7gTJR2}Ym4qX=9vSU_LoJM1{<^D*oX!Px261#VM< z6bH7#`Tp@DOl-wAw@#wJNeTs1k}$oHQOL#0c%=GdJ?@|h-kX^J>#s08o|q!F2>tO{ zCy+3|tea9b9CA*=l3S%|&6>j+H*;Uol#2CD^uMNQhK8|c#k{Q&zX?rd5lG}n!1!)d z)iW48cb})suB{V*k{(6^|FL(^wV71Qq&-jEjB(uB9mnpJAbjI)uFB53|Y#e01b zu|OR#W7WOH;Wws1N~Qjsyl@kBz%5?Ig*=SDFxG2IhuXgyna4ZyW55nyYKXyJ5;9_D4rCaTLb}E`y|js)=YJKz+v3RPrsY02 z+H#_1q?uWjD8JH@6=(<~i83N|rSDbcMip}o;j_KC91pI@`(0A5^63|rWXAIZQ`hra zZatrjiiIW*th~QD^4_R)tV46MH712D@tbO}oS0Es7&Vq0^TzHkD2TI_Py0@ADZm*6 zNL3)Mr}eIe$5wIgjNGGaviIZ@xhB^~^@`WxT$ODBzYfkyQG9P>{h**M2a8L0CKg{8ayP=LMCmH`s0LV8PDl;3DG0JR zKWib|(YasUn>znF#f1pSXGT?|k!G7vG2_#hm6*F|Ab*+odsJAGOpFqPf9LWa<~AI; zRER?fP(a|d>fV@O6?W-=Y(-eqF2>ON;; z)R=0CeHor~9y=L&PY8D$=$aRo*+u21J?`v9m`6Z_G_?hyKZ^_)fc>)$S)WU5y^f=^G9kxkh=ZY6=<8Cs-s(EdJsTx98+D+w!!?%PIFCBnbsPw z?9PWcZegom3O}dMwoe;v{%A_Ic|Z=7#1KXe9iyeO<1UP|>N+g#6(5qym*aXa(_fBo z?deDcaxo3I0izwSJYH8>mt}HyIBAPNo#{8Wota^SL#syAbG z!Hwr*Fe1dMv@4fvqMiC*%fSgh{nTPdKgzc}R-wZzO`Wj@nI>7c91*!Ru}CB=wFG&I zeGVYm={K(n6L6@4T$Zy9MY2Zq6Bq9wrK`S(c~~`vqg0}liqXljjo|#m>&AGT%`0JG?+kOrlbN#NTe~0;<*HT9-$@l);R9A)^5XL?${}4_ zbkG{Wv1|H{?@=mI9U~fqLCpIwQJ0h1yXL(jfDp67J_BIvOdO3 z_F#Fmh0H7!Wxna`)hkxBM=04)K%&dj3DpQ*RznEbqw>e1&xrOG++=8$A`}aa!GwOp z8XZG43tlINz+AdX9ELL^(0{}b)Sv3}>oI53mt(=oI+vCEX%ELgxBE zg(=+@UDt({4k)47&b{!Pp4?Z$#_niY;F0cJeo`6py8EbK>oZi_a5sCTsR-7)1p#BBKm#|(75&`{m?@wX1OQAf`_zKKIqKZ$L*`oxFy7` zhy`3zr%l$kQQ+)F$wohrzLj= zX`Nb&kYZFMpyw-a;G#sAX^{Tr7TQu>7ZaO0e`zQ6_Lr_lXR@XxO_;&|Zt+Y3T_RpR z(wjaL#iQX!L#58Y4t!Al;iHuB%~oP|@wWeINO{T*zw!1^90Uu25o}kc{JUt1lmOWl z>h`y&{orRb8c=okyhhN^ds1g7vy0r|E=o&oNtMQiL&~6wkPSZuAVCLj&=@J+ep6=f zXa|#n!1)(4A~R^;=ZO699ew3Iw`gOaqx%t!^1?P=xUyouC~FSpJkLf+nmyuZXVaT~ zFkjsGXHjyPc@*2er0+#AF*vpVoY@8J~jC3Wt2l67_SaD;cNI)KH*BvKKs+b#nt`jgSroHa! z=E;q^o{d&FZ*Co0Zu17nb3KLn+X0Yd?1|x&*~={hE|1d&PN~bt-pmaTL8%Y+7KVD+ zHzI@saa*s6Z|EO;7)ADZTzQ^+x{WO1exP!vN#Rjk%B>* z!FZ%LV`3O!s-q&r>pxoVb|mjS9Ogw?vmQs`4k8ugyl<~IB+lGX{mn4-klS`YQyKmZMK?(4SoH5c&IqnvOc-{8zn1!e7 zB=aTu1h6Cc4G@6`6PvC@=$ZDJl&yqhKu%$r<x z1XLY9ockHq8*)VzFbR7BQ-1;gHQ4JI>Z1If^)&j(xKhqMNkC^)=V?k~iW&i`MIEw~ zGxeTEXxcgX zeB*x|u@wt78lizA!pxCp2@Uxd`)}9H(;=I{r_p;uZ$uyP(DFBHkn$#XJrtip;U|UI zwJpG8m(%G;Ne0K~&Mb=PGE%`{mFM#&1Ot_&;Ec&9W)V$%11pM8y8$~{4~Y`rreFZr ziSn_VZ+o@6nOdtWSgDOK{>_L=jSlq9H@~J$QRpq|iVi%35Ya=%Sa(h4W#F=krUW=P ziRv`qypu0}LF!X#WCQ5dt|^3awH$Erq;cSsdP+*p*(RPooXRz<_OFogS6F>(_alpY z>fAoBML(%9RHot!A|$WQ4qO~w+aG`NAsnN)8qF-w6k_3K+0)0U#sagBeF*Lw3|>G8J zC_|&h5msgF`SK8V62SVtlOV<|5v|O0NefJ>qlHjynp?WjVS|!;-<6ekzGtHSoNs{9 zQef4L%LpBD?4_)~67!SD)1~0_3ENfN4@&TP^Pod3pG*6~9&OWd#Ih3HvPv9M7u?Jf{{&D}wDrDwS}R_ahP> zYsVC@0{w;_7}{tzCnGzadLS2K z6HNzmN-A;WE$!y1&CICWkrwsz>4TOHtz+afWi%L=8(G98Cs<*X_XGp-$Tl14SCQAaqPhaFT>=V zD8@sUuf*Hpbs&^3@>VptFvkN2+AAmSCM2P_7u>S|r_|7`!yE#M68!rk9b1DPpdt~B z>MFMOZ7G>bG2m65^4&6}0%+;SO%iF;U>47F`FpY?ET~F!yqBVdF^jEe0Z38FuB_N| z7-12kIiL+eVybh!-T4OAd!aS_YGqIeNp?i(W}NlZhh?R9K!C@qouh&3hCY$3LHa+$ z!9g3G<32pK(Lo>xjph8-=gL)q5AesEgheQ=%e5{IYsP57{MfI6#?WM1FJ!WGJMn*; zXs4xXaNCBoJLv0dn-XfSkkRzT)ip6ly1sLXhA>z+=!L{6`5e}i>-Ufao}{fR2n2dn z{1wvC#~D5}nvqytQR70-)jPYX5NO1t=)Pg{nf)ey6d<2eYGr(U$vM~$n?i7QTHeJxyT%GUhXkn#u5hGtPmQTC{`Ch^Fh(>I+;k z0})}Nu9VCu5qnRMhmVNG{j~$Xy;>0HT}_xR&=?d+8K2?IYgZOmhr?F*eTgn5PHDaY z5OCVT!PX4+vxN+%5LW;|`@o{6JZ`xRZgF9oBa$Psje@iJ#UXaOI1&`pmswsyDH``( zWD-9F&Vnqvo#f?sPZpAp9iwsc!lhEyXMgRDvW^svBGNB!A!I)+&Z_6m?m;F|se*Qb z`vFx$ZOhpZUeN9DtobAI1O<2>J81<+dUJ7pKyYQo&)H#hV{xD2geG-w#gf5hI?Dw> zWzdh?`DDRdi5I>lC_)rB^pue=LpYz<0aU2_=_L)!J$0qeT2@JCK;<xPENfgExTEf>Lve6YZ=QbM$1Prv%}O+0MaFM( z%L(vdGAO-e$71_+9*dZn^Vxwg7wUK|!U@l{??t%p7m%kRP3nfeU9y|}Mk0>OS)1o@ zrp*BaO?oxSI!J0pK$nBF9dw@zQL=Od(Zh?GrYY{Uv(2(P$VUSZ%bPFE*c5S2;{TKS zO0M~pUdqLpg@`zlYNGO(SfWvG0wDuDOx8-X+59)$wvbh+ zD$`fz=^WV9YLN1bRb3sy-`i^;XJ0pZ#ml@cX$&he%r`~th=Uiz-hiV3yAFMz-X3)? zw0iQ?m<9zKSWzJ>ZCE+9hgvWBRoV`AFh}7tO=sq`22nE%MOcs3);YMxB)3iIk#~%c zv#7hI3=;Y@ooPbpr&%8d+8By47YY7ZartUChg7}j zJo;^>F)wR*%7V_7#V06*bkzh>EO5+8ohzew?md5fBUa)D?r#=ilTcELp7JA1#$r>> z%fAl03>yDsh1VN9`s@ZXOjiM`uvr$3eiBYqv!=qxW}DE0i3Pd4@4tW&VuIfuYOkdL zbdH{mNopmJ5SC0lrU9gK@n*W;JhYzv^j@Xam5YL_1EAEY>JgK^)LouTPCY#ad;hxS zZnbc>?%kAc)76T(lF}wRgK`%RF;@;p8ml2VMd5!?vlVcyJ|mtjGLk)VZL}3}Rx@_WrP)FvXKVzDY!nxF`rHcwLXb~;@1AI=!wfJR|wlOjC(VJNhz)-}8WU*w3eN@0$O|H1 z?z@d;4|YuhEw{pmB{3f*C8`>Uz}E?N*uhZRSXnJN{+mqV37&2G4H7#%MyL3QB!?T! zEJ4ni+M_90x_R1r_>+=gdP84jVL``|0oa^<5fl$z*PUTxH9qHf(p%vDNoN7nav|SN ziF?C9s&ncJJW#Vy7}h0YHT16l2*Yx42eV<;Wl$5LrtTNfhv-;GM7}Aj6K~k)_ucX^ zg=-b6Ivw-}leWjTmXf!f@EvZL1tPo4?*}fhV93|UV>-qI_iW!%FNLz}@47FKzkeF+e1Z*aALsEgpwyVWaxmO1`qW^nE=)9U z-RrNc>r`x4+6c4=7YzN)=To+``FpU#A0IbHC~;qnC?SH+nt)ZD;r|&xuI}dIk=DBL zGdqI3kW$LSnoC%i)O*Mc3uwUh=&xZ2(UrRu&}Dn~po;T;D1YajdKfhMl{BEh_t@hQ z=%mw7)!0Ku@GX zYPY?X@_L6NsHZo6T0#t)B8!$nLoF(V(rcut_B#^7T+S7qmG16iH*_v1DWUwkUOcm= zA8cgin_0J_rgNBoJDeC7XQCpx0Lh#fxavYgewhVi9H2&G%sU123|rfOSOZLBE?IAA8X`cc~PUDfy)D?>VZzk zY$9$(51luUzMt?03_M5JKyngC+@!B0mQ5Pu9W26B4w*}^d=(!w60x7)=Z+OO<;IKO zl{{JSw}D3cYYnow$-VSooD3&|v?X`RMreNm`~JH^)7=`|8z*jU*Knjn2+ZAldQP3#)EJx1-?i&Kv{i|ao<%Vfdxq3khRnZ0+Zf|2K8qTbaaSPJ00)|+< z^YNr#5J7UYj%eGAjay1lKF{fDso3Y*mr2yx?ita_lVgJVshRXD(uj|MbZ-X545F=4 z$`Ob}WIsyyDJh@w%QHgDlES9yr6vRXMi6g7CzDYrR`!ng3!&xK^%}tQm zR$xspm5C0-S9fz%gSRe2+c{%7BVi?&#`FM*Hnvq19=@L*j4kyR|1C3xSwCYdRg^RY zdOh68lbZ{HR{ynWu0!Sf@vj3cw&Du`1lZr6J%ZW!yBxd+1Lj!?X@)xi>SchoRq0xe zxXWRCAcR)#KA1dScyO3|$)EDTxb<}v=pOF|9Xh#T|EiIU&9J{RHtU>l*SS>-c;K*c z5>U-MeV;&1LFLijub8YB{|tE*mCQt$^T0$W2BEK-o~M;%FG}Viz?T7V3yVb9J1W66 z6WTz&D-E*1x7OV)niJmEe)op| za|6PoD>5vbgx+>S%ztOoSz#O3q#ThSfpuY(2L(`TEl{W!4n}&vG^lLt+le(ie zh2kY@T2}X6v2gpI>B#CEy^>P%Gh9(n4+_PRcRY3Zy(>H)%>- z_GU>aQ3GbT->#MLFz+DN>M5Khvh$%5?X7M~S&gxsn}IE@t^RQlUG0La-?vly8Qf#O zw@*Iv2boxdM*K-o(fpV<1^8nrj;#w=#@GSwMoQf@{&>589UZ`TX@#(|M#=E&4<$w4 zb|bu!cfV0aezHw7f-6%jxmFs40y4N+|o%yzC8ezIv z+GrO$`3?Kh+&FXVIbYh2!Vg1<_v1}G7!P)=_b?Z;2=LPr91a)t+sW?_=wEzO-)NMY z$NiPyV<5H5*b#_raTuPF*yzFtO}NYrcqUjxY#6^Njx# z^{Eab1M+Xv)F$>XY3FKAI|^Q}8r-}k-|Qv0O7RkhRzZux9^>yIR3jc7TH5|2(yY2+ ze24*cM?_nY*}5G?X2G`6OS&NZn{n0MDgFL4D32D!yCQD)pss3#uh88VC<{zEy*NaF z08d*pl=Zx@j9C1Ao=KI>Arsd~yW73CP*C6Wd&=+fpxR62bwb_wf}fTk`BO&O{l4aY zyqMSC^~iDHtFf0oJdB1%gyiku>?mGwc7ObP8n+&#n#A=q|8f3XnK$R6>>m*h%*akC zej8xgp9dq-xJQX|!M9CHtE{jnpz$r6Yo=v#{B!elD!wt(XhI-Wp@Y<-axz9#P};F6 z3`~xAI$vl52-RX!t3>%(_q2V5KkinvMfuFKD%oWP(}XFSw#mX5a7u>R*7x$swLpf2 z`wa>2N!~I(|2(|1mad;RCm(ugS4Rbv0^D^Ba)Ni?h-f-nFKnB@;7$3p0*n1HTd$cd z1ii-7WW|(LbyjU1TGiU1D4>oKI@j`c*(<)ZQNWbea{3&za1k4~E_sb}Jw>@Q2uxB3lBqn^+-EjiSytr$n;LmBoVO$ZYcN~eQ9-Xf0q#Zik1Tu#Ww3&5U5RB zuOE=v1TqPYm*)g57X;G2))ZTK6)%SS#xT53_KFMGV%dz`%RU^Zycel{D__C6A;3)v zZa6dzJD(DyX{-UrdZpL}%88N_O2qIVVhcAy26(qG*42(p()o_gkR&~dlw|p}K+ii5 zdYk4Q(}+;u7n(>z@1qiik|(>$+3%Z%s^cSrb&~-F7HKn&D7S)-%LrG+id;od6C?@? zs30V@@7*bUzj(L$%nGC3!Geze+%pd-MWa=xj8yM7Kf7m4FyC4S&8SWx9sSA%!L@NV z3Z5F!A*v*O+z_eZ#9i+8Iw!21G9wWdn9HAQ**=_to4dK6su%RfvI#OVQ!x`3qG}eA zL*y0qGe?jT6&p?$z|6nES=*0CV+1B=ulR2z7Dg!M*(58t-!=oxCF-V<5})8)VVpwI zoV~@M2I=@KnBzp{reqPoR2(#Kn@G_dQNugi7NUTkKEO3~(`q}#?~FrhIT8i~5g^YS zK`{%-`0|>r;R=s3V;`nV5rPV2y;`!QQI)51p%u=uy{!4bKUvWFMwCq5EZ>HscP^?& z2Fe4y+dq-_VV!`tUI(nQBdY{ocULnKZ7mwxrW-=@l1J-XuKY`xeDf5fM&1_aVExuIN z=C$D$Ft>hX1$iH}4@<+$^_AW~^8IJ?eak#%>9dFnp5&+QqvP`GY8E{;!*JD}2#sOBH6KBDnDB}HYmU``55+?y=zk{I$=uRpeGFvrFE z{-lz#EyGR`m+=EEEow$37(tE;kMOkWo1B(pGROR|>7smsjZ5h2ro4Co-5R8yFW?Dh z;;yrQsbu3+hYt_$3&x!4SaDS=G-jC1PvFfLEH|!;q5U1m^VM+p66{6vuBx4RXBq3q zQqR}?laN+u2-3r>rk31D(Npz9^IpkyxGogg#=3%@FT>aohj7nk}+_F3rX+fK9MuFa$%c z$_NbLHWfw;I+KPyv~QIX{jhPv#6y zW!98g@lyl>Mxd~*L-c5Zaa&k5W>kQ?uNGwi8-g!!zazx_ECWw zb{T?tPN7x2r>CarS9U@wd_kL}$#z&@K*8Vtudb*J0;y|5_b8eQVXcZN^x1U~FB`+C zG9YF8kHd&dsPi4yZFMn#_?IEtkV~9we46DTD0xfqkkIiO+dtVFT91@KT|9rz|7N+E&b?LgJ#kR*{4Xe=|2#_P z;Cz7I1ejrfz{v1@vl(13g_`=zp~Io+Px7Z|>zz~bQ+&jcvK>_w9{g+qgx()i#WduB z_09DdB8K>b5H^Lb=-AJi_~I7Qx1wmqNSW4$HZ2wxdcmwF&m=?e!geGpwL0sCcNswQ z&a@cb*=dLlHWd!Sv_BMw&0$MVF=>p5U9eog`0SSW(d8d{LcMh-ZPAgcJ^f|>>Di{r z1Nwf+SYo~aQQVjN{El`vk*H93$FRq^V?*LESW=1?gi2bi7ecCH6<)jb8QA-IS_N1} zOqzWVY}oM9v=?M;FL72|;Xci2n|jZyoO6I3L;%7~@&0B5!3jg$2v< zW{&+J{Ril|FS-t}0Aodz(0v?PTT?$-TT^8af>ItS<_8G=>%h7+G7D9zx_|0L%QPw# zANK-b@tLjRHSeHVq&vejH4xtKwkIDz2)ZN-UcX3yb82qV;_iYM@uZIodi|1k0RulY zI+&q&_ozuoAQRb0{FPB+0PiYRB-I5|2SH z9-F9O?ygU%4Y1jxjQN%9F|9h^XhSVae-U%GTb$>K6G60TT&dc$fW9Gk-2(i}+!H)- z&ev1?!xxQ!_vk_Z;%{Z{;4n!OZC}-*@f+RN(>31=utIthKjp$ZAnA8i8m&npBf%}$ z%$4l=Hv=PHU+TYP7;5=a^Qki7BKe*>*%wY&Yb2Z$`s~G%IC^9w2CQH8$JhX>{)s!W zdk-rJxh?RZ*#&@$r3p%(w9IBSz8+Hjm=u|a>b~P(S3o9P(jCFAiSg;)E8Uvc2F7v? zA4OS}G7dbVh+W@J5tU)L+F5Jhk3|*akJ`tH(T@4}D0S^u{tq82nmt5Aw|6{~hBaH1 ziS443f|-Kj3M^VOIft!>JA4^`Mha!4>BtOWXrp=BZMd)x5?6ifwQBhNJ3tAzxaDl` z>EoYJ4TL+8_8&`+WSbZsI*=~k84A-ax`Beyp@VYJ~5<-(M zl+U}cbE`r?cueBTGWDJNxv8VBIsJ&q$3D=A(=L^Hr`y-)dT_`;Tvv#gFiaq}sm4yM zx-_X8chh}LJi^EX?W4f#6q0#8q({fUiW}rp%9WHG=+LO{V%`)`z0L{-H&)95XzZ36e z%7M|%ZD2Ms35bVNRS0thV6R5=TTBBKlYFtbOW)h+%Wg&0gW>?M0hyfI6J(Y8o4n~x z9P%KB=e^v?TW%pInlW8@KJP`P>&8t-%knm$J$Ir7ehQKB>I>sIz=x@L(2BjA0BQfk zN4`2My=g}5a^<2YsVPVqE`(X_4V4vIMsED0V8rkW$zlLDDMn=H;Phi#A38QOq z1{x?ADfvM$zJ&s3BD17iVvWE zoH*@y8hv=1eQIL}qUuQK=6h!ockDfeaY3iVOpH*UqzSa6=Q;w20Fu!FI~7Lw-@T=K z95&KoCuzXDDK`UJFfl*QrBQWhLB)M*Ee?BhCxx4(E zNMezLz)K)CGYy;HbC(LQrdHKrOQNPuG$WY+M?kp0L(tY|JfFaVBtK~t=SL{a_WU+t zZNGqJ=5)<(%4gq&Hkqfu`e+g`-g?3%z=K%VD-MLjuqEDW68NpP!%nl-n2J5R_OE^1w28D%76pdE`Hi7&LUW_V+R20FSu)=o*gU`346fI? z)jZLNbu5bPUG5F_Cp+}Zls?SIrhJ)Wr7fl*B{z`nqqtwQ^D!tK{Mcab`F|=Dk=gr} zxG&-~rR@v|=Oq@vrcpsYUN6g&G z{wqFU<tX7B>SkK%9^I|18k`VF9Rx)YY&mI$n zq|;Fw&^%eW22owJKXKmmK``tV6|jeMoKN!2PYr>BqrCxC+@H%vn45xZW%+|cH7a+< z)pj#ewazwZvUvabvr)I1sNiOs z)u58;CjnZ$1vNls0E5;jN8?0^QQt}&no5n^27Xo31)owQm<&wYt8_j(&7KmqWI?u^ zjHQ}wu%K$OF&&Z{KqI=P__CtB<@)QD$P}^YQ~FnWY-E}yrt-@fn=!;27za!OMC(KM zD+5aZZ_d{ggNy*)+EMfxwXByjIC2IZa z?y|K$km-}0%BU8y|Gsl1XYdZG6f>*E;#iR3Vf-QE^u%lKKJ?{11IY|;)hq^k-x7;& z;T1@F%9@`$BJyAiTIJ%2W?4rIK)kV4BIvQ5ge6pa>W`{VZ4P2G&jA+Msf_q5R%_c@ z96TpWeG!@M03(9b3ZZA3w_BcXKnirO&*ozJjHy3f#bzXZwjuqe#0j8&k_c+xUYBHX zdOtSfOGFXEKH-4dEi&fWS;lrK&MO0~xL0^A$H{_0@E9EM_>iAoyTZ4{Eei5!+v1WJ z;k$McsZglP0{0xfl&soJZQqV8NkFR`vf2m8{8&FWCdld{@609KrZTIV=A6`jU_QQ@ zk?VGOY^0Q5^@@d1#PJi!PwZts-noh=7FlXInq69U&yupQI!~sPky5O}h=(HY*|0=M z>}ys+>jr))l-7Y>SHq$YRffau9Y4E1MB8xH-Tu7_q z3Gkpy!xstZxb&?ZouEldDwE3tt#qZP&6rFfa zBds0KrrkKYU2nAM-YLcbCO#w%%H_OO*py4;Q{h~b2@?(#mR^^TK+BJXavPiEYSnlKm7c5vSzvJgK#QGq)M;K@^qqd(K(iHwht3M$P9o*Jq=u z;oV^)eI1O^L+Pl=t8pZ};JBKu*xK?Vftg!UqVd~eJmKty6xQKjB-tbW05#_&D+Th?oLw#ZjKFR}#% zs%pkWQAdO;lX-&a@w*3(mpme^(%Emx3?YMs4QTFPuI23)@|*W0esNGZ-*>jAoJ;6| z(}ZS~JtW$W=d*Z{wlW-fDgRi9VET+I_$wbQNu1*;GVnmTo7;yF3X#$cByCjq@TTus zjGbvwFZ@?yE6d9qP!)_8t0OlZ8QXIHWOP1a)LiKKK##58kH9e^}s9SCLVBdpVt4`TU()T!FX{ZRFLNhj@BQfQ_!~Z zsa!&DFX`icd*1w_n(XV_En>I@Zy+g0GpiSCnQE-gSedj_gADyavhv$VhUrlvi!vZ% zQX&~lm;1(f{}ZwN%DcJl^%C>Ofl2G{RGq3qz_kkPi^PRbA_I zZ*3m2^*-)LFS+~fLB-o}fb~{4_RPcwX=E)+1J=V*)mFaF)J=I90l2euXFK&j*m8|x zjz~O0snQ_BOe#pu7%>455KCn4V&)ZM2B4m1$O-{x0!m>mAo%f!v7SS$;*lJ@8ijmS zp!Ch(X0`QZ%yhCwb4?-tfjl~5n_?@o3njUR+m>{B`?sW01SwKnb16UOhz ze72JSTdP$-G$SUfH+xHG4Xg#T+%$oJCxAV~>WPAv7uvhxc+)p7g&n99(ATo6qUQ)4 zBkgM4_>^$Twen{SI9>t*FxmZ&7(aPf*!G`@?m8Vq}kp`6DGPoDz>GoJ9N1sV%Ih z6j?_=&>N;IhE=-THJEo09C0oF*qoxXE?9Y2K;eeZhOz^Ja$*t0f0Wo>_)kO?d8gf9 zir&BcN=(R$5@>@=P1K$YsOn>y+ulwTt1FyJXZ7t;Z2x%wXLt(K(^WVT2g+ifq}Fk> za$Fw`&`C`+v;dZwpKOD^l6bNx3#p>GC}1YYO>EFSdDDqGklc7I{q&OrOE~Xe@l(el z-VT&~L^$m>p_n;w2jII*HCGlQk zaCtj{4aB?$N>^ln^8tf19QX{e!2qA9S=4quPKRJq6~{*UoEP^I#|m)&@rV8omURI@ z-IVV;gsdydt~s!2M7Bc*l7?8*u!ps|9_w1#`}5%w5FV=|5?6?57Lu;5J}o&GZBU9T zILU1wyQuszU=O^xYjn2U{L*#@b;sUz(6G{ z&WWe!0APvDwf@XRcHHXbIkQ$3q*jZ=FBhh;gQZG_ltD|Z>1ep$Eo`K>9I3h+Cc*?} zyofw6)lfJ1Z!}z~7kuHCpOX@o+mH6sntP$*04BR>_z~Y-U=sFfak2d9H!3WwWUTe7 zTYFDvybf;%-yO~?)u?Vtlf+|V?lf}Cb-=`wwwC7pk~8m!q^Mh}kBd(K7y;a8*hB;hQhR-ZETI!b8C_wn$a zIq?O-wu5V&-B^cEMW*oZiEd)-hn2If(WCZLs`2Qbi9IM{F-C99|`jyaVd|$E;Wv zr!Ss7P-If|gzTpBN6xUF8Qg;=i@S(CrXl4=>>9BBLt&`i9-EXh`b_a~8!jw5mV zN%R&Q5wWcwCWs>p0eXA?KqYIC)1mehX!(npSiQ0kv&bQ==P{Zu7QB@#pe+BzdL9L0 z9kJfGOt2t|d@W7(mADL@ytTo@)_4$F0=_hMe5FHAKDru|0R|(&iaPLZ%^g;s*i#jg zl{k;WCk(q5Nh)FA?E#}_YmYD7X&R5#5>bn>BtyyfKSbwK$G)HciWTM&+C90!o!*aK zoszx&=^J)XQ~t#Z3ZWFJNLUyPu_@ccuPb^p3~8OMTHJff`+mX_2|ySs1#X*kp87%rUuy0RKWKsrXzp*!2`oe7{+8(9r|VTK&WVq!ynio0HUCU2 z6zTQa1&;0bFrYk^p=R|-p*G^H7i-a}#TiJglo`H0Wa_gVr0vje z^d>khZ6D~;-qWCIBv1#F1V%Q3h;IJ82COrdfHjC`9yjwomUluw7A+te%DzN@-qWEc(U@T)#`zw0dz33)I| z7)v#LYi>YebA^A7dsn+@$o~#t0Xv(r!$AW&cCJyoF$3nnh&cI%bz&2#;(gU8u<9|* z@kl=UY5?Pp_wQdH`}it>R0gD~CzL2##WykzIMA3XtN6hv-m?#Z&(1TMx(PnUMy0b2 z@-FB!o1$@F1Fr*H{Kl-esDVOSuBMLMGi{l=NUH>kh}v{$^hB>Cl5>;6ulChH?UPxj z8$XQhE6g&IO_L~D)yx860&106+p9{3h-9kK2*32PZYGHi2MSkqJ$11!&>f>1y{ArC z3{zfR-$$CVv?7MP~UG zZP6_xJH;GNUL#TlR7#kAu~eKhGp6i=9|peBtcR(}@Vo-tNL4L+T|CIuY;LlzVngLp zqHP>g$Ol2mj{jUC?s2=uOTU1-jDexH zZ_AF1#q;z!`Cnkf+Dex9bZ0=G>puNwzs$-vAvk%x4!sryw zfTkN4O+01X1NG)5^TM-G`5;YczaWQKRU^QX7~vK65WN>`@_xOn=(lEi(XYbKuP)B| zEOLV8ZWn=nb;H!rear{D%oCAn?-EkOLnXmfdi@IqWOF2c%w?fLl2ZY*T%q((5=y;| z;Ru-(UMmBJ(-21mZV+=$W%@4*;S~2iT?!fUIYtso)M5Pd=*_aS2oH&a9jlk2Njd<0Eg|0xuIM@&G^^d z=&P&%UrA?(T^sQY%$?;$;T+b&cn5L{f+9n|s5 zJ5Z%if6q=ZjzHsl61p7MWv&I=^q5k1)_8LIA*U7Ov4TUL(P#;OObX`h{$8IixMUN)bfU0Y04(j%n+BKduMn*cRBQnk`U2M5^dz1|OWc$E;jqtuDo)7ti!5kve-<-TaUvN4;Y2^M=8AQYm z+NWQTK-b{LjE=l9VxtW^ZFn+0O+~?4@L{89?qN`0hA1OB%sS^gRc0_q(7a08n0EJ1 z-abmnwrM@cQ+kmvK)pd*@dYd;Ygo%`XcpBYl9@KRBfO&732MXKfLOVsIc7%I_n)}| zdi<0qhpYnu`?$WoD!nPl7XdE^c%}x950`N$BmTyG3h92NAa&>I|bjo zjIPeXr4LDxl~mi<*Sm~InecrQJ#8OHBW43pX{(264S$YViTc;j^r@_ZKZUp;7Y?R_t3My$z6Y+QIe~to$G5O3inJybY>yxBV?ClK3Prr%ZIaH zSr5>-&+5>dd-f21V@;%xQ(|&{eDF{uNYQ*gx-=j}svyGnfGvZT0|fp=m%$(>yYa8k zUro14Ei)AKM7;lUn0y*^dzyUUyI9LxcMaYNF67eUmusAIF#$YJKv!uDZdVA-L&|3d z2y-PzPK2igXsSv~+HY@^wea3)Yvcon{+Yo%=)mh*j=sba*Tm2B1nQ~S3=asawWl#z zCe!|~YzeZ!}_l?3_lR-bM1c}LV#YVXnIS$D#9bE{N}_Dxmj z9+MGB8uqD7`<})MQtn*dRE*Xvd`HpODqx)o@qQWzg1&h&h#JD$BYl+I(Vh+QD1js^ z%6VqrPDCR}G$WPu7tB&RTkp%>Y`S5SScR~RO{FMJYbE6cNNk2@AxQU6N<*v7XGpfs zdO7 zI>U;W9SF%cV$Kq*3PUPaU=c|_%>Owkg}yiwtjX?T9%xqX-+_)ZdZDx`#Od_!Q~gG& zlkvp5ap}7qU_?2!YpHw(DYjVy2U*3T+PD9=Y7RZW`I?-^n1_KEoN9wo^8CXr+&k4JvvZ$+(GY5~V_ z8Q25c*|I(cqi0-eUKuFOAe^b#AF#@6!38f$1|1uv$f&~HLlU!O`l(pmW9)ri&zI(7 zC|4*a@e>fxBu*95M)XwK6`X6`A7;9ciIhrW>wI;E&LKAY!Id7hUPQc$3nH1%{mo&-2CvttYs8BMP7 z9S)yO73Z&pK}?!8OTg#Dj$}Lx>2_VhidUL;=1PaTqq=IFVn}w;E`R$`0*t)#6<8_g z^abgnNFVl<(3%Vmq6UDEe}Z`vupn!vslL9~txSm0ZG}L&2GD7UML%_W)a2OZ%)$2Q zi&q&&EMiPLs+k|&ro9M*+QCOcd&h9_w}^|w{BiSLN{i$dBN@Lf`7Cm|a9WwEC%8zpW`ld7ffzKFauBSD<9Cp}x3mpe24ic|LPtWwoE z^t`cGCB$KH}KXU9=|HY9NpiI021V!idsGp;Oa>@R`}YyJt6%tY5} z7&!ZRA3h&=_DCe4`Wpr+sNbCB5pkU?qP(&?1no!MTX9PqOp52w5JH|5kBi0(>!-he z_Iq9JD7?Tg$D(fjIP7Jp%;VF2y_a8po`n>I6uhVw3hm_ifNmJ{Iw(KKaJPcl;agt*0hr6^x0R0VvQ zBnw;#R1HOwS*kw6Le$7~t|tg|y*TP}B55i*PfcPkNCW7vzIbN2vuuh>=N}JUivL5R zonP79#3p{6qX*h)U*vL2+W@NN(~C3UEahDeIvUWBDcw}($`o0vhVR2@afe4=xS*0T zS~UddA17n}&TnUQK=bPI(^_D7=chmwF^8O)Nl9M@8m0oep_^&Jz%nKVKLdG)73iOr zD*=MSC$2Q8Gq4PvXyhBw_`o5i61pE{u1_7Lr^SX!t?nv`U2styxP4dE;Ak=?ueS}U zOyHv-hB%r(wy$n1+O7{{Z(=wNlEC-VIBb8Y)=;}AxAt*IOg(F~RtEHow$;awb>#CR z`iR~V?qW7eWO-(~9ZHx3h{A#q<*Hb+2lSyXKJNgmPDsB{sa`}Jw)Z2beA;`V4{_fP zv-?y3h+qqEq*Y$tDB21RoYq1;ZR$fb~$s`QJ)m0+Z2Y5yPt#Re=~7+e0iLt&7on?1f)G5r8-KXdt6MG>`& zhlD;IGh<66O=^eBf52`NKr1Z(dhU~u0)x#)vi|5XU$dEl`C|y3B^vMDf|Cj{5@`SqBo)Lv1n=urntHbU^a9s$NB5(Yvy zFWUtVET`Y;rDar*_3g)2&aJ*Cse4boq(A-Swzx)`sYkic=x=np)-+# zDV*?cumFcwtkvQ3@nA5AeK(NNJL;3-O(-E?BAz9Foxvt)cC$b%EOAFcWG_+{66nCx zpuOCj(A2gq?PDz%Oh}C*k-#ky*3P6Le@SCBDB9rY@j~R0f;Y`Tf!YyQjIXbnlxa<; z6DH1m9P>8-7F;9`3H^87Wj_1K*z*ITorK0WuiJhfJV7h>QlaLA-bTuUHgy8YlLBTp zqq4j9_O!*CtMU@M(WNNivzutnp&5eVapqE%s5{B(Q9-o6T!9uFt?jc!iV4$Q`v)`% zJlYV(^ohsGD;-XtgD+B~TUEDzTdQxP52GxN9UrlQyTC3Wl7cyL@d{<~56z=jFvyRo za<9l+=AveWRP%5-f%TgVUT+tvlL@Lzbbfnh4#kC~bY9`Q*vhaMJBo=2*8S3Fxn?GF z#;5aWTq&;VWAfa%r>+7;pZwMxn8*TUq+Aci1P-cpeiVz3!J9u@ORsLX%f+w#5Temm z$J{l1jsfKCmUqT#Yy>1SSbdstRzXLHhx6#6xX=cid1sQ&H}Ygl?#LiQ&$x_wQR7Pc zsQsNPqO1x`CZSTJ*^HEys^nic%q5x5%bwt z%1xO$JS5Lu^MNBIg57 zjHE8Dx0`6SxZf=D-6^6y5zy|WtVZMCwIWsSyK^vFUwJ`79dq+#r=&N#45bGP2QKV+ z0~KXF-ONSF4%8Bc3VN;bxZR50T*{Clh>MV^zv*s6J`P7AsT!OuPY6!_htQn{Our3@ z6(1^kD7ib&$vIdE5N~fuSgD9F?>*r^wuyV{QJhtz)0B`P6%GNh#=+h^=eenq)76Hb zeiE*--tRa+8HY;i^jXk2qT3<@y3N@z)!F|gM(o`iD64F86e^$D?R+G2)3cw-6+ zC_n5P6aOq2jf1O8zKFU5gOO=>aH{T=Jadopb3JFt;d}nQu!1N6B{p(SDeg%X1|ZSy zBoq)OfL%^jMoLLnVayAIXyteutr%J2GP?Tg;tA|XpJ;jB){F-jqV<#Pct}MAhqNaL z)MZx)GD~n-7sB`)I(Il5k(*QD1);R`IrwB?j!tFFM}T3z|B{+YLpBnouHfsWI^GO# zjnj1_vB($@rRt+=nt6t&^52fKIlt*zPg$c=CJ}Ag@^|K8qj}|cPNQw281B(aMK`fi zcm(l_{!Z-eLdVe^`5acDeR;$H+#jMCeN7;dv>Th;6?`ubBmyM`W`1?*QkyT8$3|2A zXf19JlBaoEpL7BdAa5%)2VD25!FW7T+EQDctypztf_sVmCRm9J;mdUc#c{SQH{Q|i zCtwz(QZy<^>0hp?*@_xLG;#Vo%zU`$_8zzOP9Ri#kvJsAas4r9tLS8r5y-oRTrPLz zZ;vFX$r<}&i(FUVGT9RMe8zYZ2OgN9UPo&bGVM$u`AI5XFf>25pTg}j9PSVOFtuJt zp%jsEf*OwWa92b#=ijZYy8d!*@!I?c5il6A{-$ZI^85w=jf{aUk?^cLms zh=pcQTT_&CA=NYEeN$4H500;gW~2p+g!{^6@X+eZQsNHU6F0X=wyXeuUEHoNb{_Fu zy(M*CpeNQoOz0&(R$b46di)Kd1xD@0;_p2E@Uhv})5Ff>B_@`1XH4I96MP-enT1^o zcldT~#RxF7*wcn$wJ=>G*_umJ@D008sL7i5V{)bV%no(W3nfMC=+EXt<4IIEY;j@d zFrK(>WJRCq@az(ftt7wXTp%?oI(D-u-x0Ufr{QPNO>oWpA)z(20acaaM~&1umXI&6 zq;e$=X2$Q7i9;bkyR4$@BDXMLusp8c5v@SU)1OHi>|Evm^0mqvHe2+7aLG zT~4J1T(dkTKwhl;>M#9C_Po#_U}@f9A4pj&;KhCWR`c(=@r9Zvf%D19oRubv$}pUa<7+e*__jiFL($?PR%{QgmWs^@4&k?+I#p!+k{ni{Mib zn7hxZ>%T_5O?@}!o__x`qIeFS`U4zqDlc%u#pH_``j=Kyh-|)IrX@xnmISmu3Zcd) zm-f2VM5=)&QvP@@ijNgDrX0YI==wjQs(_skbWM5q)0p?7XbXOOJvZ1DY1tjUU)N*T zIy4Zmt3;=)c9Zr06XVW_i|7$J#ME-86+F<~qul!S-QvDJFo^ncuCh4MB4bJeC^NaD zK2)cA6EB4lD}@z%egL>oVk|y57acj>wA05J^+@7gFFfe9Wy+^j@MO%&m$o=u_-dFV6R!|;>jTZOMPQ3u^xfL4Q}4+g+ak}fiB^# z{(V>{?p*7x>G0B2^ZKS~V9ZhYm2Y~(LJ|A6re{Md=PYyvLzrJ19riIsX&HY?GvKLl zAJQIM*J3S$WK$X8UjGuifJ5Hsiyijx4 zq>P{3PlQsQbx-!}X9H&Z0`t zkoUC8;+u($6J}Jd7M?p-;3_ir<8`IHH3qFMqD!vBv0l)gjr;Nf`F5E0DLOtDbVHr+ zF`stdX<VhHN}DG8!^Q;kIm3P3XZ0C4_!%b^R}eEn#mcW5W(?CcW*ma!d4zk*YwXBdI8ErHkkh{qS@t%q7vZwM| zPd$!FFUhK=-Fkq;iRYn`&sISO5%D8D-2+olF2DCR%wzyX%36Jc`CYHjMbIp$&o|Gy zm_uP8UgG&ZkzH*?td4PtYm<(*Ti|E{?>{^{0p3n|tB75VQOz>eW?(yFaytoUsF-#q z*TmWqR`QL{#$iJu=Tu&U3c%7BzttdTNiYmPp2jjYa`IdHP`dyb@^@3vCFRuYBy}X_pfv zeBQGmNv~GcO6{2-P(o{A;GyM#bRDu6t3WOL{yrIIf|@hmA*EFqJ8l^ zVtR(Yk%cM3LB1_5S@>cRCEamI&;#^XD14w%=GV{DzCI8BawcF`dDCtI7$dHPc9&M* zwq@yLIb%r@*@21)fO29vaZM@YCVNWYJxUoqA^#aR+vFL7Ayy!fzh;!~y#$WbA||tk zE14p_r`;V`(ae4!ZqgC@Cc-*bKNsb#7tz1lVW|e>M{6YGYS7F@w{91jnHLBcUY?6K z-f#GSl_2a!;WbCbJ-sv8r>1KmF_YAz(uO0v03OJbb7z>-#TzGQUsuI@ZUVL;t%;> z75rWuEeLa=mah99o~T@#gT6`;q?Qru4uq-HIf{P~-)*2{lT}g@?N+zUMc@2o#77PZ zBTmJvS0Esz=m*-{o{0ot_X{kM`EE#z0AS+>I0@UNVq3a_|6ZMo!vpN<#Uc_xoroxpbkAr0MU&@pY*Gn%B^LW3G%!ty*Am>%VMU(jx z)nw#NiW%*%93c^}_=p z=dyoKtt?9%2n6T))xjDK`!Qpml6*4l^adg0?9C`)YSnkT0>Qh{*7 z#Bl9@w&|$JKtHon;gonhx{1*}OL^|#|H*~qq6z@T8>3b-i=$BQl_I~OiLJ<{_8p7< zz@Xt8FE`*K2i7wT#>cE}n%1vbTOd5Ji7%%Y)5_yT_vn)xLCxIXqEV>5tvn_rmVxy? zfBz#&+jlFEjY0|>+DpHAHJK^iO8i{;WE@}8SLVL@HNpC)L)tKD)Sw2uoW0jQX3ZefKoT-hKHJtdlhVjy3%V|OK`6d2Waht) zfu|VhGK*IX@G#oxFI@uYjf({UPo0t1BQF8G-0ai?iGB#{;U1)yfc*pEq`J+>=0PnY z;o7psO9d8&ie~w`nURg)wcjxpq4_SNgXepB4?gHcjGXC9LE&@n6Fp-DM>!{Jo?P0r z!i5AQl1S#}QzwPCI4*yQ^n{*=Ses7>`Z4IR**xU?*BcAVh0d&F!!9`e7JTDlecnIz z|9A)ba@5BMe9D^t3X&TZTqX&MgtSaT6RhG+!;g`kU}Nh_m2pTy<}#{xGyNrX}y#s255RlTBtdt+$O0{p7S zrrHS{oOC*{UuS6s?ksS{(qOs`^N>&6YrxLmeWjep&{G<1bg^kbopAbXsHg%URns+P z>U_0i(`i`6pn`T-9I0WPS?46jm>@T)a~!dTT&<)`M@`hJ!lwb0ClCXF-jNp!i$zY3 zF&j|5S}>Xv{avqknc^fQMsx$~^C(7`v-w7#HY`Nc$Ev5tv!B3@_vzz*tK|X+5ER;6 zY@wN^{1?naRxH5&h>-voyM|<_xj?Tj672_$cMP^laKP}XWOpwi1tDcDD!_-7*@J+6 zXI12}gL$aWH$2wr)a(P_s zvBrE4BqC+^5T6+!5!BT25$k6wG~qR=O=d>o&Fgyfa{~<&$tcJ;T3fU=9?FN2r+a0z9xghlv|o9_2vi2Y0+ZogDby(VB0BIlk*li|v&VJ;N~7}3zY^j!ZHfHBO; z--gMadQIO#VmJ$p3p^R-aDaLCPMMxcUn%XViXp!2dgNA(S{?|P6_jz7QDpbH3jr45 zJMtcWtW)@zQZtiz+n}ySscC94B-dEC_o{s-?ADz3iy150OdiYTsy@Yufy;9{lzDP~ zIJ41fp#y%%>h3+}C!RL`J{ZnVBICm|=`z}m1ua8EPPeZ?#;;D2?l~D~_%!R_e@l*f z2xi~{9joxllz_bu8LSV!p5>bDtCXieyj;#ByZg<15@noTnQ;^`M6{@F&rmq2oTR7c zDrifk@S@TU^zsSV`~;ss(T$0R!3Jg82%E=6Wwkjvd0bXOY4!FD9yo_FYV%dPctN>8 z3RW49dTxRWg>EM7|0&~9C3ZBzImMx^yDO^+>mpwRQ^7lWEQ888_N87U?^_EhlmMLJ zDws&zO3TjZNHJ-|UPYyy!_{b*#o zNPdLA6YdKT8eBFbp=rcJt2E zg%937&ksCd;R*g@7<2mIaha4|Koi}{vFh(DQb}V6OK3{aRpvjjK0}S+e2ZI0Ctfor zxXz#DqoWXStLAWJW%%_e<-v|~7U+t_Y!{7#vBj10ri>VsP^S|0&Yv4W#Z8C+A2!O} zmEh$37pwzupv(rsd2BZ3fF z=L|dM$O*5&AUFja@#$0in$w-%KSaV*?rkABPSkg*oMc#Tn22V&_O_!>B5)G3mR5$V z`D}?_z*b_Qta%3;i`^ehRORXs;CBY;Fjz9Vm+>V@-?-uj2c!=QM?MnlJ*;Rurf%M4 zZs?<@T=af%jS`6EJRo8+c<(fgd6xG)Os0()2-oWM7waH>Pu%&e`nlx#@M&PT zf8nL2(bBZrgAsX@W5bP=NqyM?FJ=mc6?VaQF_9j5Qw!+!%S-=8f3!o3y%c2KzKy5j z(UeYlJUPljsx)UpAP@(#=##uep*9j%(AZrYl8_{7jDqK-l3b(~Amyo6c&LO98|%ME z|9hTOuM$agS&0H*x%CQ?y&|NG$*py+w%C075>i&3y9Ecq+shP;@b0~A5NR4rk=3_*Gd^j|NM8eq+-HRp={fk>tH2ilU>ue=x)lQ;_b~k9+Tk`#o^wvSMw=cf5I zC?l8#1Px9A{#Q#g-z1~m!P6#GY%-Ky3lxO0L?q4eO!Ta75Lg?0NGHw0cjnv?WuEhV zVI_#1VbqCM!rBS8Axv%hj}G@RyR_UN2V^GVK5gCI_vDmhnZM+h)vvH9M{h~}jj}kz zjvi}-SH2PVR4@`NzZK0&*LlwftMY-LZOl00Z^?E=7q);KdEJ00@N4rm$?>Yu{*}f6 z15?!5X~fe6pb{haA~pV*ERQ?{iIbt$u%RuyfU@HxAeQX5*|DR`3+bU+#fr%y;~VP9 z?7%s-w8I!dDYNY~i!QLCMNSPWo_b(~)CY^M1Pw`S0hRHYA%ag~jBGc1Zl94e?==~- z0;0_wO`@g9P?*3yORp4J>n@x^!EcF#6VqC1(2IyrA=!9$9a^n3o7)#ypPOjF7mSJ*+9cNyT=qr5B_dx0bglljTuEspP6> zSPJcIYG4nb7T~n`(qsq}Mx2ntvM{c1MArtu4ntoyR&4TXEWos0N~wB8?@I&_ zm0F4(;z#kR^IJSz4th_>8SQuS#M(HyQ^q$kqu&J$SE23#wfcG|hbyg>MtIniG$VF< zXwF>7tIbXyIL@L{bvBC_4!$|5;4Y;e7|(;g4qSWrbXH@*@fSA)hJHZ6>g+)keb9jj z&?!KAXGPU?hja^hu$;T5JnsWb=OE1jN@CJAAgm6*74@eJT9lipW?}AP6z`5d`hCgV z8&cl#ZHKs%gIDj;E3$~i!fOrLoB#(JcVN91+5>uC(yEubz0Y^P)6D zai`l@LMR3p*{|9WKM!)Qv@*tj`e)bAL(CTY4}sBazi5CjV8-}Syt5)aiNhTM@E8@M zuV1#zYLMa`8S@bHQrKLFmGTlfDmCQir;D?QKO1!DtA)8$&bk@q09jl`0lY<)shn-K z`g%1?!JO?GX&cO#jnot)+Yp5Vm_)Gq+sBV{sQR6{ri^2DBe+q>tymi z1=W=YhCFBC(LopOr*M!fJ=5V#{7=c+`=QiLwkEVum|yDj-&X5%58yLgIxpp-5H^3p z56B{~n~(U(n^}4cP=9XR%u_IJ9mkMrm`d5-R;u~?;3mqpj;!$zTE{W+bbP$XQ~!?s z)<~Y#?M?VwEMj8ZHB(;)RX2fgJjsNo_j+;I{zU)yH5hsKf@M67y@w8%lG!btFZ6=) zHY9gQ+nPjO?tZHZH{L($F$ z=sl_fQh1Qvv|NJV3q+g+{B7P1O`nG$|CWw@IQZPaGW23=JF$Lx8w3Gzu=2m7OrI##5Uds=&JcB%#`)H9tY z9herTuN5+V9POcih&pm&S|^slCCI##3uD;6!8ZfPY@yZk5=L z<6H_cMMlh<{28aB75BZsagr|YH!0y+kJ%R58)V77;~U0w-5>U9{+7X^V@K{1clLHV ze!FjD<9v=P=@LTJkEE7emGj0QS|l{jjG05Jg$tP%r*|nM^%IJv^kQ0bf(Ntll~K1Vp+JikJZT7 z+>M_wbU0WvYfZ<6y=HcFz=Xu^C$qybOxp9W&wXlY@b%2$a9&V=$bp5ykc2AMITj2h^qo>uvX|rI%fZ~U5jO40D1YKMBLkp9D|M+lb&TM(3G+3;BjF}Gw-bK>c>3r z=Kxz8x*I8>s5WE_;q?Z~Apv`cimMi-Wd%$4cLAoZX>Ovf*9BQvx5wxkDZlMe=%8~-Vou<@HjC` z`91wcu-n=x;<>qmax|es_!m4^yK9dWxYyLEI##Z?Z z5-hVW#64z%32@Vn_gBM7uw4 zfqWqU0mi73GG^!^ej6fxn=rU9_hlMPmcui25?-q%!LSTcboINeKHA=Xx+t3%A3zk` zx>!gqP#Jb#tRp^3yx5qVrmt?&23ysbfVvin50!(}`FafjQtOi%3@u1#(=xYbtKN>6 zxJs@NZwaQ419!z?Nh*@wuCpxfx%LuAgFB?QG!&2<{}@W3Rjthmt$iJODGKzB4DLPd zg(qGCdr!kMWN{a&y#DD-Z63M4ZJmiX(rInVXG2;S;j>@>4;zs6L`q6UavSyF(7hDa z-2)AT!@mu}MCepD)(~U6T%%=+K-1KNXriI%F28D|&qqQ1A>S503@c$p;(qSKvC?WT z{vy|B4GQXEL6`cW$=wp)h|h4-rZrRmDTL-O0s`ZSl$Z%b?!xYpD8ldIApnjgI)Jkn zU`GUst+^o&KTSUd}C zd5h$f)^L!OYU-h2dKGn>*>kAZjd$4nj-ZHXa^kv&$5eC89MctwuUi*L4L9eoBojh$ zP&9SpSyDf14xU(ce@IU-VY~n{-n7y95A(Kv#&v_WLzHX&M^#z`i^Moms%#N?mx{C@ zUm+lTXZX@jC7b4H_cZcxab^b|Z)5c+`lq!KEJ5L1CV!LPM)?Hs+Y94FUNkxlkHQqS z{V`nm2LX1YlcX+P*%cxoyd1=|LEwM$zI%h26meSerKeYip7I7(wkf$Dnf|(L_HU|6 z!(E-O=u;Z6!M@v2)zJW2y}~XUjkEI`+1=bWq{a1l?Y^G7bp@Sa1y(X1r`7g9nYha1 z@-^qjGe_4*6)+$7Qn@dqU3q9-NprAYJ@vI&RESRP!!T@csQdm2Ox_rb2=!)~ox(cT z+e^-lgCiJp2#ueQxqjRi`sL>BS6Z@CDQ{0JtdQ^5T+J zF4giP6Pj5Zyazm2Y*4$JdSD`uH5K}A1oK@tdpy2i zYuif*xbX%Got(59) zDJ!)^{cTYRHxkIuvq?9umRUkP6CT~%f+Qmq52;ciiprnw@RHAUXsn(cOp<1N_VXG> zW){}meMpJHda$#MJ(kQA+$g*NoKXSFkmXQt_tR}w{Vu`hB>gt7HN&60?0-~a?z3XT z=nVMn`fKOHvKps*H(0o#4(j|;P!`-5Jkwhp!2r*llWG2fAA#@nj-EoAbn^G-;-#>k7cO_Gehy-U`O1D`%v#yu0UFZTj3o&SC5T ztntca{d=d++kjCv%BpFZ0r(@)Sf0(<@?>9piz zok>htz$jQ1eT}5tm6B_U8B^9XPZNA`%AUrU;WX24&28AkK;uidvug$T_9Vz>vfQQD zzfo(*(;RK*4=v}@fB)(Q`n_FDq-E?f=y%%-ix7=kg366yS=%PqQs_8_CT<=DO0}Vy zdLPnyy*Lbn`}Rx8S}4#F)U$ei?%CM`*5+_9pYYZsQX=h5#bcdO^0TuL?jmHLnH63F zE$;5RG^e-#x^O_LUc&vK)OnQoG|fHkNFv!RWnceY%I#lPz(-RqlcgGor$$fgRS^0} z2KgPz>wR#<%11lr?`7$p5~?P%sl+EwseI)u-|;$#H9WWLh#B?MD%WC&GY`c)7SH)^ zTZL%?Qi_O}V&=dAUmI#VGa4IY33F(r%=1>(k7iamysQe|7Ohgd&0D*U0xn zC@uebZ3<6eG#$GW0Uv~$y;U^rfPitxmDy5(y1ALDO?A-Baq@X*a0MfgwLR?LUAPH{IMa!fzr!IK>;Ldbvp=1gr@yK%6RQL0aKTGGqM(_mb5^ zF1~5@Nj)THK|w9byc2xJ4;b~KvO zcmyMrb3|ku2rH`(m#%06@>H2=NI8kr5iTZE{xLVP>2`H}A^m$r#hW6~EP^r$e563g zJ(nw+KAOpx>;>mi1+6WuWFw6)Jy9w>M4RABsCX&%Bn?TzxcO-1e8UJx|NVohPflw>vlfPRat~;2VqpwDri;IGLbaC$;m=vMy{POO4733!isi1c=nkSvu49#P6*Ix_$~D0 z^sC52Mjh*DQ-FZYs@Si@1rn4x&|}u?CmVgKPRzoAk_?NEUADja*s&LJVV3EjKwfMy zV#r3JjIwQ4kSLh10j9=|y>n}KslFFZ@uG%pBqpA*%146^zBd#@8hN&`01R4Te!wEU3?n*8wxc4*S*|ipwdX) z4S9!wfdrmWNmRU64*4iH<>kmP$OYKi0NZur zBAZMpcI1J)jBI2v+hteooM%g%Gvw3svq3nf7ASg}-vdfnE0`=HgQMw+P`%N($&IKv zw7ltCjW06O=k~*AdMC5Yc`}kCai6|yr09E8Gvm9^p)^)XD?)4^v$=hJ33`Tw#c+`U zB3P~05VHk}SHhfV4X+}+J5_mMNN%bMfe5__+Qw;_G*c;TYZbgRQdpVzfMO1r#`1qN z^7KP1gCmI6axVriT~-#<0UY#$x=^P{tow>noQNIXqZG?0(W#HZP&x{2xD6<7?ZEc` zE+cruHSwYi6a>$Q_pNlIu;2=d08ff2nM1}?x)AJw;Ioa(e33>3zpIyaYuB4?T|*r%er2>vi{bBL1(3Z`tX1}hgO6qpIPUTLND7e9{}S0Y-mlNB zFj|ut;J=9(XO;FAMQ3xS5NqxG=`A+`PL)_wP@{ED?tX0f-vpBew0O1W_fGg**83wT z1*op{`uIAQ!=N5()_{dK14Gh<3x8+~NFRt)>$2Y(u8)Gu<(Kf=p+YW?znxY#yMGW? zYQ7JWDb)U_io^!k#>>$}t-dD!8^Od~Z$f4%ODXY@?1eSrWNM+|I>$0HJdCP2F&hf;)vK3SKL3#|f*W>@M}@Bskv zO`)EnPSV9Ad}fl=m#A4QVqt8OrMO%k2c0BgM1bcKSPP9Dm0kv+$ZQ4=#X=xoX6WAj z(S>2U?$!0J$vDO6-!p`Ar0^pX+Z1wRx)zp@RHVJr{8+c}76uTPoRWFJ=UB z8YUg$ldtQ!9)I{5_|M0yip-WM)IK2r02teJQ40Ob`N-JV-u<@#kdiIn=ti@#l>=3F zPa|Ol1WGnns4_yb7iZW>QQepY%t@9svdO0UiQHxQ?^?rA+rlx<9Yqwe`Zq8o4x_uyKjamORnl_g%C}wj9?^Qf|U>1nHum{HL zNmCy4Xq?;OH+D>VjH1b=Z_bl7$sI>JQ=qkLDhly`WXSaZ&%@77T`uDWMtGm%8mryI zXp)yOcT-dva=w+7EUQfKuLiL~hkVrBm6rgJ&)z*zO&$E7 zlMY@meZ1r-T^=m<3mj5v$=!b(emvmKiB{-}LH~OD&8d$#Wv{^R(=>N-Jl{)%TcrDG zhd<24U;_;iB<1?BE`cH8E-?hp5+tn~MK+QV{$-V7p^WvB^e5G_^+er!C0<3$#vWo- z)Pve>qY#Uk%;~9VVn+b_J&xYbW;yGu4=)?Ei0)FxGiH01;z*0g`#NS=DuBF)b{gM1 z?o_-~gfNyfSr8_{rt%S?w@nCt)WykzfEeAN4|vfB+g>Our(TCb&TB~+|C_)~H41*+gf9?&3B^CmBS=own5;!M}|o5k@1QvKzrF4`C@>oOFOCH5%v8eRu^> z0LG%-bxifHVgy4-ovDZOFeUpW-PrXCj=be8)Op36L9UbIT6gtLw2EGpvM88$_P1)4>0K^2aFUPrR@#jEvQp5))wZk(6FQdWfZ2kE|srcfX+x{TZvY6{*D z_o4jstOMnkRmt6xF6M|qk*{X`4(qs#*@XryQO*(%d5T41wlA;yB)C2F!JN1vq)U5L zOG~+7`_J~j0xy96skW}LaZetl`8ET8w)z9AOE)CGpW*_t~ zZf@!x3noZ@Fddk#M~GNo00mtHXgW zV&A(NPu97RyixQi*<5u1ew)ZnidK8lFyt%SjP>UhQnKLQ*t_-QT&b^Y z{`m}ELNCX@pAIAtGKzfb<(M~yDf$I3rAN>DS#%01&&NhXI=;06%iZ>&vYRye`|DDo zpPiZqaj4v+>TV&Pi5QV^sN%(WI|>6{iXSXD9a$nL37o|%l6?5Dt9h#SbGjWU%PQq^ zbsqbM2kZl1VKFg-*B0P>1fX&c?dO5)2X!^Czzj4s9!k6IOWvAIyhcy^f!Tg=4EKX+XjA zIbInn1@kZdS`A);L3tMOEN>L0>-hgSYlAUcvmTzJSE9w58uq!$h%z~|0(>Jf?GZuw z>(M6xa=XEi1?+iTe>q5c{jKAHenU+woB7y#CplWDYq9+0yZ7GM11|AN*~{E`&?NbV zLpiqHP1CQY9&5R;u)?1BWqQ@%b(c_^`McPy8{J{fd4JW!C6ixNPBWfRmGH&2gGR6s zSIVudwW0-n>($8};N2wnjfSUA-_oO#WLUZhJIpjAIs_;4#APN#=k}Qd)U2zDgk1Ev z#^z5;FI<>O{Tr#zk4U&)GvprzkI1k)BjPG$az2yA+{)0uU6@w81!K2|GIb3@N1^C1 z+U7gV@G6db3xo8?TV?@Y-3ofyGLEK+k@1I3IX3fD9BG6>k^cOGd?mJ!>41P^p{p%gk+!d0PoXJrBmw8>SO}_dpW&|1 zm?zQPP&kz7pZC`^bn1c0p&u!k|8{P=xkZDWfZT|%(w^&e^0GlMh{n)cT}H*9q@tVYeH4?O z1{aQ1tI#_5GGGAa7ck@yd-d_Y5ccKE-SxyS#x*RZ7K}NI!XIe+MW1cGA-dia9RN>> zfcA_?f4JsOjn~G@)B(QXj6mYXR^Cx5ALMXR0a?@M8d4=<+iAJhRE1tSgbI{Q;BiVD z0Ijd3h|r1pfx*df?~+(Jg%NC43JkJr|DzMknEvLms3FEuTDS7Zxq?0AqerRzfL@3? z?k;-9jd>8;cc%pWg-Ng&ovoHV0&Wc|wSc-Y?eYzJ`eI}09EaG_YtGoEqPhn&=A(c9P)@#7|BEDc{^N)-rnhP!pSOV6i-^RK6 z-;A6w^<0Q2N?IuS4O;O4RLc4YVUI{He1+IX!t^LA8G?VtbaeQk$~10bjiO-j^6R_! z=U7KEo6l~ro9VOaaVAjarQ{WXP9uh|+ipt^G2J;7ts0V%CU81kI&}Ye0@u<2oSz`S zpTAqmMxBYRGy2M!a2LTd*?GLjEyFE>uY9y$O$4+`p;@JIhd$TKASL0(+t7Ff_3hpc z$^BVEn0v)d`raVMvLS1m1VqLHV$$$hpt0yd(}vSi{qLOdb!WV!j@!eV`@3pFIci%> zI@^`Eu(B{2V_vrdf8lzJlK-^Ov^uxEPwAzQZnCx4g#mV+@#}Qj+EhC!`Ha(?Exl{O z<#DQECWSwERqp|<>oQ5W(C)`qN#$&zOt#94R=>3bjT{hK>d6T8n!=fBto z&<9E%l2K=Sqs&mN7GK4=;ofVZTR`M7rln^Gn#9X!Iw5*e0__S1UXFl{Ubx;9r9e2( zd1-f4=jqh&VwMtW)`QM(|7>n)gRi6qty@beKLQsaF@!f+kB>Y;( z9W=z2SkbtIi!s0b7XnzjWl*_vj9McqUsez^%?53hh9b!-dV{n0BUhr(!}E=z*nV`@ zWySyl3t$7VDREOFW9Xl5Q=U(Z&kIB3K?xWDF&htDybXt-N3$nTTz~%ppo#ePST`9o zV2B=MRW&(q#X$6ormkH-g;aVcM#oxZV-}EH_!Uz5wiAuKLtnVVlhsmuR}ckFtB1d( zk?Jwu_w%+uE#fDq+$hiR*8AjC=<%G0C3DFc~!9% zNII6SHTth`&XCUYel-y(6`2TA%eny{{SV?%KovVWzf$IGz zmQ~Jy8QE9gXf2bx&It=k`pB+Wr{)CpsWV?MesH}jjP7A#;LQb;?G8qOO0)P^5LwQR z)Fwg)pcJr1RpyK%WsSD%kfBs(j1)p_vYBk}^@i^?Hm(HLcfyrl%{v)*o45&lVM}OP zzIPPsE*ZCn+#(&@RgPhInHW~GX9}rYWBVD?kocqyMtB>GND=V~U`&vgDYcxtsE!C& znY=!3C`d}aR;`@at*k0>!Fu{*BNt#}r&R0`)HyPw1m2eL0fAzRvyRbIm4r=*rGr&n z4z9MBSrX+xZro8S;Sl`nE>6{J1^X`$PzD{B3rq*?k1xpv$i_FU%>oW z7cFosr3-v@qnr6czBbdhG09YXgk#a5RSKXlay%qv;pC#v0x>dO9i^%-KdT&O6wdx4KN!54WW_WY8m=4D)ZUr;hEXzGu+FBCKKRoUu0$EpMo^P`}UTEhh zvHYnWDo`C_5a{GyE`!Yq#SKLIS5W1W@(|Inm$p#H;>*Bi3HbFyad5EBrm4%fgLf7ZUmLJ|75K4A{{ zSQym*Z+mH5`)*&rd(T=ei^uEdK25EOTE*I{YatXd_u9uyoi56XN|h!$DBg>1sunzL z%RU2<#e}UdloK!@fyVfE$So(peu?7i_*-nh0}E)vA*AvE=EmWV!1+v}2nfFQgN18u zh?IQ$m^SsuU4$JBCGU66CB2tF_S8@0L?G+`Ef2h+o4TcS6-@n606bTo%uRP+(Z(X7 zHCESzdgWqfj3~d9lgx&d%bf}G!>;-YnxK%kdA33Iq3jg_jEN|`+?X$7dTjEJrmBn3 z2pUg@hj8b-(SPByG?4gUO^oST+g&=HMv^$g-u`CPYMbq*>r@TSRN~gNNDw<8T|aKm z9jw8Nrh+g%z<)QPy)d(>2V2%6*ekTM28^0Bz_0HbBXH-|M=E$UG%ma|FLqrGwFkTI zycFo<2Fuv|YmYN{&Xp9k;CBv>LAH;%6z-$*SljB8_J)Sgp)cstg@1GvYoLh&%~aaF zoNkI6_hdvcQL07)LEJe8ZC)#^%f5adF@8x|cf-(Z0NF-CGFAjS*EW@f%lWPH4bf{( zToH@P$|v2vJOB)mK?u9;zx8c2>`?;gD;@ZC+~be~f&-K8?bRTrpJ;PxKU>zs!fOOrtrhVLfhy>pYh#f$v}C@jp?ZIBTQPP++I^=h* z2S6XDr=y{QwzjC1xw8FuWi$&aVeA3~GIO{v~s)_9>Ek8qBezSN^-qT>YTzUb| z1LVx%2qj{tXG|xi&&m>;jq!Nq`5IRZcJAM}DGr8bj=ESO8;f6%0^7(aE_zf-Q-g;C zmK$#85JyN_w&*P++TbwZC7z8zxP)Wn6ms4-rIwQDbzrv|OyWeH#$%$j(OIO!R((9` zd(0A!wr^okmeuN36~HoM4rumTHzG>Rn&uaG#A=+iZpP=h^w2+9GO#EeNI)5KQ57g}*y!7aLmDZ8}v z7vdJ@h@nU6!|-}B*1_^AnW$Icf9Tjl65YU%{bfH|Zccl>Qj@OoFO-S5H>bU3A(}0p z;>d%~K`Qh%EDvCf>!$RzB{%wYGvsY9KnN8U!InlA&)+X-#_IdTbUHpcmoWIy@0`sn zw1)D^bp_I`y5SrrOjT{EO;lQ-fS3i~t!4cq z7gES}TBK!$jqI{$k4o9%((wT<;)?7qt~Z%0Af`2hwTk{Z;*npyRQpT#fK4H-tTY99 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

KIQsy%+6$0DQsQ_H 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

w#b%3vCe3aDaHuLHtU9x~(a5%MszS%U^I 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 Date: Fri, 29 Mar 2024 15:52:55 -0400 Subject: [PATCH 15/21] Optimize sql queries in asset permission check --- dandiapi/api/views/asset.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dandiapi/api/views/asset.py b/dandiapi/api/views/asset.py index a8fe74f4f..4595b9ebd 100644 --- a/dandiapi/api/views/asset.py +++ b/dandiapi/api/views/asset.py @@ -30,7 +30,6 @@ from django_filters import rest_framework as filters from drf_yasg.utils import swagger_auto_schema from guardian.decorators import permission_required_or_403 -from guardian.shortcuts import get_objects_for_user from rest_framework import serializers, status from rest_framework.decorators import action from rest_framework.exceptions import NotAuthenticated, NotFound, PermissionDenied @@ -41,6 +40,7 @@ from dandiapi.api.models import Asset, AssetBlob, Dandiset, Version from dandiapi.api.models.asset import validate_asset_path +from dandiapi.api.models.dandiset import DandisetUserObjectPermission from dandiapi.api.views.common import ( ASSET_ID_PARAM, VERSIONS_DANDISET_PK_PARAM, @@ -84,8 +84,7 @@ def raise_if_unauthorized(self): # user has ownership asset_id = self.kwargs.get('asset_id') if asset_id is not None: - asset = get_object_or_404(Asset, asset_id=asset_id) - + asset = get_object_or_404(Asset.objects.select_related('blob'), asset_id=asset_id) # TODO: When EmbargoedZarrArchive is implemented, check that as well if asset.blob and asset.blob.embargoed: if not self.request.user.is_authenticated: @@ -94,9 +93,12 @@ def raise_if_unauthorized(self): # User must be an owner on any of the dandisets this asset belongs to asset_dandisets = Dandiset.objects.filter(versions__in=asset.versions.all()) - user_dandisets = get_objects_for_user(self.request.user, 'owner', Dandiset) - intersection = asset_dandisets & user_dandisets - if not intersection.exists(): + asset_dandisets_owned_by_user = DandisetUserObjectPermission.objects.filter( + content_object__in=asset_dandisets, + user=self.request.user, + permission__codename='owner', + ) + if not asset_dandisets_owned_by_user.exists(): raise PermissionDenied def get_queryset(self): From adb06ce30d883baeb2eb94aeec7bf1e6f339d527 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 4 Apr 2024 12:37:16 -0400 Subject: [PATCH 16/21] Handle orphaned embargoed asset blobs in migration --- .../migrations/0008_migrate_embargoed_data.py | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/dandiapi/api/migrations/0008_migrate_embargoed_data.py b/dandiapi/api/migrations/0008_migrate_embargoed_data.py index d0ad94b2d..ba5c85c37 100644 --- a/dandiapi/api/migrations/0008_migrate_embargoed_data.py +++ b/dandiapi/api/migrations/0008_migrate_embargoed_data.py @@ -7,13 +7,9 @@ def migrate_embargoed_asset_blobs(apps, _): Asset = apps.get_model('api.Asset') AssetBlob = apps.get_model('api.AssetBlob') - embargoed_assets = Asset.objects.filter(embargoed_blob__isnull=False).select_related( - 'embargoed_blob' - ) + EmbargoedAssetBlob = apps.get_model('api.EmbargoedAssetBlob') - # For each relevant asset, create a new asset blob with embargoed=True, - # and point the asset to that - for asset in embargoed_assets.iterator(): + def migrate_embargoed_blob(embargoed_blob): # Check if the blob we care about already exists (possibly under a different blob_id due to # de-duplication). # This will handle the following cases: @@ -27,34 +23,42 @@ def migrate_embargoed_asset_blobs(apps, _): # # In case #3, the asset will effectively be un-embargoed. existing_blob = AssetBlob.objects.filter( - etag=asset.embargoed_blob.etag, size=asset.embargoed_blob.size + etag=embargoed_blob.etag, size=embargoed_blob.size ).first() if existing_blob: - asset.blob = existing_blob - asset.embargoed_blob = None - asset.save() - continue + return existing_blob - # This asset's blob hasn't been transitioned yet - blob_id = str(asset.embargoed_blob.blob_id) + blob_id = str(embargoed_blob.blob_id) new_blob_location = f'blobs/{blob_id[0:3]}/{blob_id[3:6]}/{blob_id}' - new_asset_blob = AssetBlob.objects.create( + return AssetBlob.objects.create( embargoed=True, blob=new_blob_location, - blob_id=asset.embargoed_blob.blob_id, - created=asset.embargoed_blob.created, - modified=asset.embargoed_blob.modified, - sha256=asset.embargoed_blob.sha256, - etag=asset.embargoed_blob.etag, - size=asset.embargoed_blob.size, - download_count=asset.embargoed_blob.download_count, + blob_id=embargoed_blob.blob_id, + created=embargoed_blob.created, + modified=embargoed_blob.modified, + sha256=embargoed_blob.sha256, + etag=embargoed_blob.etag, + size=embargoed_blob.size, + download_count=embargoed_blob.download_count, ) - asset.blob = new_asset_blob + + # For each relevant asset, create a new asset blob with embargoed=True, + # and point the asset to that + embargoed_assets = Asset.objects.filter(embargoed_blob__isnull=False).select_related( + 'embargoed_blob' + ) + for asset in embargoed_assets.iterator(): + asset.blob = migrate_embargoed_blob(asset.embargoed_blob) asset.embargoed_blob = None asset.save() - assert not Asset.objects.filter(embargoed_blob__isnull=False).exists() # noqa: S101 + # Finally, handle orphaned EmbargoedAssetBlobs. Since we've already taken care of all assets + # that point to embargoed blobs, we can migrate the remaining embargoed blobs without issue. + embargoed_blobs = EmbargoedAssetBlob.objects.iterator() + for embargoed_blob in embargoed_blobs: + migrate_embargoed_blob(embargoed_blob) + class Migration(migrations.Migration): dependencies = [ From 52e191096f12328d6a01bf179ca974332216e1e9 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 4 Apr 2024 12:46:36 -0400 Subject: [PATCH 17/21] Sum download counts of to-merge asset blobs --- dandiapi/api/migrations/0008_migrate_embargoed_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dandiapi/api/migrations/0008_migrate_embargoed_data.py b/dandiapi/api/migrations/0008_migrate_embargoed_data.py index ba5c85c37..13943aeaf 100644 --- a/dandiapi/api/migrations/0008_migrate_embargoed_data.py +++ b/dandiapi/api/migrations/0008_migrate_embargoed_data.py @@ -26,6 +26,8 @@ def migrate_embargoed_blob(embargoed_blob): etag=embargoed_blob.etag, size=embargoed_blob.size ).first() if existing_blob: + existing_blob.download_count += embargoed_blob.download_count + existing_blob.save() return existing_blob blob_id = str(embargoed_blob.blob_id) From 976aba99d3651badaff7ec1696d72d8f15348c6b Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 18 Apr 2024 13:56:09 -0400 Subject: [PATCH 18/21] Fix invalid parameter type --- dandiapi/api/storage.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dandiapi/api/storage.py b/dandiapi/api/storage.py index 6477bed14..dcaa517ea 100644 --- a/dandiapi/api/storage.py +++ b/dandiapi/api/storage.py @@ -85,7 +85,8 @@ def _create_upload_id(self, object_key: str, content_type: str, tagging: str | N Key=object_key, ContentType=content_type, ACL='bucket-owner-full-control', - Tagging=tagging, + # The param to create_multipart_upload must be a string + Tagging=tagging or '', ) return resp['UploadId'] From afde2258b17aa963e47565176e157e36541865eb Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 25 Apr 2024 14:27:20 -0400 Subject: [PATCH 19/21] Send email to dandiset owners once un-embargo is complete --- dandiapi/api/mail.py | 29 +++++++++++++++++++ .../api/mail/dandiset_unembargoed.html | 4 +++ 2 files changed, 33 insertions(+) create mode 100644 dandiapi/api/templates/api/mail/dandiset_unembargoed.html diff --git a/dandiapi/api/mail.py b/dandiapi/api/mail.py index 46904bc06..f895988a4 100644 --- a/dandiapi/api/mail.py +++ b/dandiapi/api/mail.py @@ -6,6 +6,7 @@ from django.conf import settings from django.core import mail from django.template.loader import render_to_string +from django.utils.html import strip_tags if TYPE_CHECKING: from collections.abc import Iterable @@ -204,3 +205,31 @@ def send_dandisets_to_unembargo_message(dandisets: Iterable[Dandiset]): messages = [build_dandisets_to_unembargo_message(dandisets)] with mail.get_connection() as connection: connection.send_messages(messages) + + +def build_dandiset_unembargoed_message(dandiset: Dandiset): + dandiset_context = { + 'identifier': dandiset.identifier, + 'ui_link': f'{settings.DANDI_WEB_APP_URL}/dandiset/{dandiset.identifier}', + } + + render_context = { + **BASE_RENDER_CONTEXT, + 'dandiset': dandiset_context, + } + html_message = render_to_string('api/mail/dandiset_unembargoed.html', render_context) + return build_message( + subject='Your Dandiset has been un-embargoed!', + message=strip_tags(html_message), + html_message=html_message, + to=[owner.email for owner in dandiset.owners], + ) + + +def send_dandiset_unembargoed_message(dandiset: Dandiset): + logger.info( + 'Sending dandisets un-embargoed message to dandiset %s owners.', dandiset.identifier + ) + messages = [build_dandiset_unembargoed_message(dandiset)] + with mail.get_connection() as connection: + connection.send_messages(messages) diff --git a/dandiapi/api/templates/api/mail/dandiset_unembargoed.html b/dandiapi/api/templates/api/mail/dandiset_unembargoed.html new file mode 100644 index 000000000..978dea42f --- /dev/null +++ b/dandiapi/api/templates/api/mail/dandiset_unembargoed.html @@ -0,0 +1,4 @@ +Dandiset {{ dandiset.identifier }} has been un-embargoed, and is now open for +public access. +

+You are receiving this email because you are an owner of this dandiset. From e14f626d08d46432dcc02cc7e33671ec07d213a5 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 25 Apr 2024 16:51:18 -0400 Subject: [PATCH 20/21] Add dev email setting --- .github/workflows/backend-ci.yml | 1 + .github/workflows/frontend-ci.yml | 2 ++ dandiapi/api/mail.py | 6 +++--- dandiapi/settings.py | 1 + dev/.env.docker-compose | 1 + dev/.env.docker-compose-native | 1 + tox.ini | 2 ++ 7 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 9701e2c6e..f5f186059 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -58,4 +58,5 @@ jobs: DJANGO_DANDI_WEB_APP_URL: http://localhost:8085 DJANGO_DANDI_API_URL: http://localhost:8000 DJANGO_DANDI_JUPYTERHUB_URL: https://hub.dandiarchive.org/ + DJANGO_DANDI_DEV_EMAIL: test@example.com DANDI_ALLOW_LOCALHOST_URLS: 1 diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index ac3779841..8ec1e9ac7 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -66,6 +66,7 @@ jobs: DJANGO_DANDI_WEB_APP_URL: http://localhost:8085 DJANGO_DANDI_API_URL: http://localhost:8000 DJANGO_DANDI_JUPYTERHUB_URL: https://hub.dandiarchive.org/ + DJANGO_DANDI_DEV_EMAIL: test@example.com DANDI_ALLOW_LOCALHOST_URLS: 1 # Web client env vars @@ -177,6 +178,7 @@ jobs: DJANGO_DANDI_WEB_APP_URL: http://localhost:8085 DJANGO_DANDI_API_URL: http://localhost:8000 DJANGO_DANDI_JUPYTERHUB_URL: https://hub.dandiarchive.org/ + DJANGO_DANDI_DEV_EMAIL: test@example.com DANDI_ALLOW_LOCALHOST_URLS: 1 # Web client env vars diff --git a/dandiapi/api/mail.py b/dandiapi/api/mail.py index f895988a4..9f05acdb5 100644 --- a/dandiapi/api/mail.py +++ b/dandiapi/api/mail.py @@ -194,14 +194,14 @@ def build_dandisets_to_unembargo_message(dandisets: Iterable[Dandiset]): ] render_context = {**BASE_RENDER_CONTEXT, 'dandisets': dandiset_context} return build_message( - subject='DANDI: new dandisets to un-embargo', + subject='DANDI: New Dandisets to un-embargo', message=render_to_string('api/mail/dandisets_to_unembargo.txt', render_context), - to=[ADMIN_EMAIL], + to=[settings.DANDI_DEV_EMAIL], ) def send_dandisets_to_unembargo_message(dandisets: Iterable[Dandiset]): - logger.info('Sending dandisets to un-embargo message to admins at %s', ADMIN_EMAIL) + logger.info('Sending dandisets to un-embargo message to devs at %s', settings.DANDI_DEV_EMAIL) messages = [build_dandisets_to_unembargo_message(dandisets)] with mail.get_connection() as connection: connection.send_messages(messages) diff --git a/dandiapi/settings.py b/dandiapi/settings.py index 69a5196fd..f2d1e0ef3 100644 --- a/dandiapi/settings.py +++ b/dandiapi/settings.py @@ -107,6 +107,7 @@ def mutate_configuration(configuration: type[ComposedConfiguration]): DANDI_WEB_APP_URL = values.URLValue(environ_required=True) DANDI_API_URL = values.URLValue(environ_required=True) DANDI_JUPYTERHUB_URL = values.URLValue(environ_required=True) + DANDI_DEV_EMAIL = values.EmailValue(environ_required=True) DANDI_VALIDATION_JOB_INTERVAL = values.IntegerValue(environ=True, default=60) diff --git a/dev/.env.docker-compose b/dev/.env.docker-compose index 61ab9c0ca..d022b58bb 100644 --- a/dev/.env.docker-compose +++ b/dev/.env.docker-compose @@ -12,4 +12,5 @@ DJANGO_DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME=dandiapi-embargo-dandisets-logs DJANGO_DANDI_WEB_APP_URL=http://localhost:8085 DJANGO_DANDI_API_URL=http://localhost:8000 DJANGO_DANDI_JUPYTERHUB_URL=https://hub.dandiarchive.org/ +DJANGO_DANDI_DEV_EMAIL=test@example.com DANDI_ALLOW_LOCALHOST_URLS=1 diff --git a/dev/.env.docker-compose-native b/dev/.env.docker-compose-native index 3aed3d184..00ea3da65 100644 --- a/dev/.env.docker-compose-native +++ b/dev/.env.docker-compose-native @@ -12,4 +12,5 @@ DJANGO_DANDI_DANDISETS_EMBARGO_LOG_BUCKET_NAME=dandiapi-embargo-dandisets-logs DJANGO_DANDI_WEB_APP_URL=http://localhost:8085 DJANGO_DANDI_API_URL=http://localhost:8000 DJANGO_DANDI_JUPYTERHUB_URL=https://hub.dandiarchive.org/ +DJANGO_DANDI_DEV_EMAIL=test@example.com DANDI_ALLOW_LOCALHOST_URLS=1 diff --git a/tox.ini b/tox.ini index 9da9f95ea..38db6f1c3 100644 --- a/tox.ini +++ b/tox.ini @@ -45,6 +45,7 @@ passenv = DJANGO_DANDI_WEB_APP_URL DJANGO_DANDI_API_URL DJANGO_DANDI_JUPYTERHUB_URL + DJANGO_DANDI_DEV_EMAIL DANDI_ALLOW_LOCALHOST_URLS extras = dev @@ -65,6 +66,7 @@ passenv = DJANGO_DANDI_WEB_APP_URL DJANGO_DANDI_API_URL DJANGO_DANDI_JUPYTERHUB_URL + DJANGO_DANDI_DEV_EMAIL extras = dev commands = From 02709d81309b3f4398714cd352fcbae9ed5531cc Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 25 Apr 2024 16:53:33 -0400 Subject: [PATCH 21/21] Send dandiset un-embargo request email immediately --- dandiapi/api/services/embargo/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dandiapi/api/services/embargo/__init__.py b/dandiapi/api/services/embargo/__init__.py index 3ca8f22b8..dd07e04da 100644 --- a/dandiapi/api/services/embargo/__init__.py +++ b/dandiapi/api/services/embargo/__init__.py @@ -7,6 +7,7 @@ from django.conf import settings from django.db import transaction +from dandiapi.api.mail import send_dandisets_to_unembargo_message from dandiapi.api.models import Asset, AssetBlob, Dandiset, Version from dandiapi.api.services.asset.exceptions import DandisetOwnerRequiredError from dandiapi.api.storage import get_boto_client @@ -78,3 +79,6 @@ def unembargo_dandiset(*, user: User, dandiset: Dandiset): # initiate the un-embargo process dandiset.embargo_status = Dandiset.EmbargoStatus.UNEMBARGOING dandiset.save() + + # Send initial email to ensure it's seen in a timely manner + send_dandisets_to_unembargo_message(dandisets=[dandiset])