Skip to content

Plugin registry - #640

Closed
d-chambers wants to merge 14 commits into
masterfrom
plugin-registry
Closed

Plugin registry#640
d-chambers wants to merge 14 commits into
masterfrom
plugin-registry

Conversation

@d-chambers

@d-chambers d-chambers commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Description

This PR creates a simple plugin registry on the Patch and Spool, and updates docs accordingly.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings and/or appropriate doc page.
  • included tests. See testing guidelines.
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

Summary by CodeRabbit

  • New Features

    • Accessing known third‑party namespaces that aren't installed now surfaces a clear installation message.
  • Documentation

    • Added a Supported Plugins page, a plugin registration guide, and promoted plugin support in the site sidebar; docs now render a plugin registry table.
  • Tests

    • Added tests covering plugin-registry loading, the new installation messaging, and hasattr/lookup behavior.

d-chambers and others added 2 commits March 23, 2026 11:21
Explains how to open a PR to add a row to the plugin registry CSVs so
users get a helpful error message when the namespace is not installed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Namespace attribute lookup now consults a CSV-based plugin registry and raises a new DASCorePluginError with installation guidance for registered-but-uninstalled third-party namespaces; registry loading, documentation, site nav, and tests were added or updated to support this behavior.

Changes

Cohort / File(s) Summary
Core plugin registry & namespace logic
dascore/utils/namespace.py
Added module-level _PLUGIN_REGISTRY_DIR, imports (pathlib.Path, pandas, DASCorePluginError), a cached _load_plugin_registry that reads CSVs into a namespace→(package_name, package_url) map, and updated NamespaceOwner.__getattr__ to consult the registry and raise DASCorePluginError (with install URL) for registered-but-uninstalled namespaces.
New exception
dascore/exceptions.py
Added DASCorePluginError subclassing AttributeError and DASCoreError for plugin-related attribute errors.
Docs utilities
dascore/utils/docs.py
Added get_plugin_table() to load and combine *.csv from the plugin registry dir into a deduplicated/sorted DataFrame with columns namespace, package_name, package_url.
Tests: namespace & docs
tests/test_utils/test_namespace.py, tests/test_utils/test_doc_utils.py
Added tests for _load_plugin_registry, cache clearing fixture, getattr behavior raising DASCorePluginError for registered-but-uninstalled namespaces (message includes package and URL), and tests for get_plugin_table() including empty-dir fallback.
Documentation: contributor guide
docs/contributing/extending_dascore.qmd
New section explaining how to register plugins via dascore/plugin_registry/patch.csv (or spool.csv), required CSV columns, example row, and describing resulting error behavior when namespace is accessed but provider not installed.
Documentation: supported plugins page
docs/supported_plugins.qmd
New Quarto page that renders the plugin registry table (namespace and linked package_name) by calling get_plugin_table() and formatting it as a Markdown table.
Docs index & site nav
docs/index.qmd, scripts/_templates/_quarto.yml
Added a "Plugins" highlight/link to the docs index and inserted a "Plugins" entry into the website sidebar (About section) linking to supported_plugins.qmd.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description explains the feature (plugin registry on Patch and Spool) and references documentation updates, but critical checklist items remain unchecked despite evidence in the code that tests, documentation, and features have been implemented. Update the checklist to accurately reflect completed work: check the boxes for documented feature, included tests, and documented closing issue if applicable.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title "Plugin registry" accurately summarizes the main change—adding a plugin registry system to DASCore—and is appropriately concise.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch plugin-registry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation patch related to Patch class labels Mar 23, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6b646c564

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


import pandas as pd

from dascore.exceptions import DASCorePluginError

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Define DASCorePluginError before importing it

This import points to a symbol that is not defined anywhere in dascore/exceptions.py, so any code path that imports dascore.utils.namespace will fail immediately with an ImportError. I checked dascore/core/patch.py:24, dascore/core/spool.py:34, dascore/io/__init__.py:17, and dascore/viz/__init__.py:5, all of which import from this module, so this would break basic Patch/Spool and built-in namespace imports in a normal install.

