diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index bf3d5157f..4282ee9fc 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -53,6 +53,9 @@ 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_INSTANCE_NAME: DEV-DANDI + DJANGO_DANDI_INSTANCE_IDENTIFIER: "RRID:ABC_123456" + DJANGO_DANDI_DOI_API_PREFIX: "10.80507" steps: - uses: actions/checkout@v5 with: diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index f06c5e383..7ce03f8eb 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -78,6 +78,9 @@ 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_INSTANCE_NAME: DEV-DANDI + DJANGO_DANDI_INSTANCE_IDENTIFIER: "RRID:ABC_123456" + DJANGO_DANDI_DOI_API_PREFIX: "10.80507" # Web client env vars VITE_APP_DANDI_API_ROOT: http://localhost:8000/api/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f87676285..e339a7ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# v0.20.0 (Wed Dec 17 2025) + +### Release Notes + +#### Add vendorization support ([#2584](https://github.com/dandi/dandi-archive/pull/2584)) + +This PR makes the instance config as defined in `dandischema.conf`, per https://github.com/dandi/dandi-schema/pull/294, available through the `api/info/` endpoint and replaces the hardcoded `DAND:`, the hardcoded RRID, and the supported licenses with the corresponding attributes in schema instance config defined in `dandischema.conf`. + +--- + +#### ๐Ÿš€ Enhancement + +- Add vendorization support [#2584](https://github.com/dandi/dandi-archive/pull/2584) ([@candleindark](https://github.com/candleindark) [@jjnesbitt](https://github.com/jjnesbitt)) + +#### ๐Ÿ› Bug Fix + +- Empty commit for release [#2680](https://github.com/dandi/dandi-archive/pull/2680) ([@jjnesbitt](https://github.com/jjnesbitt)) +- Update management command for schema migration [#2650](https://github.com/dandi/dandi-archive/pull/2650) ([@jjnesbitt](https://github.com/jjnesbitt)) + +#### ๐Ÿ  Internal + +- Remove `DANDI_SCHEMA_VERSION` as a setting [#2644](https://github.com/dandi/dandi-archive/pull/2644) ([@jjnesbitt](https://github.com/jjnesbitt)) +- Auto approve users from McGill University [#2657](https://github.com/dandi/dandi-archive/pull/2657) ([@kabilar](https://github.com/kabilar)) +- Don't use django setting to store allowed schema versions [#2643](https://github.com/dandi/dandi-archive/pull/2643) ([@jjnesbitt](https://github.com/jjnesbitt)) +- Add create_test_user management command [#2642](https://github.com/dandi/dandi-archive/pull/2642) ([@jjnesbitt](https://github.com/jjnesbitt)) +- Add docker volume for pre-commit cache [#2640](https://github.com/dandi/dandi-archive/pull/2640) ([@mvandenburgh](https://github.com/mvandenburgh)) + +#### Authors: 4 + +- Isaac To ([@candleindark](https://github.com/candleindark)) +- Jacob Nesbitt ([@jjnesbitt](https://github.com/jjnesbitt)) +- Kabilar Gunalan ([@kabilar](https://github.com/kabilar)) +- Mike VanDenburgh ([@mvandenburgh](https://github.com/mvandenburgh)) + +--- + # v0.19.0 (Thu Nov 13 2025) ### Release Notes diff --git a/dandiapi/api/doi.py b/dandiapi/api/doi.py index 5cd18cbd9..2869d358a 100644 --- a/dandiapi/api/doi.py +++ b/dandiapi/api/doi.py @@ -3,6 +3,7 @@ import logging from typing import TYPE_CHECKING +from dandischema.conf import get_instance_config from django.conf import settings import requests @@ -29,10 +30,11 @@ def _generate_doi_data(version: Version): publish = settings.DANDI_DOI_PUBLISH # Use the DANDI test datacite instance as a placeholder if PREFIX isn't set - prefix = settings.DANDI_DOI_API_PREFIX or '10.80507' + prefix = settings.DANDI_DOI_API_PREFIX + instance_name: str = get_instance_config().instance_name dandiset_id = version.dandiset.identifier version_id = version.version - doi = f'{prefix}/dandi.{dandiset_id}/{version_id}' + doi = f'{prefix}/{instance_name.lower()}.{dandiset_id}/{version_id}' metadata = version.metadata metadata['doi'] = doi return (doi, to_datacite(metadata, publish=publish)) diff --git a/dandiapi/api/management/commands/create_dev_dandiset.py b/dandiapi/api/management/commands/create_dev_dandiset.py index 045748960..becaaf8d9 100644 --- a/dandiapi/api/management/commands/create_dev_dandiset.py +++ b/dandiapi/api/management/commands/create_dev_dandiset.py @@ -2,7 +2,8 @@ from uuid import uuid4 -from django.conf import settings +from dandischema.conf import get_instance_config +from dandischema.consts import DANDI_SCHEMA_VERSION from django.contrib.auth.models import User from django.core.files.uploadedfile import SimpleUploadedFile import djclick as click @@ -32,7 +33,7 @@ def create_dev_dandiset(*, name: str, email: str, num_extra_owners: int): version_metadata = { 'description': 'An informative description', - 'license': ['spdx:CC0-1.0'], + 'license': [sorted(x.value for x in get_instance_config().licenses)[0]], } dandiset, draft_version = create_open_dandiset( user=owner, version_name=name, version_metadata=version_metadata @@ -68,7 +69,7 @@ def create_dev_dandiset(*, name: str, email: str, num_extra_owners: int): calculate_sha256(blob_id=asset_blob.blob_id) asset_blob.refresh_from_db() asset_metadata = { - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, 'encodingFormat': 'text/plain', 'schemaKey': 'Asset', 'path': 'foo/bar.txt', diff --git a/dandiapi/api/management/commands/create_test_user.py b/dandiapi/api/management/commands/create_test_user.py new file mode 100644 index 000000000..c39a0034c --- /dev/null +++ b/dandiapi/api/management/commands/create_test_user.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from allauth.socialaccount.models import SocialAccount +from django.contrib.auth.models import User +from django.db import transaction +import djclick as click +import faker + +from dandiapi.api.models.user import UserMetadata + + +@click.command() +@click.option( + '--auto-approve', + is_flag=True, + help='Auto approve this user, skipping the questionnaire.', +) +@click.option( + '--password', + default='password', + show_default=True, + help='The password for this user.', +) +def create_test_user(*, auto_approve: bool, password: str): + fake = faker.Faker() + + with transaction.atomic(): + email = fake.email() + user = User.objects.create( + first_name=fake.first_name(), + last_name=fake.last_name(), + username=email, + email=email, + ) + + user.set_password(password) + user.save() + + UserMetadata.objects.create( + user=user, + status=UserMetadata.Status.APPROVED if auto_approve else UserMetadata.Status.INCOMPLETE, + ) + + uid = fake.random_number(digits=8, fix_len=True) + SocialAccount.objects.create( + user=user, + provider='github', + uid=uid, + extra_data={ + 'id': uid, + 'name': user.get_full_name(), + 'email': user.email, + 'login': fake.user_name(), + }, + ) + + click.echo( + click.style( + f'Created user "{user.email}" with password "{password}"', + fg='green', + bold=True, + ) + ) diff --git a/dandiapi/api/management/commands/migrate_version_metadata.py b/dandiapi/api/management/commands/migrate_version_metadata.py index 9b5417509..060a9486e 100644 --- a/dandiapi/api/management/commands/migrate_version_metadata.py +++ b/dandiapi/api/management/commands/migrate_version_metadata.py @@ -1,28 +1,90 @@ from __future__ import annotations -from dandischema import migrate +import logging + +from dandischema.consts import DANDI_SCHEMA_VERSION +from dandischema.metadata import migrate +from django.db import transaction import djclick as click from dandiapi.api.models import Version +from dandiapi.api.services import audit +from dandiapi.api.services.metadata import validate_version_metadata + +logger = logging.getLogger(__name__) @click.command() -@click.argument('to_version') -def migrate_version_metadata(*, to_version: str): - click.echo(f'Migrating all version metadata to version {to_version}') - for version in Version.objects.filter(version='draft'): - click.echo(f'Migrating {version.dandiset.identifier}/{version.version}') - - metadata = version.metadata - - try: - metanew = migrate(metadata, to_version=to_version, skip_validation=True) - except Exception as e: # noqa: BLE001 - click.echo(f'Failed to migrate {version.dandiset.identifier}/{version.version}') - click.echo(e) - continue - - if version.metadata != metanew: +@click.argument( + 'dandisets', + type=click.INT, + nargs=-1, +) +@click.option( + '-a', + '--all', + 'include_all', + is_flag=True, + help='Run on all dandisets.', +) +def migrate_version_metadata(dandisets: tuple[int, ...], *, include_all: bool): + if bool(dandisets) == include_all: + raise click.ClickException("Must specify exactly one of 'dandisets' or --all") + + versions = Version.objects.filter(version='draft') + if dandisets: + versions = versions.filter(dandiset_id__in=dandisets) + + logger.info( + 'Migrating %s dandiset draft versions to schema version %s', + versions.count(), + DANDI_SCHEMA_VERSION, + ) + + migrated_count = 0 + failed_count = 0 + unchanged_count = 0 + for version in versions.iterator(): + logger.info('-----------------------------------------') + logger.info('Migrating %s', version) + + with transaction.atomic(): + locked_version = Version.objects.select_for_update().get(id=version.id) + + try: + metanew = migrate(locked_version.metadata, skip_validation=True) + except Exception as e: + logger.exception('Failed to migrate %s', version, exc_info=e) + failed_count += 1 + continue + + if locked_version.metadata == metanew: + logger.info('No change in metadata for %s. Skipping save...', version) + unchanged_count += 1 + continue + version.metadata = metanew version.status = Version.Status.PENDING version.save() + + audit.update_metadata( + dandiset=locked_version.dandiset, + metadata=locked_version.metadata, + user=None, + admin=True, + description=f'Update schema version to {DANDI_SCHEMA_VERSION}', + ) + + migrated_count += 1 + logger.info('Metadata migrated for version %s', version) + + # Validate outside of transaction, since this function uses `select_for_update` itself + validate_version_metadata(version=version) + + logger.info( + '%d migrated, %d failed, %d left unchanged, out of %d total selected versions', + migrated_count, + failed_count, + unchanged_count, + versions.count(), + ) diff --git a/dandiapi/api/models/metadata.py b/dandiapi/api/models/metadata.py index 103778051..35c885abb 100644 --- a/dandiapi/api/models/metadata.py +++ b/dandiapi/api/models/metadata.py @@ -3,6 +3,8 @@ from typing import TYPE_CHECKING from uuid import uuid4 +from dandischema.conf import get_instance_config + if TYPE_CHECKING: import datetime @@ -10,6 +12,8 @@ class PublishableMetadataMixin: @classmethod def published_by(cls, now: datetime.datetime): + schema_config = get_instance_config() + return { 'id': uuid4().urn, 'name': 'DANDI publish', @@ -20,8 +24,8 @@ def published_by(cls, now: datetime.datetime): 'wasAssociatedWith': [ { 'id': uuid4().urn, - 'identifier': 'RRID:SCR_017571', - 'name': 'DANDI API', + 'identifier': schema_config.instance_identifier, + 'name': f'{schema_config.instance_name} API', # TODO: version the API 'version': '0.1.0', 'schemaKey': 'Software', diff --git a/dandiapi/api/models/version.py b/dandiapi/api/models/version.py index ef3e148ea..9363b8aa3 100644 --- a/dandiapi/api/models/version.py +++ b/dandiapi/api/models/version.py @@ -4,6 +4,7 @@ import logging from typing import TypedDict +from dandischema.conf import get_instance_config from dandischema.models import AccessType from django.conf import settings from django.contrib.postgres.indexes import HashIndex @@ -229,6 +230,7 @@ def _populate_access_metadata(self): def _populate_metadata(self): from dandiapi.api.manifests import manifest_location + schema_config = get_instance_config() metadata = { **self.metadata, '@context': ( @@ -237,9 +239,9 @@ def _populate_metadata(self): ), 'manifestLocation': manifest_location(self), 'name': self.name, - 'identifier': f'DANDI:{self.dandiset.identifier}', + 'identifier': (f'{schema_config.instance_name}:{self.dandiset.identifier}'), 'version': self.version, - 'id': f'DANDI:{self.dandiset.identifier}/{self.version}', + 'id': (f'{schema_config.instance_name}:{self.dandiset.identifier}/{self.version}'), 'repository': settings.DANDI_WEB_APP_URL, 'url': ( f'{settings.DANDI_WEB_APP_URL}/dandiset/{self.dandiset.identifier}/{self.version}' diff --git a/dandiapi/api/services/metadata/__init__.py b/dandiapi/api/services/metadata/__init__.py index 9c9fdc80a..5ee47e1f4 100644 --- a/dandiapi/api/services/metadata/__init__.py +++ b/dandiapi/api/services/metadata/__init__.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from celery.utils.log import get_task_logger +from dandischema.conf import get_instance_config import dandischema.exceptions from dandischema.metadata import aggregate_assets_summary, validate from django.conf import settings @@ -108,6 +109,8 @@ def version_aggregate_assets_summary(version: Version) -> None: def validate_version_metadata(*, version: Version) -> None: def _build_validatable_version_metadata(version: Version) -> dict: + schema_config = get_instance_config() + # since Version.Status.VALID is a proxy for a version being publishable, we need to # validate against the PublishedDandiset schema even though we lack several things # at validation time: id, url, doi, and assetsSummary. this tricks the validator into @@ -116,13 +119,16 @@ def _build_validatable_version_metadata(version: Version) -> dict: metadata_for_validation = publishable_version.metadata metadata_for_validation['id'] = ( - f'DANDI:{publishable_version.dandiset.identifier}/{publishable_version.version}' + f'{schema_config.instance_name}:' + f'{publishable_version.dandiset.identifier}/{publishable_version.version}' ) metadata_for_validation['url'] = ( f'{settings.DANDI_WEB_APP_URL}/dandiset/' f'{publishable_version.dandiset.identifier}/{publishable_version.version}' ) - metadata_for_validation['doi'] = '10.80507/dandi.123456/0.123456.1234' + metadata_for_validation['doi'] = ( + f'{schema_config.doi_prefix}/{schema_config.instance_name.lower()}.123456/0.123456.1234' + ) metadata_for_validation['assetsSummary'] = { 'schemaKey': 'AssetsSummary', 'numberOfBytes': 1 diff --git a/dandiapi/api/services/publish/__init__.py b/dandiapi/api/services/publish/__init__.py index 216b32c4e..1e5e5307d 100644 --- a/dandiapi/api/services/publish/__init__.py +++ b/dandiapi/api/services/publish/__init__.py @@ -4,6 +4,7 @@ import datetime from typing import TYPE_CHECKING +from dandischema.conf import get_instance_config from dandischema.metadata import aggregate_assets_summary, validate from django.contrib.auth.models import User from django.db import transaction @@ -182,7 +183,10 @@ def _publish_dandiset(dandiset_id: int, user_id: int) -> None: old_version.save() # Inject a dummy DOI so the metadata is valid - new_version.metadata['doi'] = '10.80507/dandi.123456/0.123456.1234' + schema_config = get_instance_config() + new_version.metadata['doi'] = ( + f'{schema_config.doi_prefix}/{schema_config.instance_name.lower()}.123456/0.123456.1234' + ) validate(new_version.metadata, schema_key='PublishedDandiset', json_validation=True) diff --git a/dandiapi/api/services/version/metadata.py b/dandiapi/api/services/version/metadata.py index 198b42756..7c6763fb5 100644 --- a/dandiapi/api/services/version/metadata.py +++ b/dandiapi/api/services/version/metadata.py @@ -1,6 +1,6 @@ from __future__ import annotations -from django.conf import settings +from dandischema.consts import DANDI_SCHEMA_VERSION from dandiapi.api.models.version import Version @@ -18,7 +18,7 @@ def _normalize_version_metadata(raw_version_metadata: dict, name: str, email: st # not specified in the version_metadata return { 'schemaKey': 'Dandiset', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, 'contributor': [ { 'name': name, diff --git a/dandiapi/api/tests/factories.py b/dandiapi/api/tests/factories.py index 3fa29f47d..559a35f10 100644 --- a/dandiapi/api/tests/factories.py +++ b/dandiapi/api/tests/factories.py @@ -4,8 +4,9 @@ import hashlib from allauth.socialaccount.models import SocialAccount +from dandischema.conf import get_instance_config +from dandischema.consts import DANDI_SCHEMA_VERSION from dandischema.models import AccessType -from django.conf import settings from django.contrib.auth.models import User import factory import faker @@ -113,9 +114,11 @@ class Meta: @factory.lazy_attribute def metadata(self) -> dict: + from dandiapi.conftest import get_first_allowed_license + metadata = { **faker.Faker().pydict(value_types=['str', 'float', 'int']), - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, 'schemaKey': 'Dandiset', 'description': faker.Faker().sentence(), 'access': [ @@ -134,7 +137,7 @@ def metadata(self) -> dict: 'schemaKey': 'Person', } ], - 'license': ['spdx:CC0-1.0'], + 'license': [get_first_allowed_license()], } # Remove faked data that might conflict with the schema types for key in ['about']: @@ -162,7 +165,9 @@ class DraftVersionFactory(BaseVersionFactory): class PublishedVersionFactory(BaseVersionFactory): doi = factory.LazyAttribute( - lambda self: f'10.80507/dandi.{self.dandiset.identifier}/{self.version}' + lambda self: f'{get_instance_config().doi_prefix}/' + f'{get_instance_config().instance_name}.' + f'{self.dandiset.identifier}/{self.version}' ) status = Version.Status.PUBLISHED @@ -229,7 +234,7 @@ class Meta: def metadata(self) -> dict: metadata = { **faker.Faker().pydict(value_types=['str', 'float', 'int']), - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, 'encodingFormat': 'application/x-nwb', 'schemaKey': 'Asset', } diff --git a/dandiapi/api/tests/fuzzy.py b/dandiapi/api/tests/fuzzy.py index d005501a5..d416d8f77 100644 --- a/dandiapi/api/tests/fuzzy.py +++ b/dandiapi/api/tests/fuzzy.py @@ -2,6 +2,8 @@ import re +from dandischema.conf import get_instance_config + class Re: def __init__(self, pattern): @@ -23,12 +25,22 @@ def __hash__(self): return hash(self.pattern) +schema_config = get_instance_config() + TIMESTAMP_RE = Re(r'\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}\.\d{6}Z') UTC_ISO_TIMESTAMP_RE = Re(r'\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}\.\d{6}\+[0-9]{2}:[0-9]{2}') DATE_RE = Re(r'\d{4}-\d{2}-\d{2}') DANDISET_ID_RE = Re(r'\d{6}') -DANDISET_SCHEMA_ID_RE = Re(r'DANDI:\d{6}') +DANDISET_SCHEMA_ID_RE = Re(rf'{schema_config.instance_name}:\d{{6}}') VERSION_ID_RE = Re(r'0\.\d{6}\.\d{4}') HTTP_URL_RE = Re(r'http[s]?\://[^/]+(/[^/]+)*[/]?(&.+)?') UUID_RE = Re(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') URN_RE = Re(r'urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + +DEFAULT_WAS_ASSOCIATED_WITH = { + 'id': URN_RE, + 'identifier': schema_config.instance_identifier, + 'name': f'{schema_config.instance_name} API', + 'version': '0.1.0', + 'schemaKey': 'Software', +} diff --git a/dandiapi/api/tests/test_asset.py b/dandiapi/api/tests/test_asset.py index 32c541184..ac0428888 100644 --- a/dandiapi/api/tests/test_asset.py +++ b/dandiapi/api/tests/test_asset.py @@ -3,6 +3,7 @@ import json from uuid import uuid4 +from dandischema.consts import DANDI_SCHEMA_VERSION from dandischema.models import AccessType from django.conf import settings from django.db.utils import IntegrityError @@ -28,7 +29,14 @@ from dandiapi.zarr.tasks import ingest_zarr_archive from dandiapi.zarr.tests.factories import ZarrArchiveFactory -from .fuzzy import HTTP_URL_RE, TIMESTAMP_RE, URN_RE, UTC_ISO_TIMESTAMP_RE, UUID_RE +from .fuzzy import ( + DEFAULT_WAS_ASSOCIATED_WITH, + HTTP_URL_RE, + TIMESTAMP_RE, + URN_RE, + UTC_ISO_TIMESTAMP_RE, + UUID_RE, +) # Model tests @@ -127,16 +135,7 @@ def test_publish_asset(draft_asset: Asset): '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', - } - ], + 'wasAssociatedWith': [DEFAULT_WAS_ASSOCIATED_WITH], 'schemaKey': 'PublishActivity', }, 'datePublished': UTC_ISO_TIMESTAMP_RE, @@ -181,7 +180,7 @@ def test_asset_total_size(asset_factory, asset_blob_factory, zarr_archive_factor def test_asset_full_metadata(draft_asset_factory): raw_metadata = { 'foo': 'bar', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, } asset: Asset = draft_asset_factory(metadata=raw_metadata) @@ -201,7 +200,7 @@ def test_asset_full_metadata(draft_asset_factory): 'contentUrl': [download_url, blob_url], 'contentSize': asset.blob.size, 'digest': asset.blob.digest, - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{DANDI_SCHEMA_VERSION}/context.json', } @@ -210,7 +209,7 @@ def test_asset_full_metadata_zarr(draft_asset_factory): zarr_archive = ZarrArchiveFactory.create() raw_metadata = { 'foo': 'bar', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, } asset: Asset = draft_asset_factory(metadata=raw_metadata, blob=None, zarr=zarr_archive) @@ -232,7 +231,7 @@ def test_asset_full_metadata_zarr(draft_asset_factory): 'digest': asset.digest, # This should be injected on all zarr assets 'encodingFormat': 'application/x-zarr', - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{DANDI_SCHEMA_VERSION}/context.json', } @@ -242,7 +241,7 @@ def test_asset_full_metadata_access( ): raw_metadata = { 'foo': 'bar', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, } embargoed_zarr_asset: Asset = draft_asset_factory( metadata=raw_metadata, blob=None, zarr=embargoed_zarr_archive_factory() @@ -788,7 +787,7 @@ def test_asset_create_path_validation(api_client, asset_blob, path, expected_sta api_client.force_authenticate(user=user) metadata = { - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, 'encodingFormat': 'application/x-nwb', 'path': path, } @@ -815,7 +814,7 @@ def test_asset_create_conflicting_path(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'foo/bar.txt', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) @@ -827,7 +826,7 @@ def test_asset_create_conflicting_path(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'foo/bar.txt/baz.txt', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) @@ -839,7 +838,7 @@ def test_asset_create_conflicting_path(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'foo', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) @@ -1255,7 +1254,7 @@ def test_asset_rest_rename(api_client, asset_blob): api_client.force_authenticate(user=user) # Create asset - metadata = {'path': 'foo/bar', 'schemaVersion': settings.DANDI_SCHEMA_VERSION} + metadata = {'path': 'foo/bar', 'schemaVersion': DANDI_SCHEMA_VERSION} asset = add_asset_to_version( user=user, version=draft_version, asset_blob=asset_blob, metadata=metadata ) @@ -1652,7 +1651,7 @@ def test_asset_rest_delete_zarr_modified( { 'metadata': { 'path': 'sample.zarr', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, 'zarr_id': zarr_archive.zarr_id, }, diff --git a/dandiapi/api/tests/test_asset_paths.py b/dandiapi/api/tests/test_asset_paths.py index b67430344..7cdcdbe2c 100644 --- a/dandiapi/api/tests/test_asset_paths.py +++ b/dandiapi/api/tests/test_asset_paths.py @@ -1,6 +1,6 @@ from __future__ import annotations -from django.conf import settings +from dandischema.consts import DANDI_SCHEMA_VERSION from django.db.models import Q, QuerySet import pytest @@ -383,7 +383,7 @@ def test_asset_path_ordering(asset_blob): asset_blob=asset_blob, metadata={ 'path': 'a/z', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) add_asset_to_version( @@ -392,7 +392,7 @@ def test_asset_path_ordering(asset_blob): asset_blob=asset_blob, metadata={ 'path': 'aa/z', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) diff --git a/dandiapi/api/tests/test_dandiset.py b/dandiapi/api/tests/test_dandiset.py index e74493966..c4e6a7487 100644 --- a/dandiapi/api/tests/test_dandiset.py +++ b/dandiapi/api/tests/test_dandiset.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING from urllib.parse import urlencode +from dandischema.conf import get_instance_config +from dandischema.consts import DANDI_SCHEMA_VERSION from django.conf import settings from django.contrib.auth.models import AnonymousUser from django.utils import timezone @@ -21,6 +23,7 @@ PublishedVersionFactory, UserFactory, ) +from dandiapi.conftest import get_first_allowed_license from .fuzzy import ( DANDISET_ID_RE, @@ -34,6 +37,9 @@ from rest_framework.test import APIClient +_SCHEMA_CONFIG = get_instance_config() + + @pytest.mark.django_db def test_dandiset_identifier(): dandiset = DandisetFactory.create() @@ -452,7 +458,7 @@ def test_dandiset_rest_create(api_client): ], 'name': name, 'identifier': DANDISET_SCHEMA_ID_RE, - 'id': f'DANDI:{dandiset.identifier}/draft', + 'id': f'{_SCHEMA_CONFIG.instance_name}:{dandiset.identifier}/draft', 'version': 'draft', 'url': url, 'dateCreated': UTC_ISO_TIMESTAMP_RE, @@ -460,8 +466,8 @@ def test_dandiset_rest_create(api_client): 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, + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{DANDI_SCHEMA_VERSION}/context.json', + 'schemaVersion': DANDI_SCHEMA_VERSION, 'schemaKey': 'Dandiset', 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], 'repository': settings.DANDI_WEB_APP_URL, @@ -489,7 +495,7 @@ def test_dandiset_rest_create_with_identifier(api_client): api_client.force_authenticate(user=user) name = 'Test Dandiset' identifier = '123456' - metadata = {'foo': 'bar', 'identifier': f'DANDI:{identifier}'} + metadata = {'foo': 'bar', 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{identifier}'} response = api_client.post('/api/dandisets/', {'name': name, 'metadata': metadata}) assert response.data == { @@ -534,8 +540,8 @@ def test_dandiset_rest_create_with_identifier(api_client): f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' ], 'name': name, - 'identifier': f'DANDI:{identifier}', - 'id': f'DANDI:{dandiset.identifier}/draft', + 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{identifier}', + 'id': f'{_SCHEMA_CONFIG.instance_name}:{dandiset.identifier}/draft', 'version': 'draft', 'url': url, 'dateCreated': UTC_ISO_TIMESTAMP_RE, @@ -543,8 +549,8 @@ def test_dandiset_rest_create_with_identifier(api_client): 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, + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{DANDI_SCHEMA_VERSION}/context.json', + 'schemaVersion': DANDI_SCHEMA_VERSION, 'schemaKey': 'Dandiset', 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], 'repository': settings.DANDI_WEB_APP_URL, @@ -574,7 +580,7 @@ def test_dandiset_rest_create_with_contributor(api_client): identifier = '123456' metadata = { 'foo': 'bar', - 'identifier': f'DANDI:{identifier}', + 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{identifier}', # This contributor is different from the user 'contributor': [ { @@ -631,14 +637,14 @@ def test_dandiset_rest_create_with_contributor(api_client): f'{settings.DANDI_API_URL}/api/dandisets/{dandiset.identifier}/versions/draft/assets/' ], 'name': name, - 'identifier': f'DANDI:{identifier}', - 'id': f'DANDI:{dandiset.identifier}/draft', + 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{identifier}', + 'id': f'{_SCHEMA_CONFIG.instance_name}:{dandiset.identifier}/draft', 'version': 'draft', 'url': url, 'dateCreated': UTC_ISO_TIMESTAMP_RE, 'citation': (f'Jane Doe ({year}) {name} (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, + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{DANDI_SCHEMA_VERSION}/context.json', + 'schemaVersion': DANDI_SCHEMA_VERSION, 'schemaKey': 'Dandiset', 'access': [{'schemaKey': 'AccessRequirements', 'status': 'dandi:OpenAccess'}], 'repository': settings.DANDI_WEB_APP_URL, @@ -712,7 +718,7 @@ def test_dandiset_rest_create_embargoed(api_client): ], 'name': name, 'identifier': DANDISET_SCHEMA_ID_RE, - 'id': f'DANDI:{dandiset.identifier}/draft', + 'id': f'{_SCHEMA_CONFIG.instance_name}:{dandiset.identifier}/draft', 'version': 'draft', 'url': url, 'dateCreated': UTC_ISO_TIMESTAMP_RE, @@ -720,8 +726,8 @@ def test_dandiset_rest_create_embargoed(api_client): 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, + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{DANDI_SCHEMA_VERSION}/context.json', + 'schemaVersion': DANDI_SCHEMA_VERSION, 'schemaKey': 'Dandiset', 'access': [ { @@ -754,7 +760,11 @@ def test_dandiset_rest_create_embargoed_with_award_info(api_client: APIClient): user = UserFactory.create() api_client.force_authenticate(user=user) name = 'Test Embargoed Dandiset' - metadata = {'name': name, 'description': 'Test embargoed dandiset', 'license': ['spdx:CC0-1.0']} + metadata = { + 'name': name, + 'description': 'Test embargoed dandiset', + 'license': [get_first_allowed_license()], + } # Create embargoed dandiset with funding and award info embargo_end_date = (timezone.now().date() + datetime.timedelta(days=365)).isoformat() @@ -802,7 +812,11 @@ def test_dandiset_rest_create_embargoed_no_funding_info(api_client: APIClient): user = UserFactory.create() api_client.force_authenticate(user=user) name = 'Test Embargoed Dandiset - No Funding' - metadata = {'name': name, 'description': 'Test embargoed dandiset', 'license': ['spdx:CC0-1.0']} + metadata = { + 'name': name, + 'description': 'Test embargoed dandiset', + 'license': [get_first_allowed_license()], + } # Create embargoed dandiset without funding info query_params = {'embargo': 'true'} @@ -847,7 +861,11 @@ def test_dandiset_rest_create_embargoed_funding_no_award(api_client: APIClient): user = UserFactory.create() api_client.force_authenticate(user=user) name = 'Test Embargoed Dandiset - Funding Only' - metadata = {'name': name, 'description': 'Test embargoed dandiset', 'license': ['spdx:CC0-1.0']} + metadata = { + 'name': name, + 'description': 'Test embargoed dandiset', + 'license': [get_first_allowed_license()], + } # Create embargoed dandiset with funding source but no award number query_params = { @@ -867,7 +885,11 @@ def test_dandiset_rest_create_embargoed_award_no_funding(api_client: APIClient): user = UserFactory.create() api_client.force_authenticate(user=user) name = 'Test Embargoed Dandiset - Award Only' - metadata = {'name': name, 'description': 'Test embargoed dandiset', 'license': ['spdx:CC0-1.0']} + metadata = { + 'name': name, + 'description': 'Test embargoed dandiset', + 'license': [get_first_allowed_license()], + } # Create embargoed dandiset with award number but no funding source query_params = { @@ -888,7 +910,7 @@ def test_dandiset_rest_create_with_duplicate_identifier(api_client): api_client.force_authenticate(user=user) name = 'Test Dandiset' identifier = dandiset.identifier - metadata = {'foo': 'bar', 'identifier': f'DANDI:{identifier}'} + metadata = {'foo': 'bar', 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{identifier}'} response = api_client.post('/api/dandisets/', {'name': name, 'metadata': metadata}) assert response.status_code == 400 diff --git a/dandiapi/api/tests/test_info.py b/dandiapi/api/tests/test_info.py new file mode 100644 index 000000000..2c02b0870 --- /dev/null +++ b/dandiapi/api/tests/test_info.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from dandischema.conf import get_instance_config + + +def test_rest_info_instance_config_include_none(api_client): + resp = api_client.get('/api/info/') + assert resp.status_code == 200 + + # Ensure that there is no difference in the keys being returned from the info endpoint. If the + # info endpoint were missing values, it would allow for default values to be creep in when + # de-serializing the JSON into a Pydantic model. + assert ( + resp.json()['instance_config'].keys() + == get_instance_config().model_dump(mode='json', exclude_none=False).keys() + ) diff --git a/dandiapi/api/tests/test_tasks.py b/dandiapi/api/tests/test_tasks.py index 433a93aea..6d1d78795 100644 --- a/dandiapi/api/tests/test_tasks.py +++ b/dandiapi/api/tests/test_tasks.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import TYPE_CHECKING +from dandischema.conf import get_instance_config from django.conf import settings from django.core.files.storage import default_storage from django.forms.models import model_to_dict @@ -20,12 +21,15 @@ from dandiapi.api.tests.factories import DraftVersionFactory, UserFactory from dandiapi.zarr.models import ZarrArchiveStatus -from .fuzzy import HTTP_URL_RE, URN_RE, UTC_ISO_TIMESTAMP_RE +from .fuzzy import DEFAULT_WAS_ASSOCIATED_WITH, HTTP_URL_RE, URN_RE, UTC_ISO_TIMESTAMP_RE if TYPE_CHECKING: from rest_framework.test import APIClient +_SCHEMA_CONFIG = get_instance_config() + + @pytest.mark.django_db def test_calculate_checksum_task(asset_blob_factory): asset_blob = asset_blob_factory(blob__data=b'known-content', size=13, sha256=None) @@ -379,29 +383,23 @@ def test_publish_task( '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', - } - ], + 'wasAssociatedWith': [DEFAULT_WAS_ASSOCIATED_WITH], 'schemaKey': 'PublishActivity', }, 'datePublished': UTC_ISO_TIMESTAMP_RE, 'manifestLocation': [HTTP_URL_RE], - 'identifier': f'DANDI:{draft_version.dandiset.identifier}', + 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{draft_version.dandiset.identifier}', 'version': published_version.version, - 'id': f'DANDI:{draft_version.dandiset.identifier}/{published_version.version}', + 'id': ( + f'{_SCHEMA_CONFIG.instance_name}:' + f'{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}', + 'doi': published_version.doi, # Once the assets are linked, assetsSummary should be computed properly 'assetsSummary': { 'schemaKey': 'AssetsSummary', @@ -439,15 +437,7 @@ def test_publish_task( 'startDate': UTC_ISO_TIMESTAMP_RE, # TODO: endDate needs to be defined before publish is complete 'endDate': UTC_ISO_TIMESTAMP_RE, - 'wasAssociatedWith': [ - { - 'id': URN_RE, - 'identifier': 'RRID:SCR_017571', - 'name': 'DANDI API', - 'version': '0.1.0', - 'schemaKey': 'Software', - } - ], + 'wasAssociatedWith': [DEFAULT_WAS_ASSOCIATED_WITH], 'schemaKey': 'PublishActivity', }, } diff --git a/dandiapi/api/tests/test_version.py b/dandiapi/api/tests/test_version.py index 9aea61ded..4252cbc42 100644 --- a/dandiapi/api/tests/test_version.py +++ b/dandiapi/api/tests/test_version.py @@ -4,6 +4,8 @@ from time import sleep from typing import TYPE_CHECKING +from dandischema.conf import get_instance_config +from dandischema.consts import DANDI_SCHEMA_VERSION from dandischema.models import AccessType from django.conf import settings from freezegun import freeze_time @@ -28,7 +30,16 @@ from dandiapi.api.services.publish import _build_publishable_version_from_draft from dandiapi.zarr.tasks import ingest_zarr_archive -from .fuzzy import HTTP_URL_RE, TIMESTAMP_RE, URN_RE, UTC_ISO_TIMESTAMP_RE, VERSION_ID_RE +from .fuzzy import ( + DEFAULT_WAS_ASSOCIATED_WITH, + HTTP_URL_RE, + TIMESTAMP_RE, + URN_RE, + UTC_ISO_TIMESTAMP_RE, + VERSION_ID_RE, +) + +_SCHEMA_CONFIG = get_instance_config() @freeze_time() @@ -58,7 +69,7 @@ def test_version_next_published_version_preexisting(): @pytest.mark.django_db def test_draft_version_metadata_computed(): draft_version = DraftVersionFactory.create() - original_metadata = {'schemaVersion': settings.DANDI_SCHEMA_VERSION} + original_metadata = {'schemaVersion': DANDI_SCHEMA_VERSION} draft_version.metadata = original_metadata # Save the version to add computed properties to the metadata @@ -68,9 +79,12 @@ def test_draft_version_metadata_computed(): **original_metadata, 'manifestLocation': [HTTP_URL_RE], 'name': draft_version.name, - 'identifier': f'DANDI:{draft_version.dandiset.identifier}', + 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{draft_version.dandiset.identifier}', 'version': draft_version.version, - 'id': f'DANDI:{draft_version.dandiset.identifier}/{draft_version.version}', + 'id': ( + f'{_SCHEMA_CONFIG.instance_name}:' + f'{draft_version.dandiset.identifier}/{draft_version.version}' + ), 'url': ( f'{settings.DANDI_WEB_APP_URL}/dandiset/' f'{draft_version.dandiset.identifier}/{draft_version.version}' @@ -78,7 +92,7 @@ def test_draft_version_metadata_computed(): 'repository': settings.DANDI_WEB_APP_URL, 'dateCreated': draft_version.dandiset.created.isoformat(), 'access': [{'schemaKey': 'AccessRequirements', 'status': AccessType.OpenAccess.value}], - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{DANDI_SCHEMA_VERSION}/context.json', 'assetsSummary': { 'numberOfBytes': 0, 'numberOfFiles': 0, @@ -93,7 +107,7 @@ def test_draft_version_metadata_computed(): @pytest.mark.django_db def test_published_version_metadata_computed(): published_version = PublishedVersionFactory.create() - original_metadata = {'schemaVersion': settings.DANDI_SCHEMA_VERSION} + original_metadata = {'schemaVersion': DANDI_SCHEMA_VERSION} published_version.metadata = original_metadata # Save the version to add computed properties to the metadata @@ -103,11 +117,16 @@ def test_published_version_metadata_computed(): **original_metadata, 'manifestLocation': [HTTP_URL_RE], 'name': published_version.name, - 'identifier': f'DANDI:{published_version.dandiset.identifier}', + 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{published_version.dandiset.identifier}', 'version': published_version.version, - 'id': f'DANDI:{published_version.dandiset.identifier}/{published_version.version}', + 'id': ( + f'{_SCHEMA_CONFIG.instance_name}:' + f'{published_version.dandiset.identifier}/{published_version.version}' + ), 'doi': ( - f'10.80507/dandi.{published_version.dandiset.identifier}/{published_version.version}' + f'{settings.DANDI_DOI_API_PREFIX}/' + f'{_SCHEMA_CONFIG.instance_name}.' + f'{published_version.dandiset.identifier}/{published_version.version}' ), 'url': ( f'{settings.DANDI_WEB_APP_URL}/dandiset/' @@ -116,7 +135,7 @@ def test_published_version_metadata_computed(): 'repository': settings.DANDI_WEB_APP_URL, 'dateCreated': published_version.dandiset.created.isoformat(), 'access': [{'schemaKey': 'AccessRequirements', 'status': AccessType.OpenAccess.value}], - '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{settings.DANDI_SCHEMA_VERSION}/context.json', + '@context': f'https://raw.githubusercontent.com/dandi/schema/master/releases/{DANDI_SCHEMA_VERSION}/context.json', 'assetsSummary': { 'numberOfBytes': 0, 'numberOfFiles': 0, @@ -308,24 +327,18 @@ def test_version_publish_version(asset): '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', - } - ], + 'wasAssociatedWith': [DEFAULT_WAS_ASSOCIATED_WITH], 'schemaKey': 'PublishActivity', }, 'dateCreated': UTC_ISO_TIMESTAMP_RE, 'datePublished': UTC_ISO_TIMESTAMP_RE, 'manifestLocation': [HTTP_URL_RE], - 'identifier': f'DANDI:{publish_version.dandiset.identifier}', + 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{publish_version.dandiset.identifier}', 'version': publish_version.version, - 'id': f'DANDI:{publish_version.dandiset.identifier}/{publish_version.version}', + 'id': ( + f'{_SCHEMA_CONFIG.instance_name}:' + f'{publish_version.dandiset.identifier}/{publish_version.version}' + ), 'url': ( f'{settings.DANDI_WEB_APP_URL}/dandiset/{publish_version.dandiset.identifier}' f'/{publish_version.version}' @@ -543,9 +556,9 @@ def test_version_rest_update(api_client): new_metadata = { '@context': ( 'https://raw.githubusercontent.com/dandi/schema/master/releases/' - f'{settings.DANDI_SCHEMA_VERSION}/context.json' + f'{DANDI_SCHEMA_VERSION}/context.json' ), - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, 'foo': 'bar', 'num': 123, 'list': ['a', 'b', 'c'], @@ -564,13 +577,13 @@ def test_version_rest_update(api_client): url = f'{settings.DANDI_WEB_APP_URL}/dandiset/{draft_version.dandiset.identifier}/draft' saved_metadata = { **new_metadata, - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, 'manifestLocation': [ f'{settings.DANDI_API_URL}/api/dandisets/{draft_version.dandiset.identifier}/versions/draft/assets/' ], 'name': new_name, - 'identifier': f'DANDI:{draft_version.dandiset.identifier}', - 'id': f'DANDI:{draft_version.dandiset.identifier}/draft', + 'identifier': f'{_SCHEMA_CONFIG.instance_name}:{draft_version.dandiset.identifier}', + 'id': f'{_SCHEMA_CONFIG.instance_name}:{draft_version.dandiset.identifier}/draft', 'version': 'draft', 'url': url, 'repository': settings.DANDI_WEB_APP_URL, @@ -634,9 +647,9 @@ def test_version_rest_update_unembargo_in_progress(api_client): new_metadata = { '@context': ( 'https://raw.githubusercontent.com/dandi/schema/master/releases/' - f'{settings.DANDI_SCHEMA_VERSION}/context.json' + f'{DANDI_SCHEMA_VERSION}/context.json' ), - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, 'num': 123, } diff --git a/dandiapi/api/tests/test_webdav.py b/dandiapi/api/tests/test_webdav.py index 1ab109658..009ffcc49 100644 --- a/dandiapi/api/tests/test_webdav.py +++ b/dandiapi/api/tests/test_webdav.py @@ -1,6 +1,6 @@ from __future__ import annotations -from django.conf import settings +from dandischema.consts import DANDI_SCHEMA_VERSION import pytest from dandiapi.api.models.dandiset import Dandiset @@ -18,7 +18,7 @@ def test_asset_atpath_root_path(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'a.txt', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) add_asset_to_version( @@ -27,7 +27,7 @@ def test_asset_atpath_root_path(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'b.txt', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) @@ -73,7 +73,7 @@ def test_asset_atpath_asset(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': path, - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) @@ -140,7 +140,7 @@ def test_asset_atpath_folder(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'foo/bar.txt', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) add_asset_to_version( @@ -149,7 +149,7 @@ def test_asset_atpath_folder(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'foo/baz.txt', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) @@ -209,7 +209,7 @@ def test_asset_atpath_trailing_slash(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'foo', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) @@ -237,7 +237,7 @@ def test_asset_atpath_path_missing(api_client, asset_blob): asset_blob=asset_blob, metadata={ 'path': 'foo/bar.txt', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) diff --git a/dandiapi/api/views/asset.py b/dandiapi/api/views/asset.py index 962f801c4..34daed73d 100644 --- a/dandiapi/api/views/asset.py +++ b/dandiapi/api/views/asset.py @@ -3,7 +3,7 @@ import re from typing import TYPE_CHECKING, cast -from django.conf import settings +from dandischema.consts import DANDI_SCHEMA_VERSION from django.db import transaction from django.http import HttpResponse, HttpResponseRedirect from django_filters import rest_framework as filters @@ -220,7 +220,7 @@ def validate(self, data): # will be caught further up the stack and be converted to a DRF ValidationError validate_asset_path(data['metadata']['path']) - data['metadata'].setdefault('schemaVersion', settings.DANDI_SCHEMA_VERSION) + data['metadata'].setdefault('schemaVersion', DANDI_SCHEMA_VERSION) return data diff --git a/dandiapi/api/views/auth.py b/dandiapi/api/views/auth.py index e9dab98d9..2f2d27f19 100644 --- a/dandiapi/api/views/auth.py +++ b/dandiapi/api/views/auth.py @@ -135,6 +135,7 @@ def user_questionnaire_form_view(request: AuthenticatedRequest) -> HttpResponse: '@janelia.hhmi.org', '@ccf.org', '.ac.uk', + '.mcgill.ca', ] ) diff --git a/dandiapi/api/views/dandiset.py b/dandiapi/api/views/dandiset.py index c0466555a..f0ee4a873 100644 --- a/dandiapi/api/views/dandiset.py +++ b/dandiapi/api/views/dandiset.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING from allauth.socialaccount.models import SocialAccount +from dandischema.conf import get_instance_config from django.contrib.auth.models import User from django.contrib.postgres.lookups import Unaccent from django.db import transaction @@ -434,7 +435,7 @@ def create(self, request: Request): identifier = None if 'identifier' in serializer.validated_data['metadata']: identifier = serializer.validated_data['metadata']['identifier'] - identifier = identifier.removeprefix('DANDI:') + identifier = identifier.removeprefix(f'{get_instance_config().instance_name}:') try: identifier = int(identifier) diff --git a/dandiapi/api/views/info.py b/dandiapi/api/views/info.py index fa2c410ac..69b1752a2 100644 --- a/dandiapi/api/views/info.py +++ b/dandiapi/api/views/info.py @@ -3,6 +3,8 @@ import importlib.metadata from urllib.parse import ParseResult, urlencode, urlparse, urlunparse +from dandischema.conf import get_instance_config +from dandischema.consts import ALLOWED_INPUT_SCHEMAS, DANDI_SCHEMA_VERSION from django.conf import settings from django.urls import reverse from drf_yasg.utils import no_body, swagger_auto_schema @@ -50,6 +52,9 @@ def __init__(self, *args, **kwargs): } ) + # Instance Configuration + instance_config = serializers.JSONField() + # Schema schema_version = serializers.CharField() schema_url = serializers.URLField() @@ -72,9 +77,12 @@ def info_view(request): api_url = f'{settings.DANDI_API_URL}/api' serializer = ApiInfoSerializer( data={ - 'schema_version': settings.DANDI_SCHEMA_VERSION, + # Set exclude_none=False to prevent any fields set to `None` from being set to a + # different default value when the JSON is de-serialized into a Pydantic model. + 'instance_config': get_instance_config().model_dump(mode='json', exclude_none=False), + 'schema_version': DANDI_SCHEMA_VERSION, 'schema_url': get_schema_url(), - 'allowed_schema_versions': settings.ALLOWED_DANDI_SCHEMA_VERSIONS, + 'allowed_schema_versions': ALLOWED_INPUT_SCHEMAS, 'version': importlib.metadata.version('dandiapi'), 'cli-minimal-version': '0.60.0', 'cli-bad-versions': [], diff --git a/dandiapi/api/views/serializers.py b/dandiapi/api/views/serializers.py index 240e58a5f..0132735d1 100644 --- a/dandiapi/api/views/serializers.py +++ b/dandiapi/api/views/serializers.py @@ -3,7 +3,7 @@ from datetime import date, timedelta from typing import TYPE_CHECKING, Any -from django.conf import settings +from dandischema.consts import DANDI_SCHEMA_VERSION from django.contrib.auth.validators import UnicodeUsernameValidator from django.db.models.query_utils import Q from django.utils import timezone @@ -145,7 +145,7 @@ class Meta: validators = [] def validate(self, data): - data['metadata'].setdefault('schemaVersion', settings.DANDI_SCHEMA_VERSION) + data['metadata'].setdefault('schemaVersion', DANDI_SCHEMA_VERSION) return super().validate(data) diff --git a/dandiapi/conftest.py b/dandiapi/conftest.py index 69ab457cd..b34019d3f 100644 --- a/dandiapi/conftest.py +++ b/dandiapi/conftest.py @@ -1,5 +1,6 @@ from __future__ import annotations +from dandischema.conf import get_instance_config import dandischema.digests.dandietag import pytest from pytest_factoryboy import register @@ -72,3 +73,7 @@ def version(request): @pytest.fixture def api_client() -> APIClient: return APIClient() + + +def get_first_allowed_license() -> str: + return sorted(x.value for x in get_instance_config().licenses)[0] diff --git a/dandiapi/settings/base.py b/dandiapi/settings/base.py index 7af8db68b..5eae2f8fd 100644 --- a/dandiapi/settings/base.py +++ b/dandiapi/settings/base.py @@ -7,8 +7,6 @@ from urllib.parse import urlunparse from corsheaders.defaults import default_headers -from dandischema.consts import ALLOWED_INPUT_SCHEMAS -from dandischema.consts import DANDI_SCHEMA_VERSION as _DEFAULT_DANDI_SCHEMA_VERSION import django_stubs_ext from environ import Env from resonant_settings.allauth import * @@ -170,14 +168,6 @@ # Configure the logging level on all DANDI loggers. logging.getLogger('dandiapi').setLevel(_dandi_log_level) -# This is where the schema version should be set. -# It can optionally be overwritten with the environment variable, but that should only be -# considered a temporary fix. -DANDI_SCHEMA_VERSION: str = env.str( - 'DJANGO_DANDI_SCHEMA_VERSION', default=_DEFAULT_DANDI_SCHEMA_VERSION -) -ALLOWED_DANDI_SCHEMA_VERSIONS: list[str] = ALLOWED_INPUT_SCHEMAS - DANDI_ZARR_PREFIX_NAME: str = env.str('DJANGO_DANDI_ZARR_PREFIX_NAME', default='zarr') # Required environment variables @@ -185,11 +175,19 @@ DANDI_API_URL = urlunparse(cast('ParseResult', env.url('DJANGO_DANDI_API_URL'))) DANDI_JUPYTERHUB_URL = urlunparse(cast('ParseResult', env.url('DJANGO_DANDI_JUPYTERHUB_URL'))) +# These are not used by us directly, but are used by dandi-schema. Not including them would modify +# the archive's behavior, so we require they be set. +DANDI_INSTANCE_NAME = env.str('DJANGO_DANDI_INSTANCE_NAME') +DANDI_INSTANCE_IDENTIFIER = env.str('DJANGO_DANDI_INSTANCE_IDENTIFIER') +DANDI_DOI_API_PREFIX = env.str( + 'DJANGO_DANDI_DOI_API_PREFIX' +) # This is used by us directly, but not in all circumstances + +# Non-required environment variabless _dandi_doi_api_url = cast('ParseResult | None', env.url('DJANGO_DANDI_DOI_API_URL', default=None)) DANDI_DOI_API_URL: str | None = urlunparse(_dandi_doi_api_url) if _dandi_doi_api_url else None DANDI_DOI_API_USER: str | None = env.str('DJANGO_DANDI_DOI_API_USER', default=None) DANDI_DOI_API_PASSWORD: str | None = env.str('DJANGO_DANDI_DOI_API_PASSWORD', default=None) -DANDI_DOI_API_PREFIX: str | None = env.str('DJANGO_DANDI_DOI_API_PREFIX', default=None) DANDI_DOI_PUBLISH: bool = env.bool('DJANGO_DANDI_DOI_PUBLISH', default=False) DANDI_VALIDATION_JOB_INTERVAL: int = env.int('DJANGO_DANDI_VALIDATION_JOB_INTERVAL', default=60) diff --git a/dandiapi/zarr/tests/test_ingest_zarr_archive.py b/dandiapi/zarr/tests/test_ingest_zarr_archive.py index e5d056813..9eca0ef2d 100644 --- a/dandiapi/zarr/tests/test_ingest_zarr_archive.py +++ b/dandiapi/zarr/tests/test_ingest_zarr_archive.py @@ -1,6 +1,6 @@ from __future__ import annotations -from django.conf import settings +from dandischema.consts import DANDI_SCHEMA_VERSION import pytest from zarr_checksum.checksum import EMPTY_CHECKSUM @@ -127,7 +127,7 @@ def test_ingest_zarr_archive_modified(zarr_archive_factory, zarr_file_factory): zarr_archive=zarr_archive, metadata={ 'path': 'sample.zarr', - 'schemaVersion': settings.DANDI_SCHEMA_VERSION, + 'schemaVersion': DANDI_SCHEMA_VERSION, }, ) assert asset.size == 100 diff --git a/dev/.env.docker-compose b/dev/.env.docker-compose index 5273c9421..e694e2498 100644 --- a/dev/.env.docker-compose +++ b/dev/.env.docker-compose @@ -12,3 +12,6 @@ DJANGO_INTERNAL_IPS=0.0.0.0/0 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_INSTANCE_NAME=DEV-DANDI +DJANGO_DANDI_INSTANCE_IDENTIFIER=RRID:ABC_123456 +DJANGO_DANDI_DOI_API_PREFIX=10.80507 diff --git a/dev/.env.docker-compose-native b/dev/.env.docker-compose-native index 06c7b7470..db8613aa2 100644 --- a/dev/.env.docker-compose-native +++ b/dev/.env.docker-compose-native @@ -5,3 +5,6 @@ DJANGO_MINIO_STORAGE_URL=http://minioAccessKey:minioSecretKey@localhost:9000/dan 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_INSTANCE_NAME=DEV-DANDI +DJANGO_DANDI_INSTANCE_IDENTIFIER=RRID:ABC_123456 +DJANGO_DANDI_DOI_API_PREFIX=10.80507 diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 7d9fb98f4..8941b80f8 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -23,6 +23,7 @@ services: volumes: - .:/home/vscode/dandi - uv_cache:/home/vscode/uv + - pre-commit_cache:/home/vscode/.cache/pre-commit ports: - 8000:8000 depends_on: @@ -64,6 +65,7 @@ services: volumes: - .:/home/vscode/dandi - uv_cache:/home/vscode/uv + - pre-commit_cache:/home/vscode/.cache/pre-commit depends_on: postgres: condition: service_healthy @@ -74,3 +76,4 @@ services: volumes: uv_cache: + pre-commit_cache: diff --git a/pyproject.toml b/pyproject.toml index 0121d7fa1..bff794d16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,9 +12,9 @@ dependencies = [ # Runtime dependencies, always needed "boto3", "celery", - "dandi", # minimal version is also provided in API /info + "dandi", # minimal version is also provided in API /info # Pin dandischema to exact version to make explicit which schema version is being used - "dandischema==0.11.1", # schema version 0.6.10 + "dandischema==0.12.1", # schema version 0.7.0 "django[argon2]", "django-allauth", "django-auth-style", diff --git a/uv.lock b/uv.lock index 0f256a552..773b6462d 100644 --- a/uv.lock +++ b/uv.lock @@ -1,20 +1,12 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.13" resolution-markers = [ - "python_full_version >= '3.14'", + "python_full_version >= '3.14' and platform_machine != 'arm64' and sys_platform == 'darwin'", + "(python_full_version >= '3.14' and platform_machine == 'arm64') or (python_full_version >= '3.14' and sys_platform != 'darwin')", "python_full_version < '3.14'", ] -[[package]] -name = "acres" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/ba/94b63a9af588fbf7bde25ce44d55456199654a92fb7b2337767198a824b0/acres-0.5.0.tar.gz", hash = "sha256:128b6447bf5df3b6210264feccbfa018b4ac5bd337358319aec6563f99db8f3a", size = 57750, upload-time = "2025-06-04T12:40:30.329Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/e8/806475fe4cdfd8635535d3fa11bd61d19b7cc94b61b9147ebdd2ab4cbbee/acres-0.5.0-py3-none-any.whl", hash = "sha256:fcc32b974b510897de0f041609b4234f9ff03e2e960aea088f63973fb106c772", size = 12703, upload-time = "2025-06-04T12:40:28.745Z" }, -] - [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -125,6 +117,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, +] + [[package]] name = "argon2-cffi" version = "25.1.0" @@ -181,12 +182,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, ] -[[package]] -name = "asciitree" -version = "0.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/885bc91484e1aa8f618f6f0228d76d0e67000b0fdd6090673b777e311913/asciitree-0.3.3.tar.gz", hash = "sha256:4aa4b9b649f85e3fcb343363d97564aa1fb62e249677f2e18a96765145cc0f6e", size = 3951, upload-time = "2016-09-05T19:10:42.681Z" } - [[package]] name = "asgiref" version = "3.10.0" @@ -214,33 +209,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] -[[package]] -name = "bids-validator-deno" -version = "2.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7a/ae/2045dacd02307997b612d7dd0c61092c2b6875d72cad3873d7af26dd7f3a/bids_validator_deno-2.1.1.tar.gz", hash = "sha256:1add452625548607d357c018a5ac1365809dcabf612624e71f5bdbb3d2ec081c", size = 73252, upload-time = "2025-09-29T16:25:21.774Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/08/0ca16cac7c2ad7ed8846691ab4ee4997a1d936d5c29509f3cec71f36a778/bids_validator_deno-2.1.1-py2.py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac27e7070229fb45a80ff04b61de0a37551da751ab3d893a45cbd672daed220e", size = 42418556, upload-time = "2025-09-29T16:25:06.696Z" }, - { url = "https://files.pythonhosted.org/packages/18/4d/d98fc4e6c3ed73de84f7af84c695d676e086678f9e4ec3e2fc66c54b0d78/bids_validator_deno-2.1.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:6082c69cdf4382df0ca82be7591d22444e65b2e2a72d06fb7d87a6e131d6e920", size = 41656505, upload-time = "2025-09-29T16:25:10.639Z" }, - { url = "https://files.pythonhosted.org/packages/15/44/7c8ad5a37d77db13985508aa04d4ee006df4adde8414921760ecd4b642c5/bids_validator_deno-2.1.1-py2.py3-none-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:06a06a27b07a237bcb4eecd09bf656fd8eafb61c77d0f4ae311807db0ac6dba5", size = 43321256, upload-time = "2025-09-29T16:25:13.676Z" }, - { url = "https://files.pythonhosted.org/packages/a8/cf/92cd8c6507068745c55afa0cad60a6ac85d1bd0a17dec41b93f1eab55a5b/bids_validator_deno-2.1.1-py2.py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:538914e927fe38c6cd80458acf31a5a4e3ff52020b5b2dfdea42d3bc49aac5e7", size = 44433972, upload-time = "2025-09-29T16:25:16.439Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b4/0f9b960ef336426df7f2db4920a99d10c6f57a29001696a93854aa3da0bf/bids_validator_deno-2.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:b163ed4976ece0cf21f94f6293281687633110e8bd2e8966064f2f0e2ed88302", size = 43848264, upload-time = "2025-09-29T16:25:19.546Z" }, -] - -[[package]] -name = "bidsschematools" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "acres" }, - { name = "click" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/82/5d/8b940b4149ba11e5ed5325a79f3c75a0e2ec66cb4123d3d1313222550929/bidsschematools-1.1.0.tar.gz", hash = "sha256:71555f2364a27ba6ac7bff869b5181ff64aa180c5e56725180b43012627c0e06", size = 1752873, upload-time = "2025-09-03T14:24:36.587Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/d6/15f1ea70e9dde2fbd5c1ea83db6169e5663c9b20d4854678650ff5cefad7/bidsschematools-1.1.0-py3-none-any.whl", hash = "sha256:aabd6ae19978a5a92777f04fa0740ca9ea4a746075b077d88671d47d46c56653", size = 179191, upload-time = "2025-09-03T14:24:35.2Z" }, -] - [[package]] name = "billiard" version = "4.2.2" @@ -679,27 +647,21 @@ wheels = [ [[package]] name = "dandi" -version = "0.73.0" +version = "0.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "bids-validator-deno" }, - { name = "bidsschematools" }, + { name = "appdirs" }, { name = "click" }, { name = "click-didyoumean" }, - { name = "dandischema" }, + { name = "email-validator" }, { name = "etelemetry" }, { name = "fasteners" }, { name = "fscacher" }, - { name = "hdmf" }, { name = "humanize" }, - { name = "interleave" }, { name = "joblib" }, + { name = "jsonschema" }, { name = "keyring" }, { name = "keyrings-alt" }, - { name = "numcodecs" }, - { name = "nwbinspector" }, - { name = "packaging" }, - { name = "platformdirs" }, { name = "pycryptodomex" }, { name = "pydantic" }, { name = "pynwb" }, @@ -709,15 +671,11 @@ dependencies = [ { name = "ruamel-yaml" }, { name = "semantic-version" }, { name = "tenacity" }, - { name = "tensorstore" }, - { name = "urllib3" }, - { name = "yarl" }, - { name = "zarr" }, - { name = "zarr-checksum" }, + { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/4c/45f771706639b52aea3c48c7a9dbf5ef3bea27b28d90082a82eed36d96c9/dandi-0.73.0.tar.gz", hash = "sha256:5d3eec4b2bb3bfe516d3457609d97860a97532f4c87ea1c15b71c3b83a1c273a", size = 358133, upload-time = "2025-10-13T17:29:02.207Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e0/185a68af1a5e20564e654984b24c7f4e7d42e41d2e038f13d558ad5b1523/dandi-0.17.0.tar.gz", hash = "sha256:5198c9087b1fab0767be8314e382637bfd75ee61092419ce00dd3a341816b99d", size = 143395, upload-time = "2021-05-12T20:54:10.065Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/8d/e0ae1893b6a052e561fb9b6b29c074e8696c52eb849813768a04a3d266ca/dandi-0.73.0-py3-none-any.whl", hash = "sha256:fd5680fa661c056242e95827cb80fd811f66d26dd9c3d5da2eaea22b14cfd43c", size = 362954, upload-time = "2025-10-13T17:29:00.559Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f1/e4ed51dc07f5ccfd44d8a1e2d940b375dc8c3abbb119fd78fdd051d00036/dandi-0.17.0-py3-none-any.whl", hash = "sha256:0dc9d9cabcf083e2169a4fc70c0d1f8ea60762a4dac66f8e27ed6e778f628c67", size = 152512, upload-time = "2021-05-12T20:54:08.554Z" }, ] [[package]] @@ -809,7 +767,7 @@ requires-dist = [ { name = "boto3" }, { name = "celery" }, { name = "dandi" }, - { name = "dandischema", specifier = "==0.11.1" }, + { name = "dandischema", specifier = "==0.12.1" }, { name = "django", extras = ["argon2"] }, { name = "django-allauth" }, { name = "django-auth-style" }, @@ -881,17 +839,19 @@ type = [ [[package]] name = "dandischema" -version = "0.11.1" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonschema", extra = ["format"] }, + { name = "packaging" }, { name = "pydantic", extra = ["email"] }, + { name = "pydantic-settings" }, { name = "requests" }, { name = "zarr-checksum" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/c3/68b59ef8f7d49904af4aa4ea730364f3eb02b94cceaaadf128048cbd6370/dandischema-0.11.1.tar.gz", hash = "sha256:662ca01ec3bd81ca70becc98727b0aadd5b116a40ad257d11216acab0d8dda66", size = 74714, upload-time = "2025-05-15T13:38:37.957Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/b7/b44244184c16c1b1cc69070b53325965285b81843ad7755c429e1ffaa341/dandischema-0.12.1.tar.gz", hash = "sha256:481ed1da9481090d8000b3b2373a0d4876043f48d4cddc3364c18e0a97bd0c22", size = 98659, upload-time = "2025-11-26T20:16:58.672Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/a6/ea4542a2897f8e0438621bc44cd462021c737c84be85e1964f8625ce6509/dandischema-0.11.1-py3-none-any.whl", hash = "sha256:ac80c29a8dad7b4df2e3c7edd5a830fcb31acabe75c78a4b7323097e136d040f", size = 85309, upload-time = "2025-05-15T13:38:36.83Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/959ccdc06e45781d3c41cd552baeaaf563ffb5fe240fbd24efd26b8b2b12/dandischema-0.12.1-py3-none-any.whl", hash = "sha256:88f84cefd7883ce15d5c2f6b92411b1d00fd76468b77950fff25381e17eaab44", size = 118561, upload-time = "2025-11-26T20:16:57.121Z" }, ] [[package]] @@ -903,18 +863,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] -[[package]] -name = "deprecated" -version = "1.2.18" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, -] - [[package]] name = "distlib" version = "0.4.0" @@ -1512,23 +1460,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/e4/a5c84425feece1383ede342086720f8eba4a67e6afc2e4f990d2ce1d7e94/hdmf-4.1.0-py3-none-any.whl", hash = "sha256:d0cffd8e573d77368892d20351ecba07142a8a21b75c7665d732748132d7a696", size = 336653, upload-time = "2025-05-28T19:02:09.808Z" }, ] -[[package]] -name = "hdmf-zarr" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hdmf" }, - { name = "numcodecs" }, - { name = "numpy" }, - { name = "pynwb" }, - { name = "threadpoolctl" }, - { name = "zarr" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/56/707bafae46727e0d69030fac436b96fb4ea8fefc693e661474ed45d12987/hdmf_zarr-0.12.0.tar.gz", hash = "sha256:9fa413fcddfc12e825b99f06ebdbc35439c6fad9f00a44936961b9ffd684e0c0", size = 3106640, upload-time = "2025-10-08T18:27:38.921Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/74/eeff5039149201bead00c27fab2ddf071899307bd578c3eabcd069ba4eba/hdmf_zarr-0.12.0-py3-none-any.whl", hash = "sha256:aaf866122799f547b9594f54fe906e2ffc4e8b9fe1928bf15a037dfcc864c98b", size = 33787, upload-time = "2025-10-08T18:27:37.44Z" }, -] - [[package]] name = "humanize" version = "4.13.0" @@ -1574,15 +1505,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] -[[package]] -name = "interleave" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/73/f06bcda00889d164ee1918126381f2d5e0030a6deaf80c9887e84ba18e31/interleave-0.3.0.tar.gz", hash = "sha256:4c4595e05c5c3286d306db7459972bada9a38088fb8649c1199782c8e4a60a32", size = 15689, upload-time = "2025-01-28T19:48:04.286Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/2b/13b5b2e418bc77574b7215c26be862f72d7850713116641c4ad56add61fd/interleave-0.3.0-py3-none-any.whl", hash = "sha256:09d772693b958c0cbb11e3cd8a6b726c6995c7e12b0530a10ae31a943b458c39", size = 11847, upload-time = "2025-01-28T19:48:03.081Z" }, -] - [[package]] name = "ipython" version = "9.6.0" @@ -1616,15 +1538,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, ] -[[package]] -name = "isodate" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, -] - [[package]] name = "isoduration" version = "20.11.0" @@ -1975,33 +1888,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/d5/ecfa0a06ab0f0bd060a5908edc839d81c8f37da671a69d19cf9f5773595e/memray-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:45c3ac6152915df66ff7df4602a82171b981efcfba89b132458808cd47a57f7f", size = 10258751, upload-time = "2025-08-08T19:47:53.955Z" }, ] -[[package]] -name = "ml-dtypes" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/a7/aad060393123cfb383956dca68402aff3db1e1caffd5764887ed5153f41b/ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9", size = 692316, upload-time = "2025-07-29T18:39:19.454Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/87/1bcc98a66de7b2455dfb292f271452cac9edc4e870796e0d87033524d790/ml_dtypes-0.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5103856a225465371fe119f2fef737402b705b810bd95ad5f348e6e1a6ae21af", size = 663781, upload-time = "2025-07-29T18:38:42.984Z" }, - { url = "https://files.pythonhosted.org/packages/fd/2c/bd2a79ba7c759ee192b5601b675b180a3fd6ccf48ffa27fe1782d280f1a7/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cae435a68861660af81fa3c5af16b70ca11a17275c5b662d9c6f58294e0f113", size = 4956217, upload-time = "2025-07-29T18:38:44.65Z" }, - { url = "https://files.pythonhosted.org/packages/14/f3/091ba84e5395d7fe5b30c081a44dec881cd84b408db1763ee50768b2ab63/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6936283b56d74fbec431ca57ce58a90a908fdbd14d4e2d22eea6d72bb208a7b7", size = 4933109, upload-time = "2025-07-29T18:38:46.405Z" }, - { url = "https://files.pythonhosted.org/packages/bc/24/054036dbe32c43295382c90a1363241684c4d6aaa1ecc3df26bd0c8d5053/ml_dtypes-0.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:d0f730a17cf4f343b2c7ad50cee3bd19e969e793d2be6ed911f43086460096e4", size = 208187, upload-time = "2025-07-29T18:38:48.24Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/7dc3ec6794a4a9004c765e0c341e32355840b698f73fd2daff46f128afc1/ml_dtypes-0.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2db74788fc01914a3c7f7da0763427280adfc9cd377e9604b6b64eb8097284bd", size = 161559, upload-time = "2025-07-29T18:38:50.493Z" }, - { url = "https://files.pythonhosted.org/packages/12/91/e6c7a0d67a152b9330445f9f0cf8ae6eee9b83f990b8c57fe74631e42a90/ml_dtypes-0.5.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93c36a08a6d158db44f2eb9ce3258e53f24a9a4a695325a689494f0fdbc71770", size = 689321, upload-time = "2025-07-29T18:38:52.03Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6c/b7b94b84a104a5be1883305b87d4c6bd6ae781504474b4cca067cb2340ec/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e44a3761f64bc009d71ddb6d6c71008ba21b53ab6ee588dadab65e2fa79eafc", size = 5274495, upload-time = "2025-07-29T18:38:53.797Z" }, - { url = "https://files.pythonhosted.org/packages/5b/38/6266604dffb43378055394ea110570cf261a49876fc48f548dfe876f34cc/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdf40d2aaabd3913dec11840f0d0ebb1b93134f99af6a0a4fd88ffe924928ab4", size = 5285422, upload-time = "2025-07-29T18:38:56.603Z" }, - { url = "https://files.pythonhosted.org/packages/7c/88/8612ff177d043a474b9408f0382605d881eeb4125ba89d4d4b3286573a83/ml_dtypes-0.5.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:aec640bd94c4c85c0d11e2733bd13cbb10438fb004852996ec0efbc6cacdaf70", size = 661182, upload-time = "2025-07-29T18:38:58.414Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2b/0569a5e88b29240d373e835107c94ae9256fb2191d3156b43b2601859eff/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bda32ce212baa724e03c68771e5c69f39e584ea426bfe1a701cb01508ffc7035", size = 4956187, upload-time = "2025-07-29T18:39:00.611Z" }, - { url = "https://files.pythonhosted.org/packages/51/66/273c2a06ae44562b104b61e6b14444da00061fd87652506579d7eb2c40b1/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c205cac07d24a29840c163d6469f61069ce4b065518519216297fc2f261f8db9", size = 4930911, upload-time = "2025-07-29T18:39:02.405Z" }, - { url = "https://files.pythonhosted.org/packages/93/ab/606be3e87dc0821bd360c8c1ee46108025c31a4f96942b63907bb441b87d/ml_dtypes-0.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:cd7c0bb22d4ff86d65ad61b5dd246812e8993fbc95b558553624c33e8b6903ea", size = 216664, upload-time = "2025-07-29T18:39:03.927Z" }, - { url = "https://files.pythonhosted.org/packages/30/a2/e900690ca47d01dffffd66375c5de8c4f8ced0f1ef809ccd3b25b3e6b8fa/ml_dtypes-0.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:9d55ea7f7baf2aed61bf1872116cefc9d0c3693b45cae3916897ee27ef4b835e", size = 160203, upload-time = "2025-07-29T18:39:05.671Z" }, - { url = "https://files.pythonhosted.org/packages/53/21/783dfb51f40d2660afeb9bccf3612b99f6a803d980d2a09132b0f9d216ab/ml_dtypes-0.5.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:e12e29764a0e66a7a31e9b8bf1de5cc0423ea72979f45909acd4292de834ccd3", size = 689324, upload-time = "2025-07-29T18:39:07.567Z" }, - { url = "https://files.pythonhosted.org/packages/09/f7/a82d249c711abf411ac027b7163f285487f5e615c3e0716c61033ce996ab/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19f6c3a4f635c2fc9e2aa7d91416bd7a3d649b48350c51f7f715a09370a90d93", size = 5275917, upload-time = "2025-07-29T18:39:09.339Z" }, - { url = "https://files.pythonhosted.org/packages/7f/3c/541c4b30815ab90ebfbb51df15d0b4254f2f9f1e2b4907ab229300d5e6f2/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ab039ffb40f3dc0aeeeba84fd6c3452781b5e15bef72e2d10bcb33e4bbffc39", size = 5285284, upload-time = "2025-07-29T18:39:11.532Z" }, -] - [[package]] name = "more-itertools" version = "10.8.0" @@ -2136,15 +2022,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] -[[package]] -name = "natsort" -version = "8.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, -] - [[package]] name = "nodeenv" version = "1.9.1" @@ -2154,22 +2031,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] -[[package]] -name = "numcodecs" -version = "0.15.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecated" }, - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/fc/bb532969eb8236984ba65e4f0079a7da885b8ac0ce1f0835decbb3938a62/numcodecs-0.15.1.tar.gz", hash = "sha256:eeed77e4d6636641a2cc605fbc6078c7a8f2cc40f3dfa2b3f61e52e6091b04ff", size = 6267275, upload-time = "2025-02-10T10:23:33.254Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/57/acbc54b3419e5be65015e47177c76c0a73e037fd3ae2cde5808169194d4d/numcodecs-0.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3d82b70500cf61e8d115faa0d0a76be6ecdc24a16477ee3279d711699ad85f3", size = 1688220, upload-time = "2025-02-10T10:23:23.79Z" }, - { url = "https://files.pythonhosted.org/packages/b6/56/9863fa6dc679f40a31bea5e9713ee5507a31dcd3ee82ea4b1a9268ce52e8/numcodecs-0.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1d471a1829ce52d3f365053a2bd1379e32e369517557c4027ddf5ac0d99c591e", size = 1180294, upload-time = "2025-02-10T10:23:25.533Z" }, - { url = "https://files.pythonhosted.org/packages/fa/91/d96999b41e3146b6c0ce6bddc5ad85803cb4d743c95394562c2a4bb8cded/numcodecs-0.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dfdea4a67108205edfce99c1cb6cd621343bc7abb7e16a041c966776920e7de", size = 8834323, upload-time = "2025-02-10T10:23:27.46Z" }, - { url = "https://files.pythonhosted.org/packages/c3/32/233e5ede6568bdb044e6f99aaa9fa39827ff3109c6487fc137315f733586/numcodecs-0.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:a4f7bdb26f1b34423cb56d48e75821223be38040907c9b5954eeb7463e7eb03c", size = 831955, upload-time = "2025-02-10T10:23:30.601Z" }, -] - [[package]] name = "numpy" version = "2.3.3" @@ -2222,29 +2083,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" }, ] -[[package]] -name = "nwbinspector" -version = "0.6.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "click" }, - { name = "fsspec" }, - { name = "hdmf-zarr" }, - { name = "isodate" }, - { name = "jsonschema" }, - { name = "natsort" }, - { name = "packaging" }, - { name = "pynwb" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/45/23/d50bb1dfef6eff622dff58ec64077f6b51ce9191ee4f093881b1394a6ddd/nwbinspector-0.6.5.tar.gz", hash = "sha256:93023bcd72c73add18644211fb26f6b005e012ec9c6a8b3f53e136ddeb58af16", size = 591999, upload-time = "2025-07-25T18:05:22.529Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/e7/01fd709430ee0178db611f1db9652bfca71599799c602101e13ce5bdeedc/nwbinspector-0.6.5-py3-none-any.whl", hash = "sha256:f0219a9b06482b07415e7beebddf3ae7d97371cc178e7bc05c7d3edf7285d135", size = 64409, upload-time = "2025-07-25T18:05:21.4Z" }, -] - [[package]] name = "oauthlib" version = "3.3.1" @@ -2616,6 +2454,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, ] +[[package]] +name = "pydantic-settings" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -2786,6 +2638,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + [[package]] name = "pytz" version = "2025.2" @@ -3000,6 +2861,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/fa/3234f913fe9a6525a7b97c6dad1f51e72b917e6872e051a5e2ffd8b16fbb/ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83", size = 137970, upload-time = "2025-09-22T19:51:09.472Z" }, { url = "https://files.pythonhosted.org/packages/ef/ec/4edbf17ac2c87fa0845dd366ef8d5852b96eb58fcd65fc1ecf5fe27b4641/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27", size = 739639, upload-time = "2025-09-22T19:51:10.566Z" }, { url = "https://files.pythonhosted.org/packages/15/18/b0e1fafe59051de9e79cdd431863b03593ecfa8341c110affad7c8121efc/ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640", size = 764456, upload-time = "2025-09-22T19:51:11.736Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cd/150fdb96b8fab27fe08d8a59fe67554568727981806e6bc2677a16081ec7/ruamel_yaml_clib-0.2.14-cp314-cp314-win32.whl", hash = "sha256:9b4104bf43ca0cd4e6f738cb86326a3b2f6eef00f417bd1e7efb7bdffe74c539", size = 102394, upload-time = "2025-11-14T21:57:36.703Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/a3fa40084558c7e1dc9546385f22a93949c890a8b2e445b2ba43935f51da/ruamel_yaml_clib-0.2.14-cp314-cp314-win_amd64.whl", hash = "sha256:13997d7d354a9890ea1ec5937a219817464e5cc344805b37671562a401ca3008", size = 122673, upload-time = "2025-11-14T21:57:38.177Z" }, ] [[package]] @@ -3045,8 +2908,8 @@ name = "secretstorage" version = "3.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography" }, - { name = "jeepney" }, + { name = "cryptography", marker = "(python_full_version < '3.14' and platform_machine != 'arm64') or platform_machine == 'arm64' or sys_platform != 'darwin'" }, + { name = "jeepney", marker = "(python_full_version < '3.14' and platform_machine != 'arm64') or platform_machine == 'arm64' or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748, upload-time = "2025-09-09T16:42:13.859Z" } wheels = [ @@ -3129,23 +2992,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] -[[package]] -name = "tensorstore" -version = "0.1.78" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ml-dtypes" }, - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/ee/05eb424437f4db63331c90e4605025eedc0f71da3faff97161d5d7b405af/tensorstore-0.1.78.tar.gz", hash = "sha256:e26074ffe462394cf54197eb76d6569b500f347573cd74da3f4dd5f510a4ad7c", size = 6913502, upload-time = "2025-10-06T17:44:29.649Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/36/cfb5a2acf9005896c88f80b93c2aee42f00fab9d0045369fef6e1b297242/tensorstore-0.1.78-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:8a1d0ae7996c80f2e623be5b8cfbc32a307d08dfef3d2dcb455f592908ecd46d", size = 15727334, upload-time = "2025-10-06T17:44:19.93Z" }, - { url = "https://files.pythonhosted.org/packages/54/cd/d1bcc3aab5be4298616dbc060b5aa2012b686270aaa16a9579c7945d0a1c/tensorstore-0.1.78-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:311846cfb2d644cd4a7861005e521a79816093e76d7924c83de5d06ca323067e", size = 13780722, upload-time = "2025-10-06T17:44:21.822Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3b/b0bb4440a9d67859b1abb367e436c62b0a27991dd7109f20be9dabff488f/tensorstore-0.1.78-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630538a66eb9964bd2975c4e09ae83be9984f2e4ebd5f7969983137bfda92071", size = 18157269, upload-time = "2025-10-06T17:44:23.743Z" }, - { url = "https://files.pythonhosted.org/packages/68/d6/d95cde18ca2475bf317051b2be168cc963c5cfcd67e9c59786326ccdca53/tensorstore-0.1.78-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6886bec93b8ba22f83c4dc9e7c1ee20b11025ea9a5a839de21d0cbf7fd7aada2", size = 20060053, upload-time = "2025-10-06T17:44:25.942Z" }, - { url = "https://files.pythonhosted.org/packages/db/a2/dbd1af0e97d5d549051309d72c6e3f2fe81fae636f9db3692d21adc9c731/tensorstore-0.1.78-cp313-cp313-win_amd64.whl", hash = "sha256:e0073de8fa3074bc4cc92ced0210310fd89851899faf42a5ba256f0ba87d095c", size = 12711250, upload-time = "2025-10-06T17:44:27.926Z" }, -] - [[package]] name = "textual" version = "6.3.0" @@ -3162,15 +3008,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/2a/bca677b0b05ee77b4105f73db0d8ef231a9f1db154d69388abd5c73f9dcc/textual-6.3.0-py3-none-any.whl", hash = "sha256:ec908b4b008662e7670af4a3e7c773847066b0950b1c50126c72fa939b514c97", size = 711457, upload-time = "2025-10-11T11:16:59.754Z" }, ] -[[package]] -name = "threadpoolctl" -version = "3.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, -] - [[package]] name = "tox" version = "4.31.0" @@ -3454,45 +3291,6 @@ brotli = [ { name = "brotli" }, ] -[[package]] -name = "wrapt" -version = "1.17.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, - { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, - { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, - { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, - { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, - { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, - { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, - { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, - { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, - { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, - { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, - { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, - { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, - { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, - { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, - { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, - { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, - { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, -] - [[package]] name = "yarl" version = "1.22.0" @@ -3571,21 +3369,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] -[[package]] -name = "zarr" -version = "2.18.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asciitree" }, - { name = "fasteners", marker = "sys_platform != 'emscripten'" }, - { name = "numcodecs" }, - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/1d/01cf9e3ab2d85190278efc3fca9f68563de35ae30ee59e7640e3af98abe3/zarr-2.18.7.tar.gz", hash = "sha256:b2b8f66f14dac4af66b180d2338819981b981f70e196c9a66e6bfaa9e59572f5", size = 3604558, upload-time = "2025-04-09T07:59:28.482Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/d8/9ffd8c237b3559945bb52103cf0eed64ea098f7b7f573f8d2962ef27b4b2/zarr-2.18.7-py3-none-any.whl", hash = "sha256:ac3dc4033e9ae4e9d7b5e27c97ea3eaf1003cc0a07f010bd83d5134bf8c4b223", size = 211273, upload-time = "2025-04-09T07:59:27.039Z" }, -] - [[package]] name = "zarr-checksum" version = "0.4.7" diff --git a/web/src/components/DandisetList.vue b/web/src/components/DandisetList.vue index bb11b403d..30c9349a6 100644 --- a/web/src/components/DandisetList.vue +++ b/web/src/components/DandisetList.vue @@ -48,7 +48,7 @@ {{ item.dandiset.embargo_status }} - DANDI:{{ item.dandiset.identifier }} + {{ archiveName }}:{{ item.dandiset.identifier }} ยท Contact {{ item.dandiset.contact_person }} ยท @@ -79,11 +79,12 @@