Skip to content

Make args and kwargs visible in API docs across all public methods (umbrella, follow-up to #886) #896

@drbenvincent

Description

@drbenvincent

Motivation

Issue #886 / PR #894 fixed the discoverability of *args, **kwargs for one specific case — the plot() method on every BaseExperiment subclass. The same problem applies to several other public methods across the package: bare or trailing *args, **kwargs hide real, supported parameters from Sphinx, IDE autocomplete, inspect.signature, and help(), and silently accept (or swallow) misspelled keyword arguments at runtime.

This umbrella issue extends the work to the rest of the public surface.

Reference implementation

The pattern established in PR #894 is the template:

  1. Where the offending method is a dispatcher or abstract stub on BaseExperiment (the four-method shape: public dispatcher → _bayesian_* / _ols_* private hook), remove the public method from the base, rename its body to a protected helper (e.g. _render_plot), and force every concrete subclass to declare its own explicit, kwarg-only override that delegates via self._render_xxx(...). This converts the contract from a convention into a structural invariant — a future subclass that forgets to declare the method has no public method at all, surfacing the omission immediately.
  2. Where the offending method already has an explicit signature with a trailing **kwargs, audit whether the kwargs are real (forwarded somewhere, used conditionally) or vestigial. If real, lift the keys to explicit named parameters or document them under Other Parameters; if not, delete the trailing **kwargs.
  3. Add the new method(s) to the regex enforced by the numpydoc-validation pre-commit hook (currently scoped to \.plot$). The simplest evolution is to broaden it to a list of public-method patterns, or to invert the check (every public method should be PR01/PR02-clean).
  4. Extend causalpy/tests/test_public_plot_signatures.py (or sibling tests) so the structural invariants apply to the new method names.
  5. Update AGENTS.md to broaden the public-method discipline beyond plot().

Confirmed scope (from a static survey on 2026-05-01)

The following 25 public methods/functions in the package have *args and/or **kwargs in their signature today. Categories below; treat the inventory as a starting point — the survey itself should be re-run as part of completing this work since it likely undercounts non-class-method public surfaces.

A. Bare-dispatcher / bare-stub pattern (direct #886 analogue)

These four are exactly the same shape as the old BaseExperiment.plot. The _render_plot refactor from PR #894 should generalise mechanically.

Method File Notes
BaseExperiment.fit(*args, **kwargs) experiments/base.py:138 Abstract; every subclass overrides. Surface with explicit kwargs at base or remove from base.
BaseExperiment.get_plot_data(*args, **kwargs) experiments/base.py:306 Dispatcher → get_plot_data_bayesian / get_plot_data_ols.
BaseExperiment.get_plot_data_bayesian(*args, **kwargs) experiments/base.py:319 Abstract default; raises NotImplementedError.
BaseExperiment.get_plot_data_ols(*args, **kwargs) experiments/base.py:323 Abstract default; raises NotImplementedError.

B. Explicit signature with a trailing **kwargs

These already document most parameters, but the trailing **kwargs: Any is undocumented and lets typos through silently. Need a per-method audit.

  • BaseExperiment.effect_summary(..., **kwargs)experiments/base.py:327
  • DifferenceInDifferences.effect_summary(..., **kwargs)experiments/diff_in_diff.py:655
  • InstrumentalVariable.effect_summary(..., **kwargs)experiments/instrumental_variable.py:299
  • InterruptedTimeSeries.effect_summary(..., **kwargs)experiments/interrupted_time_series.py:1278
  • InversePropensityWeighting.effect_summary(..., **kwargs)experiments/inverse_propensity_weighting.py:849
  • PanelRegression.effect_summary(..., **kwargs)experiments/panel_regression.py:447
  • PiecewiseITS.effect_summary(..., **kwargs)experiments/piecewise_its.py:802
  • PrePostNEGD.effect_summary(..., **kwargs)experiments/prepostnegd.py:369
  • RegressionDiscontinuity.effect_summary(..., **kwargs)experiments/regression_discontinuity.py:530
  • RegressionKink.effect_summary(..., **kwargs)experiments/regression_kink.py:373
  • StaggeredDifferenceInDifferences.effect_summary(..., **kwargs)experiments/staggered_did.py:1022
  • SyntheticControl.effect_summary(..., **kwargs)experiments/synthetic_control.py:868
  • PyMCModel.predict(X, coords=None, out_of_sample=False, **kwargs)pymc_models.py
  • PyMCModel.score(X, y, coords=None, **kwargs)pymc_models.py
  • BayesianBasisExpansionTimeSeries.predict(..., **kwargs)pymc_models.py
  • BayesianBasisExpansionTimeSeries.score(..., **kwargs)pymc_models.py
  • StateSpaceTimeSeries.predict(..., **kwargs)pymc_models.py
  • StateSpaceTimeSeries.score(..., **kwargs)pymc_models.py

C. Bare-**kwargs stubs that already have no real parameters

  • PanelRegression.get_plot_data_bayesian(self, **kwargs)experiments/panel_regression.py:603. The **kwargs looks dead; the body uses none of it.
  • PanelRegression.get_plot_data_ols(self, **kwargs)experiments/panel_regression.py:635. Same.

D. Module-level public functions

  • causalpy.utils.plot_correlations(data, columns=None, method='pearson', **kwargs)utils.py. Has explicit named kwargs followed by **kwargs; audit the forwarding.

E. Modules not yet surveyed (sub-task)

The script in scope walked classes only via walk(BaseExperiment) plus top-level functions, so the following may have additional cases worth checking:

  • causalpy.data (data loaders / generators)
  • causalpy.checks (diagnostic checks)
  • causalpy.pipeline
  • causalpy.steps.*
  • causalpy.reporting
  • causalpy.plot_utils
  • causalpy.transforms
  • causalpy.skl_models
  • causalpy.maketables_adapters

A first sub-task should be to extend the static survey to cover all causalpy.* modules (skipping causalpy.tests) and append the findings to this issue before any code changes start.

Proposed approach

  1. Sub-task 1: Re-run / extend the survey. Refresh the inventory above with the modules listed in section E and post the updated table here. (The script that produced the original inventory was kept in scratch space during PR Replace plot() *args/**kwargs with explicit signatures (#886) #894; harden into tools/audit_public_signatures.py if it'll see repeat use.)
  2. Sub-task 2: Group A — apply the Replace plot() *args/**kwargs with explicit signatures (#886) #894 _render_plot pattern to fit, get_plot_data, get_plot_data_bayesian, get_plot_data_ols. Most likely a single PR. Forces every concrete subclass to declare its own explicit signature; removes the public method from BaseExperiment. Probably the largest mechanical change.
  3. Sub-task 3: Group B audit — trailing **kwargs. For each method, decide one of: (a) drop the kwargs entirely, (b) document them under Other Parameters, or (c) lift the actual keys used to explicit named parameters. Likely splits into one PR per method family (effect_summary, predict, score).
  4. Sub-task 4: Group C — clean up dead **kwargs on PanelRegression.get_plot_data_*. Trivial.
  5. Sub-task 5: Group D / E — module-level public functions. Probably one PR per module.
  6. Sub-task 6: Broaden enforcement. Extend the numpydoc-validation regex (in pyproject.toml [tool.numpydoc_validation]) and causalpy/tests/test_public_plot_signatures.py so the no-*args/**kwargs invariant covers the methods above. Update AGENTS.md to drop the "plot() only" framing and require the discipline for all public methods.

Acceptance criteria

  • Updated survey covering all causalpy.* modules posted as a comment on this issue.
  • No public method or module-level function in causalpy/ declares *args or **kwargs at its signature, with the exception of explicitly documented forwarders that list the accepted keys under Other Parameters (and can justify why explicit naming is impractical).
  • numpydoc-validation pre-commit hook scope broadened beyond \.plot$ so the invariant is enforced for every method touched.
  • causalpy/tests/test_public_plot_signatures.py (or a renamed/expanded version) extended to assert the no-*args/**kwargs invariant for the new methods.
  • AGENTS.md updated to describe the broader convention.
  • Sphinx API pages render explicit signatures and parameter lists for every method touched (eyeball check on the RTD preview).
  • prek run --all-files passes.
  • pytest passes.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationrefactorRefactor, clean up, or improvement with no visible changes to the user

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions