Skip to content

Add a registry of dandi-archive features used only where supported - #1970

Open
yarikoptic-gitmate wants to merge 4 commits into
masterfrom
claude/zealous-turing-ahafkn
Open

yarikoptic-gitmate wants to merge 4 commits into
masterfrom
claude/zealous-turing-ahafkn

Conversation

@yarikoptic-gitmate

@yarikoptic-gitmate yarikoptic-gitmate commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

This is the follow-up planned in #1963. It adds a registry of the dandi-archive features which dandi-cli uses only where the server supports them, and adds the weekly survey and the disabling env var that it described.

What it adds

dandi/server_features.py

  • ServerFeature records, for each feature:
    • a CODENAME;
    • a description, including what dandi-cli does without the feature;
    • links to the dandi-archive and dandi-cli issues/PRs;
    • base_version: the dandi-archive release the feature was developed on top of;
    • released_version: the release that added it, once known;
    • sense(client): a function that checks for the feature without changing anything on the server.
  • The features are registered in SERVER_FEATURES.
  • The module types the client as a small APIClient Protocol (api_url, session, server_info, get_url()) rather than importing dandi.dandiapi, which imports it; CodeQL flags the cycle even under TYPE_CHECKING.

DandiAPIClient.server_features decides, once per client:

  • .supports("CODENAME") -> bool.
  • .resolve() also says how the answer was reached: disabled, version, sensed or recorded.
  • .record("CODENAME", supported) lets code that found out by using the feature update the answer.

How support is decided

  1. Disabled, if the CODENAME is listed in DANDI_DEVEL_DISABLE_SERVER_FEATURES, e.g. BULK_DELETE,ALLOWED_SCHEMA_VERSIONS. Unknown names are warned about and ignored.

  2. By version, only if /api/info/'s version is a clean dandi-archive release:

    • version >= released_version: supported;
    • version < released_version: not supported;
    • before a release is known, version <= base_version: not supported.

    Development versions (sandbox instances) and versions with a post or local part (forks) do not count as releases, because they can't be compared with dandi-archive's. LINC today reports 0.0.post6586+g39387b8.

  3. Otherwise by sense().

routes_endpoint(client, path) is a probe that does not modify anything. It sends OPTIONS to path and to a sibling path that no server has, then compares status and content type.

  • Without the endpoint, both get Django's HTML 404.
  • An existing endpoint answers in JSON (200, or 401 on servers that require login).
  • Rate limiting (429), 5xx responses and connection errors count as "endpoint not there".

Registered features

Survey of known instances. test_survey[CODENAME] checks every instance in known_instances except the local Docker one.

  • It prints a table, which is also written to the job summary.
  • It fails once all instances support a feature, i.e. once the fallback and the registry entry can be removed.
  • It is marked server_features_survey and skipped unless --server-features-survey is given.
  • It runs from the new .github/workflows/server-features.yml: weekly (Mondays 05:23 UTC), on demand, and on PRs that touch the registry. Its run on this PR gave the table below.

Docs

  • The DEVELOPMENT.md compatibility section now describes the registry as the way to do this, and lists the registered features. The older conditionals that are not decided per server are listed separately.
  • The env var, the marker, the pytest option and the workflow are documented in DEVELOPMENT.md.
  • A Sphinx page for dandi.server_features is added.

Checked against live servers

The survey job on this PR produced:

Feature dandi dandi-sandbox linc linc-staging ember-dandi ember-dandi-sandbox
ALLOWED_SCHEMA_VERSIONS yes (version) yes (sensed) no (sensed) no (sensed) yes (sensed) yes (sensed)
BULK_DELETE no (sensed) no (sensed) no (sensed) no (sensed) no (sensed) no (sensed)

From the environment this was prepared in, only lincbrain.org was reachable; DANDI and EMBER hosts were blocked by its network policy.

  • LINC and LINC staging: both features are no (sensed). LINC lacks allowed_schema_versions and the bulk-delete route.
  • Probe on LINC:
    • routes_endpoint() returns True for routes LINC has (…/assets/paths/, /zarr/);
    • it returns False for …/assets/bulk-delete/ and for a made-up path.
  • Not run locally: test_local_archive checks the probe against the Docker archive (dandiarchive/dandiarchive-api) in CI's dandi-api jobs. Docker Hub rate-limited the image pull here.

Notes

Testing

  • test_server_features.py (new, ai_generated):
    • 32 unit test cases covering the version rules (including LINC's and sandbox-style versions), sensing once per client, the env var, record(), unknown CODENAMEs, the probe's responses, check_schema_version() with and without the feature, registry hygiene, and the survey's report;
    • test_local_archive, against the Docker archive;
    • the two test_survey cases.
  • Locally:
    • the survey test passed with --server-features-survey (LINC rows no (sensed), others unreachable);
    • pre-commit and mypy are clean;
    • the docs build with -W;
    • the full local non-integration suite (-m "not obolibrary"): 916 passed, 26 failed. All 26 failures are tests that need dandiarchive.org, which that environment's network policy blocks (proxy errors and timeouts); CI runs them with network access.

🤖 Generated with Claude Code

https://claude.ai/code/session_014sJtxzEPbSxycxetp4DquU

dandi-cli talks to several independently deployed dandi-archive
instances, which gain new features at different times.  Register such
features in dandi/server_features.py as ServerFeature entries (CODENAME,
description, refs, base_version, released_version, and a side-effect
free sense function), and ask DandiAPIClient.server_features whether
the server supports one.  Support is decided once per client:

- not, if the CODENAME is listed in DANDI_DEVEL_DISABLE_SERVER_FEATURES;
- by the server version, if it is a dandi-archive release (development
  versions and versions of forks cannot be compared with releases);
- otherwise by sensing; code which finds out by using a feature can
  record() it.

routes_endpoint() probes for an API endpoint by comparing OPTIONS
responses for it and for a path no server routes.

Register ALLOWED_SCHEMA_VERSIONS, now used by check_schema_version(),
and BULK_DELETE (dandi-archive#2920) for dandi-cli#1960 to use.

A weekly "Survey server features" workflow runs test_survey (marked
server_features_survey, skipped without --server-features-survey),
which fails once all known instances support a registered feature,
i.e., once its fallback can be removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sJtxzEPbSxycxetp4DquU
@yarikoptic-gitmate yarikoptic-gitmate added the minor Increment the minor version when merged label Oct 28, 2026 — with Claude
Comment thread dandi/dandiapi.py Fixed
Comment thread dandi/server_features.py Fixed
…dandiapi

dandi.dandiapi imports dandi.server_features, so the latter importing
DandiAPIClient back (even only under TYPE_CHECKING) made CodeQL report a
module-level cyclic import.  Declare instead, as the APIClient Protocol,
the few attributes of a DandiAPIClient which the module needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sJtxzEPbSxycxetp4DquU
Comment thread dandi/server_features.py Fixed
Comment thread dandi/server_features.py Fixed
…her than '...'

CodeQL reports a bare '...' body as a statement which has no effect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sJtxzEPbSxycxetp4DquU
@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.58108% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.94%. Comparing base (e65abd3) to head (0f558e4).

Files with missing lines Patch % Lines
dandi/tests/test_server_features.py 89.61% 19 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1970      +/-   ##
==========================================
+ Coverage   90.82%   90.94%   +0.11%     
==========================================
  Files          92       94       +2     
  Lines       14190    14484     +294     
==========================================
+ Hits        12888    13172     +284     
- Misses       1302     1312      +10     
Flag Coverage Δ
unittests 90.94% <93.58%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The survey test runs only with --server-features-survey (weekly), so its
logic, now in _survey(), is otherwise never run.  Test it with mocked
servers: support everywhere, an unreachable instance, a failing sense
function, and missing support.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014sJtxzEPbSxycxetp4DquU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Increment the minor version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants