Skip to content

ci: check public API compatibility in pull requests - #1033

Open
shrishsk wants to merge 4 commits into
ni:masterfrom
shrishsk:users/kshrish/914-api-compatibility-checks
Open

shrishsk wants to merge 4 commits into
ni:masterfrom
shrishsk:users/kshrish/914-api-compatibility-checks

Conversation

@shrishsk

@shrishsk shrishsk commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor
  • This contribution adheres to CONTRIBUTING.md.
  • I've updated CHANGELOG.md if applicable. No client-facing library behavior changes.
  • I've added tests applicable for this pull request.

What does this Pull Request accomplish?

Adds automated API compatibility checks using Griffe.

  • Provides a separate reusable workflow, called from CI for pull requests and pushes to master and release branches.
  • Installs tools through an optional Poetry compatibility dependency group.
  • Uses NI's shared default Python version.
  • Defaults to the newest Git tag, with an explicit base SHA for release-target PRs.
  • Supports manual comparisons against a supplied version tag, branch, or commit.
  • Derives additional API visibility from structured source documentation instead of maintaining a package list.
  • Loads each package snapshot once and compares the root package once.
  • Emits safely escaped GitHub warning annotations for compatibility findings.
  • Documents the local reproduction and maintainer approval process.

Fixes #914.

Why should this Pull Request be merged?

Detects potential API-breaking changes before merging, rather than relying solely on internal tests after merge.

The documentation-derived policy covers documented APIs in private implementation modules and public aliases. Each snapshot uses its own documentation, preventing removal of documentation from hiding a previously documented API break.

Source exports and generated API files are unchanged. Export cleanup is a separate public API decision requiring maintainer agreement and would not retroactively repair historical release snapshots.

The breaking-change label allows maintainer-approved compatibility findings without hiding annotations. Operational failures remain fatal.

What testing has been done?

  • All 35 focused automated tests passed, including:
    • DeviceCollection.device_names removal.
    • Removal of nidaqmx.Task and gRPC session-option aliases while implementation classes remain.
    • Deleted documentation and documented modules.
    • Explicit :members: selections and :undoc-members: behavior.
    • Alias chains, cycles, canonical mismatches, and unresolved targets.
    • Ordinary external imports versus documented or publicly exported external aliases.
    • Approval-label behavior, fatal operational errors, and escaped workflow-command output.
  • Formatting, targeted lint, type checking, spelling, and whitespace checks passed locally.
  • Workflow YAML/Bash syntax and Zizmor security checks passed.
  • Comparisons against 1.6.0 and the current upstream base passed.
  • Comparison against 1.3.0 completed with five expected compatibility findings.
  • The latest origin/master was merged without conflicts; all 35 tests and lockfile validation passed afterward.
  • Confirmed that generated API files and handwritten public exports are unchanged.

Hardware-dependent regression tests were not run because this change affects CI tooling, not library runtime behavior.

@shrishsk
shrishsk marked this pull request as draft October 17, 2026 12:32
@shrishsk
shrishsk marked this pull request as ready for review October 17, 2026 14:10

@zhindes zhindes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Concern about the maintainability of the PR yml and how to break compat if we need to.

Comment thread .github/workflows/PR.yml Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread .github/workflows/PR.yml Outdated
Comment thread .github/workflows/PR.yml Outdated
Comment thread .github/workflows/PR.yml
Comment thread .github/workflows/PR.yml Outdated
Comment thread .github/workflows/PR.yml Outdated
Comment thread CONTRIBUTING.md Outdated
# API Compatibility

Pull requests run Griffe to check the generated public API against the pull request's base commit.
The check covers the package and the modules in the API reference separately because package

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this something we should fix instead of working around?

Comment thread .github/workflows/PR.yml Outdated
Comment thread .github/workflows/PR.yml Outdated
Comment thread .github/workflows/PR.yml
Comment thread .github/workflows/PR.yml Outdated
nidaqmx.system.storage.persisted_task nidaqmx.system.system nidaqmx.system.watchdog \
nidaqmx.task nidaqmx.task.channels nidaqmx.task.collections nidaqmx.task.triggering \
nidaqmx.types nidaqmx.utils; do
if ! griffe check --search generated --against "$BASE_SHA" --format github "$module"; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good, --format github creates GitHub annotations. Can you please link to a workflow log that shows what these look like?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also please post a screenshot, since the workflow log will expire and no longer be accessible.

Shrish Kulkarni added 2 commits October 20, 2026 00:10
@shrishsk

shrishsk commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Updated the PR with Poetry-managed tools, the shared Python default, and a reusable workflow called from CI. It defaults to the latest tag and supports explicit bases and manual previous-release comparisons.

The manual package list is replaced by documentation-derived visibility, with regression tests. Approved breaking changes are documented; operational errors remain fatal.

@bkeryan, regarding fixing the underlying exports: would you prefer that cleanup in this PR or a follow-up? Updating current exports would improve consistency, but historical release comparisons would still need additional visibility handling.

Also, could you please approve the workflow run? Once an approved run demonstrates compatibility findings, I’ll post the log link and annotation screenshot.

@bkeryan

bkeryan commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

@bkeryan, regarding fixing the underlying exports: would you prefer that cleanup in this PR or a follow-up? Updating current exports would improve consistency, but historical release comparisons would still need additional visibility handling.

follow-up PR

@shrishsk

Copy link
Copy Markdown
Contributor Author

@bkeryan, regarding fixing the underlying exports: would you prefer that cleanup in this PR or a follow-up? Updating current exports would improve consistency, but historical release comparisons would still need additional visibility handling.

follow-up PR

@bkeryan @zhindes I feel the rest of the changes are in. Once the workflow is approved, I’ll post the log link and annotation screenshot.

@bkeryan

bkeryan commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

@bkeryan, regarding fixing the underlying exports: would you prefer that cleanup in this PR or a follow-up? Updating current exports would improve consistency, but historical release comparisons would still need additional visibility handling.

follow-up PR

@bkeryan @zhindes I feel the rest of the changes are in. Once the workflow is approved, I’ll post the log link and annotation screenshot.

You can test out workflow changes in your personal fork of this repo.

@bkeryan bkeryan Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are you doing this? It seems like you ran into some sort of problem, but you haven't clearly communicated what that problem is. Instead, you went off and wrote an elaborate workaround.

You wrote this in the PR description:

The documentation-derived policy covers documented APIs in private implementation modules and public aliases. Each snapshot uses its own documentation, preventing removal of documentation from hiding a previously documented API break.

I have no idea what policy you are talking about. Is this something you made up? Is this something that griffe implements? Is this something that was output from an LLM?

griffe is a tool that I have used before. Please use it in a normal way. If there are problems with doing that, please communicate what those problems are so that we can discuss how to solve them.

The GitHub issue includes an example of how to use griffe. I tried it again, and it still works:

PS C:\dev\nidaqmx-python> griffe check --search generated nidaqmx -a 1.3.0 --verbose
generated\nidaqmx\stream_readers\_analog_multi_channel_reader.py:140: AnalogMultiChannelReader.read_waveforms(timeout):
Parameter default was changed:
  Old: 10
  New: 10.0

generated\nidaqmx\stream_readers\_analog_single_channel_reader.py:112: AnalogSingleChannelReader.read_waveform(timeout):
Parameter default was changed:
  Old: 10
  New: 10.0

generated\nidaqmx\stream_readers\_digital_multi_channel_reader.py:489: DigitalMultiChannelReader.read_waveforms(timeout):
Parameter default was changed:
  Old: 10
  New: 10.0

generated\nidaqmx\system\system.py:80: System.DriverVersion:
Attribute value was changed:
  Old: collections.namedtuple('DriverVersion', ['major_version', 'minor_version', 'update_version'])
  New: nidaqmx.types.DriverVersion

generated\nidaqmx\task\_task.py:364: Task.close:
Public object points to a different kind of object:
  Old: function
  New: attribute

These are all intentional changes to the API signature that are not expected to break compatibility for normal use cases.

