Skip to content

Commit e0f94f4

Browse files
authored
Merge pull request #1732 from dandi/rf-schema-version
Allow for client to provide records with newer COMPATIBLE or older (upgradable) version of schema to the server
2 parents a79fffc + 0a0c59c commit e0f94f4

File tree

2 files changed

+118
-43
lines changed

2 files changed

+118
-43
lines changed

dandi/dandiapi.py

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import click
2020
from dandischema import models
21+
import dandischema.consts
22+
from packaging.version import Version as PackagingVersion
2123
from pydantic import BaseModel, Field, PrivateAttr
2224
import requests
2325
import tenacity
@@ -646,12 +648,21 @@ def create_dandiset(
646648

647649
def check_schema_version(self, schema_version: str | None = None) -> None:
648650
"""
649-
Confirms that the server is using the same version of the DANDI schema
650-
as the client. If it is not, a `SchemaVersionError` is raised.
651+
Confirms that the given schema version at the client is "compatible" with the server.
651652
652-
:param schema_version: the schema version to confirm that the server
653-
uses; if not set, the schema version for the installed
654-
``dandischema`` library is used
653+
Compatibility here means that the server's schema version can be either
654+
655+
- lower than client has, but within the same MAJOR.MINOR component of the version
656+
number for 0.x series, and same MAJOR version for/after 1.x series;
657+
- the same;
658+
- higher than the client has, but only if the client's schema version is listed
659+
among the server's `allowed_schema_versions` (as returned by the `/info` API endpoint),
660+
or if not there -- `dandischema.consts.ALLOWED_INPUT_SCHEMAS` is consulted.
661+
662+
If neither of above, a `SchemaVersionError` is raised.
663+
664+
:param schema_version: the schema version to be confirmed for compatibility with the server;
665+
if not set, the schema version for the installed ``dandischema`` library is used.
655666
"""
656667
if schema_version is None:
657668
schema_version = models.get_schema_version()
@@ -662,11 +673,48 @@ def check_schema_version(self, schema_version: str | None = None) -> None:
662673
"Server did not provide schema_version in /info/;"
663674
f" returned {server_info!r}"
664675
)
665-
if server_schema_version != schema_version:
676+
server_ver, our_ver = PackagingVersion(server_schema_version), PackagingVersion(
677+
schema_version
678+
)
679+
if server_ver > our_ver:
680+
# TODO: potentially adjust here if name would be different: see
681+
# https://github.com/dandi/dandi-archive/issues/2624
682+
allowed_schema_versions = server_info.get(
683+
"allowed_schema_versions", dandischema.consts.ALLOWED_INPUT_SCHEMAS
684+
)
685+
if schema_version not in allowed_schema_versions:
686+
raise SchemaVersionError(
687+
f"Server uses schema version {server_schema_version};"
688+
f" client only supports prior {schema_version} and it"
689+
f" is not among any of the allowed upgradable schema versions"
690+
f" ({', '.join(allowed_schema_versions)}) . You may need to"
691+
" upgrade dandi and/or dandischema."
692+
)
693+
694+
# TODO: check current server behavior which is likely to just not care!
695+
# So that is where server might need to provide support for upgrades upon
696+
# providing metadata.
697+
elif (
698+
server_ver.major == 0 and server_ver.release[:2] != our_ver.release[:2]
699+
) or (
700+
server_ver.major != our_ver.major
701+
): # MAJOR, MINOR within 0.x.y and MAJOR within 1.x.y
666702
raise SchemaVersionError(
667-
f"Server requires schema version {server_schema_version};"
668-
f" client only supports {schema_version}. You may need to"
669-
" upgrade dandi and/or dandischema."
703+
f"Server uses older incompatible schema version {server_schema_version};"
704+
f" client supports {schema_version}."
705+
)
706+
elif server_ver < our_ver:
707+
# Compatible older server version -- all good, but inform the user
708+
# TODO: potentially downgrade the record to match the schema,
709+
# see https://github.com/dandi/dandi-schema/issues/343
710+
lgr.warning(
711+
"Server uses schema version %s older than client's %s (dandischema library %s). "
712+
"Server might fail to validate such assets and you might not be able to "
713+
"publish this dandiset until server is upgraded. "
714+
"Alternatively, you may downgrade dandischema and reupload.",
715+
server_ver,
716+
our_ver,
717+
dandischema.__version__,
670718
)
671719

672720
def get_asset(self, asset_id: str) -> BaseRemoteAsset:

dandi/tests/test_dandiapi.py

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -272,35 +272,62 @@ def test_remote_asset_json_dict(text_dandiset: SampleDandiset) -> None:
272272
}
273273

274274

275+
@pytest.mark.parametrize(
276+
"server_schema_version,local_schema_version,should_raise,expected_message_start",
277+
[
278+
# Current/identity matching -- always ok
279+
(get_schema_version(), None, False, None),
280+
(get_schema_version(), get_schema_version(), False, None),
281+
("0.6.7", "0.6.7", False, None),
282+
# Less - is not good since we might be missing fields and it is easy
283+
# for client to upgrade.
284+
(
285+
"4.5.6",
286+
"4.5.5",
287+
True,
288+
"Server uses schema version 4.5.6; client only supports prior 4.5.5 "
289+
"and it is not among any of the allowed upgradable schema versions",
290+
),
291+
(
292+
"0.6.7",
293+
"0.3.0",
294+
True,
295+
"Server uses schema version 0.6.7; client only supports prior 0.3.0",
296+
),
297+
(
298+
"0.6.7",
299+
"0.6.6", # can be upgraded and thus uploaded!
300+
False,
301+
None,
302+
),
303+
# Now - incompatible, for 0.x -- rely on MAJOR.MINOR
304+
(
305+
"0.6.7",
306+
"0.7.0",
307+
True,
308+
"Server uses older incompatible schema version 0.6.7; client supports 0.7.0",
309+
),
310+
# After 1.x -- rely on MAJOR.
311+
("1.0.0", "1.2.3", False, None),
312+
("1.6.7", "1.7.0", False, None),
313+
("1.6.7", "1.8.3", False, None),
314+
(
315+
"1.6.7",
316+
"2.7.0",
317+
True,
318+
"Server uses older incompatible schema version 1.6.7; client supports 2.7.0",
319+
),
320+
],
321+
)
275322
@responses.activate
276-
def test_check_schema_version_matches_default() -> None:
277-
server_info = {
278-
"schema_version": get_schema_version(),
279-
"version": "0.0.0",
280-
"services": {
281-
"api": {"url": "https://test.nil/api"},
282-
},
283-
"cli-minimal-version": "0.0.0",
284-
"cli-bad-versions": [],
285-
}
286-
responses.add(
287-
responses.GET,
288-
"https://test.nil/server-info",
289-
json=server_info,
290-
)
291-
responses.add(
292-
responses.GET,
293-
"https://test.nil/api/info/",
294-
json=server_info,
295-
)
296-
client = DandiAPIClient("https://test.nil/api")
297-
client.check_schema_version()
298-
299-
300-
@responses.activate
301-
def test_check_schema_version_mismatch() -> None:
323+
def test_check_schema_version(
324+
server_schema_version: str,
325+
local_schema_version: str | None,
326+
should_raise: bool,
327+
expected_message_start: str | None,
328+
) -> None:
302329
server_info = {
303-
"schema_version": "4.5.6",
330+
"schema_version": server_schema_version,
304331
"version": "0.0.0",
305332
"services": {
306333
"api": {"url": "https://test.nil/api"},
@@ -319,13 +346,13 @@ def test_check_schema_version_mismatch() -> None:
319346
json=server_info,
320347
)
321348
client = DandiAPIClient("https://test.nil/api")
322-
with pytest.raises(SchemaVersionError) as excinfo:
323-
client.check_schema_version("1.2.3")
324-
assert (
325-
str(excinfo.value)
326-
== "Server requires schema version 4.5.6; client only supports 1.2.3. "
327-
"You may need to upgrade dandi and/or dandischema."
328-
)
349+
if should_raise:
350+
with pytest.raises(SchemaVersionError) as excinfo:
351+
client.check_schema_version(local_schema_version)
352+
if expected_message_start:
353+
assert str(excinfo.value).startswith(expected_message_start)
354+
else:
355+
client.check_schema_version(local_schema_version)
329356

330357

331358
def test_get_dandisets(text_dandiset: SampleDandiset) -> None:

0 commit comments

Comments
 (0)