Useful? React with 👍 / 👎.

Comment on lines +151 to +155
f"{self.__class__.__name__} has a registered namespace of '{item}' "
f"provided by '{package_name}' but it is not installed. "
f"Install it from: {package_url}"
)
raise DASCorePluginError(msg)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep getattr failures as AttributeError

If a registry row is added for a namespace, this branch will raise DASCorePluginError out of __getattr__ instead of AttributeError. That changes Python's attribute protocol: callers using hasattr(obj, "ns") or getattr(obj, "ns", default) to probe optional namespaces will now get an exception for an uninstalled plugin rather than ordinary “attribute missing” behavior. The helpful install hint needs to be surfaced through an AttributeError-compatible path.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
dascore/utils/namespace.py (1)

41-41: Add strict=True to zip() calls for safety.

Both nested zip() calls lack an explicit strict= parameter. Since the CSV columns should always have matching lengths, using strict=True will catch data corruption early.

♻️ Proposed fix
-    return dict(zip(df["namespace"], zip(df["package_name"], df["package_url"])))
+    return dict(zip(
+        df["namespace"],
+        zip(df["package_name"], df["package_url"], strict=True),
+        strict=True,
+    ))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/utils/namespace.py` at line 41, The zip calls constructing the
mapping from df["namespace"] to pairs of df["package_name"], df["package_url"]
should use strict=True to detect mismatched column lengths; update the inner zip
that combines df["package_name"] and df["package_url"] to
zip(df["package_name"], df["package_url"], strict=True) and the outer zip that
pairs df["namespace"] with those pairs to zip(df["namespace"], <inner_zip>,
strict=True) so any length mismatch raises immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dascore/utils/namespace.py`:
- Line 13: The import of DASCorePluginError in namespace.py is invalid because
DASCorePluginError isn't defined; update the code to either import an existing
exception (replace DASCorePluginError with DASCoreError in the import and all
usages, e.g., the reference at DASCorePluginError in namespace.py) or define the
missing class in dascore.exceptions (add class DASCorePluginError(DASCoreError):
pass) and keep the current import; ensure the symbol referenced at the failing
site matches the exception you choose.