Comment thread CONTRIBUTING.md
Comment on lines +89 to +134
CI runs [Griffe](https://mkdocstrings.github.io/griffe/guide/users/checking/) for pull requests and
pushes to `master` and release branches. The reusable `.github/workflows/check_api_compatibility.yml`
workflow accepts an optional `base` Git reference. By default it uses the most recently created Git
tag, matching Griffe's default. For PRs targeting `releases/**`, CI passes the PR's base commit SHA
instead.
Label changes and PR edits rerun CI through the same caller; they do not bypass existing build or
test jobs.

The checker loads each package snapshot once and compares the `nidaqmx` root once. Docutils parses
the hand-written RST API declarations, including canonical object targets, rather than searching
generated documentation with regular expressions. Each snapshot's own documentation defines extra
visibility in Griffe's model, including documented APIs in private implementation modules and public
aliases of documented objects. Removing documentation in a PR therefore cannot hide a previously
documented API removal. There is no package list to maintain in the workflow.

This policy changes only the analysis models, not Python exports. Cleaning up source-level exports
is a separate public API decision requiring maintainer agreement, including effects on wildcard
imports and supported import paths. Such a cleanup would not retroactively fix the exports in
previous release snapshots, so it cannot replace the historical documentation policy.

To reproduce the package checks locally, use the locked Poetry tooling group with Python 3.11 or
later. For the default release comparison:

```sh
poetry install --only compatibility --no-root
git fetch origin --tags
poetry run python .github/scripts/check_api_compatibility.py
poetry run python -m unittest discover -s .github/scripts -v
```

Run these commands from the repository root. To compare against a particular release, pass
`--base 1.3.0`. For a release-target PR, fetch its target branch and pass `--base` with the exact PR
base SHA. The workflow prints the selected
reference and resolved SHA so a CI run can be reproduced without relying on a moving branch or tag.
If no release tag exists, supply an explicit base reference instead. The checks compare your
current generated source against that baseline.

Once the workflow is available on the default branch, maintainers can also select **Actions > Check
API compatibility > Run workflow**, choose `master` as the branch, and set `base` to a previous release
tag. The equivalent GitHub CLI command is:

```sh
gh workflow run check_api_compatibility.yml --repo ni/nidaqmx-python --ref master -f base=1.3.0
```

Griffe reports potential breaks, including removed objects, changed parameters, and changed

@bkeryan bkeryan Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Contributors should not have to read all this. CONTRIBUTING.md should focus on the contributor workflow and expectations for the contributor.

Did you write this or is it the output of an LLM?

Comment thread .github/workflows/CI.yml
permissions:
contents: read
with:
base: ${{ github.event_name == 'pull_request' && startsWith(github.base_ref, 'releases/') && github.event.pull_request.base.sha || '' }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment thread .github/workflows/CI.yml
permissions:
contents: read
with:
base: ${{ github.event_name == 'pull_request' && startsWith(github.base_ref, 'releases/') && github.event.pull_request.base.sha || '' }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For CI runs triggered on push, I think the base branch is probably specified by github.ref_name. Please test this out in your personal fork of the repo and link to the resulting workflow log.

https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#github-context

Comment thread .github/workflows/CI.yml
permissions: {}

jobs:
check_api_compatibility:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Add this after "build", not before. Reason: if a PR has syntax errors or something, the tools used by "build" will probably return more helpful errors.

Comment thread .github/workflows/PR.yml
branches:
- master
- 'releases/**'
types: [opened, synchronize, reopened, edited, labeled, unlabeled]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are you doing this?

Comment thread pyproject.toml
Comment on lines +93 to +94
docutils = { version = ">=0.21,<0.23", python = ">=3.11" }
griffe = { version = "^2.0", python = ">=3.11" }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do not specify an upper bound for the package version. If griffe breaks compatibility in a meaningful way, then we will notice when the Renovate PR fails.

Comment thread pyproject.toml
Comment on lines +93 to +94
docutils = { version = ">=0.21,<0.23", python = ">=3.11" }
griffe = { version = "^2.0", python = ">=3.11" }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do you really need to specify the Python version? If so, please specify an upper bound like we do elsewhere in this file. I have seen this cause problems because many packages with Python version markers do not claim support for a future Python 4.0.

Comment on lines +43 to +44
- name: Test compatibility checker
run: poetry run python -m unittest discover -s .github/scripts -v

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This project does not use unittest. However this should go away when you delete your workaround script.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add automated API compatibility checks to GitHub PR workflow

3 participants