Skip to content

Commit 1aed085

Browse files
candleindarkmvandenburghjjnesbitt
authored
Add vendorization support (#2584)
* feat: have the `/info/` endpoint provide instance config info * test: update `test_rest_info()` To include the output of instance config value * tmp: temporarily set the dandischema dependency to the devendorize branch * test: provide env var to set schema config instance name * feat: make dandischema instance config available through Django settings * feat: provide instance name through dandischema instance config instead of a hardcoded value * feat: provide instance name through dandischema instance config instead of a hardcoded value * feat: replace `"DANDI` with instance name in `DandisetList` vue component * ci: add DJANGO_DANDI_INSTANCE_NAME env var to frontend CI * feat: replace hardcoded RRID with one set in schema instance config * ci: add instance identifier to backend CI env * feat: replace the use of hardcoded license * feat: use dandiarchive's version in producing `PublishActivity` metadata Per @yarikoptic this is an improvement over the hardcoded '0.1.0' * feat: vendorize software name in `PublishActivity` metadata * test: update tests for the `PublishActivity` metadata changes * style: replace use of `dandiapi.__version__` with `importlib.metadata.version` Co-authored-by: Mike VanDenburgh <37340715+mvandenburgh@users.noreply.github.com> * rf: remove outdated patching of JSON schema For discussion about this removal, see #2584 (comment) for details * style: replace use of `dandiapi.__version__` with `importlib.metadata.version` in tests * rf: import `dandischema.conf.get_instance_config` as `get_schema_instance_config` Importing the function under this name emphasizes the returned config pertains to dandischema and its usage * feat: call avoid using `dandiapi/settings/base.py` to store schema instance config `dandiapi/settings/base.py` is reserved for used for configuring settings, possibly for the Django app alone. See #2584 (comment) for rationale for the change. * revert: revert naming of API service in generated asset metadata * Always use first sorted license value in create_dev_dandiset * Don't rename `get_instance_config` to `get_schema_instance_config` * Don't use global variables for instance config * Reuse identical schema object for tests * Require DJANGO_DANDI_INSTANCE_IDENTIFIER env var * Use dummy instance config values for local dev and CI * Revert change of version value in PublishableMetadataMixin * Update tests regarding Pydantic and None values This more directly tests the issue we care about. As it was written, the test could still pass unintentionally, if the default values were the same as the values we had set them to. * Require DJANGO_DANDI_DOI_API_PREFIX env var * Use schema vendorization for DOIs * Use schema vendorization for id/identifier * Revert unnecessary change to `$route.query` * Use function to supply default license value * Fix linting errors * Fix malformed license field in create_dev_dandiset * Move `get_default_license` to conftest.py and rename * Set dandischema dependency to 0.12.0 --------- Co-authored-by: Mike VanDenburgh <37340715+mvandenburgh@users.noreply.github.com> Co-authored-by: Jacob Nesbitt <jjnesbitt2@gmail.com>
1 parent 2000b84 commit 1aed085

File tree

26 files changed

+256
-358
lines changed

26 files changed

+256
-358
lines changed

.github/workflows/backend-ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ jobs:
5353
DJANGO_DANDI_WEB_APP_URL: http://localhost:8085
5454
DJANGO_DANDI_API_URL: http://localhost:8000
5555
DJANGO_DANDI_JUPYTERHUB_URL: https://hub.dandiarchive.org/
56+
DJANGO_DANDI_INSTANCE_NAME: DEV-DANDI
57+
DJANGO_DANDI_INSTANCE_IDENTIFIER: "RRID:ABC_123456"
58+
DJANGO_DANDI_DOI_API_PREFIX: "10.80507"
5659
steps:
5760
- uses: actions/checkout@v5
5861
with:

.github/workflows/frontend-ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ jobs:
7878
DJANGO_DANDI_WEB_APP_URL: http://localhost:8085
7979
DJANGO_DANDI_API_URL: http://localhost:8000
8080
DJANGO_DANDI_JUPYTERHUB_URL: https://hub.dandiarchive.org/
81+
DJANGO_DANDI_INSTANCE_NAME: DEV-DANDI
82+
DJANGO_DANDI_INSTANCE_IDENTIFIER: "RRID:ABC_123456"
83+
DJANGO_DANDI_DOI_API_PREFIX: "10.80507"
8184

8285
# Web client env vars
8386
VITE_APP_DANDI_API_ROOT: http://localhost:8000/api/

dandiapi/api/doi.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
from typing import TYPE_CHECKING
55

6+
from dandischema.conf import get_instance_config
67
from django.conf import settings
78
import requests
89

@@ -29,10 +30,11 @@ def _generate_doi_data(version: Version):
2930

3031
publish = settings.DANDI_DOI_PUBLISH
3132
# Use the DANDI test datacite instance as a placeholder if PREFIX isn't set
32-
prefix = settings.DANDI_DOI_API_PREFIX or '10.80507'
33+
prefix = settings.DANDI_DOI_API_PREFIX
34+
instance_name: str = get_instance_config().instance_name
3335
dandiset_id = version.dandiset.identifier
3436
version_id = version.version
35-
doi = f'{prefix}/dandi.{dandiset_id}/{version_id}'
37+
doi = f'{prefix}/{instance_name.lower()}.{dandiset_id}/{version_id}'
3638
metadata = version.metadata
3739
metadata['doi'] = doi
3840
return (doi, to_datacite(metadata, publish=publish))

dandiapi/api/management/commands/create_dev_dandiset.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from uuid import uuid4
44

5+
from dandischema.conf import get_instance_config
56
from django.conf import settings
67
from django.contrib.auth.models import User
78
from django.core.files.uploadedfile import SimpleUploadedFile
@@ -32,7 +33,7 @@ def create_dev_dandiset(*, name: str, email: str, num_extra_owners: int):
3233

3334
version_metadata = {
3435
'description': 'An informative description',
35-
'license': ['spdx:CC0-1.0'],
36+
'license': [sorted(x.value for x in get_instance_config().licenses)[0]],
3637
}
3738
dandiset, draft_version = create_open_dandiset(
3839
user=owner, version_name=name, version_metadata=version_metadata

dandiapi/api/models/metadata.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
from typing import TYPE_CHECKING
44
from uuid import uuid4
55

6+
from dandischema.conf import get_instance_config
7+
68
if TYPE_CHECKING:
79
import datetime
810

911

1012
class PublishableMetadataMixin:
1113
@classmethod
1214
def published_by(cls, now: datetime.datetime):
15+
schema_config = get_instance_config()
16+
1317
return {
1418
'id': uuid4().urn,
1519
'name': 'DANDI publish',
@@ -20,8 +24,8 @@ def published_by(cls, now: datetime.datetime):
2024
'wasAssociatedWith': [
2125
{
2226
'id': uuid4().urn,
23-
'identifier': 'RRID:SCR_017571',
24-
'name': 'DANDI API',
27+
'identifier': schema_config.instance_identifier,
28+
'name': f'{schema_config.instance_name} API',
2529
# TODO: version the API
2630
'version': '0.1.0',
2731
'schemaKey': 'Software',

dandiapi/api/models/version.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
from typing import TypedDict
66

7+
from dandischema.conf import get_instance_config
78
from dandischema.models import AccessType
89
from django.conf import settings
910
from django.contrib.postgres.indexes import HashIndex
@@ -229,6 +230,7 @@ def _populate_access_metadata(self):
229230
def _populate_metadata(self):
230231
from dandiapi.api.manifests import manifest_location
231232

233+
schema_config = get_instance_config()
232234
metadata = {
233235
**self.metadata,
234236
'@context': (
@@ -237,9 +239,9 @@ def _populate_metadata(self):
237239
),
238240
'manifestLocation': manifest_location(self),
239241
'name': self.name,
240-
'identifier': f'DANDI:{self.dandiset.identifier}',
242+
'identifier': (f'{schema_config.instance_name}:{self.dandiset.identifier}'),
241243
'version': self.version,
242-
'id': f'DANDI:{self.dandiset.identifier}/{self.version}',
244+
'id': (f'{schema_config.instance_name}:{self.dandiset.identifier}/{self.version}'),
243245
'repository': settings.DANDI_WEB_APP_URL,
244246
'url': (
245247
f'{settings.DANDI_WEB_APP_URL}/dandiset/{self.dandiset.identifier}/{self.version}'

dandiapi/api/services/metadata/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import TYPE_CHECKING
44

55
from celery.utils.log import get_task_logger
6+
from dandischema.conf import get_instance_config
67
import dandischema.exceptions
78
from dandischema.metadata import aggregate_assets_summary, validate
89
from django.conf import settings
@@ -108,6 +109,8 @@ def version_aggregate_assets_summary(version: Version) -> None:
108109

109110
def validate_version_metadata(*, version: Version) -> None:
110111
def _build_validatable_version_metadata(version: Version) -> dict:
112+
schema_config = get_instance_config()
113+
111114
# since Version.Status.VALID is a proxy for a version being publishable, we need to
112115
# validate against the PublishedDandiset schema even though we lack several things
113116
# 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:
116119
metadata_for_validation = publishable_version.metadata
117120

118121
metadata_for_validation['id'] = (
119-
f'DANDI:{publishable_version.dandiset.identifier}/{publishable_version.version}'
122+
f'{schema_config.instance_name}:'
123+
f'{publishable_version.dandiset.identifier}/{publishable_version.version}'
120124
)
121125
metadata_for_validation['url'] = (
122126
f'{settings.DANDI_WEB_APP_URL}/dandiset/'
123127
f'{publishable_version.dandiset.identifier}/{publishable_version.version}'
124128
)
125-
metadata_for_validation['doi'] = '10.80507/dandi.123456/0.123456.1234'
129+
metadata_for_validation['doi'] = (
130+
f'{schema_config.doi_prefix}/{schema_config.instance_name.lower()}.123456/0.123456.1234'
131+
)
126132
metadata_for_validation['assetsSummary'] = {
127133
'schemaKey': 'AssetsSummary',
128134
'numberOfBytes': 1

dandiapi/api/services/publish/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import datetime
55
from typing import TYPE_CHECKING
66

7+
from dandischema.conf import get_instance_config
78
from dandischema.metadata import aggregate_assets_summary, validate
89
from django.contrib.auth.models import User
910
from django.db import transaction
@@ -182,7 +183,10 @@ def _publish_dandiset(dandiset_id: int, user_id: int) -> None:
182183
old_version.save()
183184

184185
# Inject a dummy DOI so the metadata is valid
185-
new_version.metadata['doi'] = '10.80507/dandi.123456/0.123456.1234'
186+
schema_config = get_instance_config()
187+
new_version.metadata['doi'] = (
188+
f'{schema_config.doi_prefix}/{schema_config.instance_name.lower()}.123456/0.123456.1234'
189+
)
186190

187191
validate(new_version.metadata, schema_key='PublishedDandiset', json_validation=True)
188192

dandiapi/api/tests/factories.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import hashlib
55

66
from allauth.socialaccount.models import SocialAccount
7+
from dandischema.conf import get_instance_config
78
from dandischema.models import AccessType
89
from django.conf import settings
910
from django.contrib.auth.models import User
@@ -113,6 +114,8 @@ class Meta:
113114

114115
@factory.lazy_attribute
115116
def metadata(self) -> dict:
117+
from dandiapi.conftest import get_first_allowed_license
118+
116119
metadata = {
117120
**faker.Faker().pydict(value_types=['str', 'float', 'int']),
118121
'schemaVersion': settings.DANDI_SCHEMA_VERSION,
@@ -134,7 +137,7 @@ def metadata(self) -> dict:
134137
'schemaKey': 'Person',
135138
}
136139
],
137-
'license': ['spdx:CC0-1.0'],
140+
'license': [get_first_allowed_license()],
138141
}
139142
# Remove faked data that might conflict with the schema types
140143
for key in ['about']:
@@ -162,7 +165,9 @@ class DraftVersionFactory(BaseVersionFactory):
162165

163166
class PublishedVersionFactory(BaseVersionFactory):
164167
doi = factory.LazyAttribute(
165-
lambda self: f'10.80507/dandi.{self.dandiset.identifier}/{self.version}'
168+
lambda self: f'{get_instance_config().doi_prefix}/'
169+
f'{get_instance_config().instance_name}.'
170+
f'{self.dandiset.identifier}/{self.version}'
166171
)
167172
status = Version.Status.PUBLISHED
168173

dandiapi/api/tests/fuzzy.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import re
44

5+
from dandischema.conf import get_instance_config
6+
57

68
class Re:
79
def __init__(self, pattern):
@@ -23,12 +25,22 @@ def __hash__(self):
2325
return hash(self.pattern)
2426

2527

28+
schema_config = get_instance_config()
29+
2630
TIMESTAMP_RE = Re(r'\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}\.\d{6}Z')
2731
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}')
2832
DATE_RE = Re(r'\d{4}-\d{2}-\d{2}')
2933
DANDISET_ID_RE = Re(r'\d{6}')
30-
DANDISET_SCHEMA_ID_RE = Re(r'DANDI:\d{6}')
34+
DANDISET_SCHEMA_ID_RE = Re(rf'{schema_config.instance_name}:\d{{6}}')
3135
VERSION_ID_RE = Re(r'0\.\d{6}\.\d{4}')
3236
HTTP_URL_RE = Re(r'http[s]?\://[^/]+(/[^/]+)*[/]?(&.+)?')
3337
UUID_RE = Re(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
3438
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}')
39+
40+
DEFAULT_WAS_ASSOCIATED_WITH = {
41+
'id': URN_RE,
42+
'identifier': schema_config.instance_identifier,
43+
'name': f'{schema_config.instance_name} API',
44+
'version': '0.1.0',
45+
'schemaKey': 'Software',
46+
}

0 commit comments

Comments
 (0)