In `@tests/test_utils/test_namespace.py`:
- Around line 267-292: The test should be updated to match the current
implementation: change the pytest.raises to expect DASCorePluginError instead of
AttributeError and update the expected message string to the implementation's
wording (e.g., "ParentClass has a registered namespace of 'cool_ns' provided
by...") so the assertion verifies the exact error type and message produced by
the __getattr__ logic (see DASCorePluginError and ParentClass/namespace
__getattr__ behavior).

---

Nitpick comments:
In `@dascore/utils/namespace.py`:
- Line 41: The zip calls constructing the mapping from df["namespace"] to pairs
of df["package_name"], df["package_url"] should use strict=True to detect
mismatched column lengths; update the inner zip that combines df["package_name"]
and df["package_url"] to zip(df["package_name"], df["package_url"], strict=True)
and the outer zip that pairs df["namespace"] with those pairs to
zip(df["namespace"], <inner_zip>, strict=True) so any length mismatch raises
immediately.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6a0a2ab-247a-4a32-b2e3-0d7c88084451

📥 Commits

Reviewing files that changed from the base of the PR and between fc47e41 and e6b646c.

⛔ Files ignored due to path filters (2)
  • dascore/plugin_registry/patch.csv is excluded by !**/*.csv
  • dascore/plugin_registry/spool.csv is excluded by !**/*.csv
📒 Files selected for processing (6)
  • dascore/utils/namespace.py
  • docs/contributing/extending_dascore.qmd
  • docs/index.qmd
  • docs/supported_plugins.qmd
  • scripts/_templates/_quarto.yml
  • tests/test_utils/test_namespace.py

Comment thread dascore/utils/namespace.py
Comment thread tests/test_utils/test_namespace.py Outdated
d-chambers and others added 2 commits March 23, 2026 11:40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov

codecov Bot commented Mar 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.93%. Comparing base (fc47e41) to head (c175c86).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #640   +/-   ##
=======================================
  Coverage   99.93%   99.93%           
=======================================
  Files         135      135           
  Lines       11605    11631   +26     
=======================================
+ Hits        11597    11623   +26     
  Misses          8        8           
Flag Coverage Δ
unittests 99.93% <100.00%> (+<0.01%) ⬆️

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

☔ View full report in Codecov by Sentry.
📢 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.

d-chambers and others added 2 commits March 23, 2026 11:54
- DASCorePluginError now inherits from AttributeError (not ValueError) so
  hasattr() and getattr(..., default) work correctly when a namespace is
  registered but not installed
- Add strict=True to zip() calls in _load_plugin_registry
- Add test verifying hasattr returns False for uninstalled plugin namespaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Mar 23, 2026

Copy link
Copy Markdown

✅ Documentation built:
👉 Download
Note: You must be logged in to github and a DASDAE member to access the link.

d-chambers and others added 8 commits March 23, 2026 12:20
- Add derzug (https://github.com/dasdae/derzug) to patch.csv and spool.csv
- Docs table now deduplicates across CSVs and shows namespace + linked
  package_name in a single table instead of per-type tables
- Refactor: cache-clear fixture is now class-scoped autouse in TestPluginRegistry
- Remove test_multiple_rows_all_returned and strict=True from zip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
qmd now just calls get_plugin_table_str(); one test added to test_doc_utils.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dascore/utils/docs.py`:
- Around line 110-117: The code assumes every CSV read into frames contains the
columns ["namespace","package_name","package_url"], causing a generic KeyError
on malformed files; before concatenating (after pd.read_csv in the frames list
or right after creating frames), validate each DataFrame from pd.read_csv and/or
the final pd.concat to ensure those exact columns exist. If any DataFrame is
missing columns, raise a clear ValueError that includes the offending file path
(from _PLUGIN_REGISTRY_DIR.glob results) and the set of missing column names;
otherwise proceed with the existing drop_duplicates/sort_values/subset
operations on the validated concatenated DataFrame. Ensure the error message
references the filename and missing columns so docs failures are actionable.

In `@tests/test_utils/test_doc_utils.py`:
- Around line 85-90: The test test_contains_registered_namespace should not rely
on live registry entries; instead create a deterministic temporary CSV with
known rows (e.g., namespace/package_name pairs), then call get_plugin_table in a
way that uses that CSV (either by refactoring get_plugin_table to accept a path
parameter or by monkeypatching the internal registry-loading helper used by
get_plugin_table), and assert the expected namespace and package_name values
from the returned DataFrame; update the test to write the temp CSV, ensure
cleanup, and use the deterministic values rather than "zug"/"derzug".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: faf124dc-dece-4f86-bb55-372e7f633966

📥 Commits

Reviewing files that changed from the base of the PR and between b924ec1 and c175c86.

📒 Files selected for processing (4)
  • dascore/utils/docs.py
  • docs/supported_plugins.qmd
  • tests/test_utils/test_doc_utils.py
  • tests/test_utils/test_namespace.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/supported_plugins.qmd

Comment thread dascore/utils/docs.py
Comment on lines +110 to +117
frames = [pd.read_csv(p) for p in sorted(_PLUGIN_REGISTRY_DIR.glob("*.csv"))]
if not frames:
return pd.DataFrame(columns=["namespace", "package_name", "package_url"])
return (
pd.concat(frames, ignore_index=True)
.drop_duplicates(subset="namespace")
.sort_values("namespace")[["namespace", "package_name", "package_url"]]
.reset_index(drop=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle malformed registry CSVs with explicit schema validation.

At Line 116, column selection assumes every CSV has namespace, package_name, and package_url. A malformed file currently fails with a generic KeyError, which makes docs failures harder to diagnose.

💡 Proposed hardening
 def get_plugin_table() -> pd.DataFrame:
@@
-    frames = [pd.read_csv(p) for p in sorted(_PLUGIN_REGISTRY_DIR.glob("*.csv"))]
+    required_cols = ["namespace", "package_name", "package_url"]
+    frames = []
+    for path in sorted(_PLUGIN_REGISTRY_DIR.glob("*.csv")):
+        df = pd.read_csv(path)
+        missing = [col for col in required_cols if col not in df.columns]
+        if missing:
+            miss = ", ".join(missing)
+            raise ValueError(
+                f"Plugin registry CSV '{path}' is missing required column(s): {miss}"
+            )
+        frames.append(df)
     if not frames:
-        return pd.DataFrame(columns=["namespace", "package_name", "package_url"])
+        return pd.DataFrame(columns=required_cols)
     return (
         pd.concat(frames, ignore_index=True)
         .drop_duplicates(subset="namespace")
-        .sort_values("namespace")[["namespace", "package_name", "package_url"]]
+        .sort_values("namespace")[required_cols]
         .reset_index(drop=True)
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dascore/utils/docs.py` around lines 110 - 117, The code assumes every CSV
read into frames contains the columns
["namespace","package_name","package_url"], causing a generic KeyError on
malformed files; before concatenating (after pd.read_csv in the frames list or
right after creating frames), validate each DataFrame from pd.read_csv and/or
the final pd.concat to ensure those exact columns exist. If any DataFrame is
missing columns, raise a clear ValueError that includes the offending file path
(from _PLUGIN_REGISTRY_DIR.glob results) and the set of missing column names;
otherwise proceed with the existing drop_duplicates/sort_values/subset
operations on the validated concatenated DataFrame. Ensure the error message
references the filename and missing columns so docs failures are actionable.

Comment on lines +85 to +90
def test_contains_registered_namespace(self):
"""Registered namespaces should appear in the returned DataFrame."""
df = get_plugin_table()
assert "zug" in df["namespace"].values
assert "derzug" in df["package_name"].values

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid hard-coding live registry entries in this unit test.

This test is tied to specific repository data ("zug", "derzug"), so unrelated registry edits can break it. Prefer building a temporary CSV and asserting deterministic output.

💡 Proposed deterministic test rewrite
-    def test_contains_registered_namespace(self):
+    def test_contains_registered_namespace(self, monkeypatch, tmp_path):
         """Registered namespaces should appear in the returned DataFrame."""
+        import dascore.utils.namespace as ns_module
+
+        pd.DataFrame(
+            {
+                "namespace": ["cool_ns"],
+                "package_name": ["coolpkg"],
+                "package_url": ["https://example.com/coolpkg"],
+            }
+        ).to_csv(tmp_path / "patch.csv", index=False)
+        monkeypatch.setattr(ns_module, "_PLUGIN_REGISTRY_DIR", tmp_path)
+
         df = get_plugin_table()
-        assert "zug" in df["namespace"].values
-        assert "derzug" in df["package_name"].values
+        assert "cool_ns" in df["namespace"].values
+        assert "coolpkg" in df["package_name"].values
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_utils/test_doc_utils.py` around lines 85 - 90, The test
test_contains_registered_namespace should not rely on live registry entries;
instead create a deterministic temporary CSV with known rows (e.g.,
namespace/package_name pairs), then call get_plugin_table in a way that uses
that CSV (either by refactoring get_plugin_table to accept a path parameter or
by monkeypatching the internal registry-loading helper used by
get_plugin_table), and assert the expected namespace and package_name values
from the returned DataFrame; update the test to write the temp CSV, ensure
cleanup, and use the deterministic values rather than "zug"/"derzug".

@d-chambers d-chambers mentioned this pull request Mar 23, 2026
4 tasks
@d-chambers

Copy link
Copy Markdown
Contributor Author

I decided to put this on the dev branch rather than master.

@d-chambers d-chambers closed this Mar 23, 2026
@d-chambers
d-chambers deleted the plugin-registry branch March 23, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation patch related to Patch class

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant