Skip to content

ci: add package sanity check and stop shipping test files (#958)#1261

Open
hassanzafarr wants to merge 1 commit into
qdrant:masterfrom
hassanzafarr:ci/package-sanity-check
Open

ci: add package sanity check and stop shipping test files (#958)#1261
hassanzafarr wants to merge 1 commit into
qdrant:masterfrom
hassanzafarr:ci/package-sanity-check

Conversation

@hassanzafarr

@hassanzafarr hassanzafarr commented Jul 10, 2026

Copy link
Copy Markdown

Closes #958.

The published wheel/sdist currently ship test modules that live inside the qdrant_client package. Building 1.18.0 locally and inspecting the wheel, I see:

  • qdrant_client/local/tests/* (7 files)
  • qdrant_client/hybrid/test_reranking.py

Three of these import pytest at module level, which is what makes pytest an effective import-time dependency of the package and causes the broken releases mentioned in the issue.

Root cause: packages = [{include = "qdrant_client"}] includes the whole tree, and there's no exclude, so anything named like a test that happens to live under qdrant_client/ gets packaged.

Changes

  1. Stop shipping test-only code — added an exclude to [tool.poetry] in pyproject.toml for qdrant_client/**/tests and qdrant_client/**/test_*.py. This only affects the built artifacts; the files stay in the source tree and are still collected/run by pytest, so nothing changes for local dev or CI test runs.

  2. Guard against regressions — the issue asks for a CI check, so I added:

    • tools/check_package_contents.py — stdlib-only, builds nothing itself; it inspects the wheel + sdist and fails if any shipped module is a test file or imports pytest/_pytest.
    • .github/workflows/package-sanity.yml — runs poetry build and then the check on push/PR.

A check on its own would have gone red immediately because the leak is live today, so both parts are needed together.

Testing

  • poetry build, then ran the checker against the resulting dist/:
    • Against the current (leaky) build → fails, listing all 7 test files + the 3 pytest imports.
    • Against the build with the exclude → passes.
  • Confirmed the excluded files remain in the source tree and that real modules (e.g. qdrant_client/hybrid/fusion.py, the qdrant_client/local implementation) still ship — 91 Python modules in the wheel, only the test files removed.
  • Interestingly the checker also flagged qdrant_client/hybrid/test_reranking.py, which sits outside local/tests; the broader exclude pattern covers it too.

Happy to adjust the exclude patterns or the workflow triggers to match your preferences.

Note: @glenlouis8 mentioned interest in the issue a while back — apologies for any overlap, happy to defer or collaborate if they already have work in progress.

Copilot AI review requested due to automatic review settings July 10, 2026 21:04
@netlify

netlify Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploy Preview for poetic-froyo-8baba7 ready!

Name Link
🔨 Latest commit d15fdf3
🔍 Latest deploy log https://app.netlify.com/projects/poetic-froyo-8baba7/deploys/6a51604ce25c170008858144
😎 Deploy Preview https://deploy-preview-1261--poetic-froyo-8baba7.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Poetry exclusions for test-only modules, introduces a script that scans wheel and source distribution contents for test files and forbidden pytest imports, and configures a GitHub Actions workflow to build the package and run the checker on pushes and pull requests.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the CI sanity check and the exclusion of shipped test files.
Description check ✅ Passed The description is directly related to the package exclusion and CI validation changes.
Linked Issues check ✅ Passed The PR implements the requested CI release sanity check to catch pytest leakage in built packages.
Out of Scope Changes check ✅ Passed The changes stay within scope by adding packaging exclusions, a validation script, and a CI workflow.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
.github/workflows/package-sanity.yml (1)

12-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider updating pinned GitHub Actions to current major versions.

Both actions/checkout (v2.7.0) and actions/setup-python (v2.3.4) are several major versions behind current (v4 and v5 respectively). While SHA-pinning ensures reproducibility, these older versions miss security patches and improvements. Updating to the latest major versions (still SHA-pinned) is low-risk and keeps the workflow on supported ground.

♻️ Suggested version updates
       - uses: actions/checkout@<latest-v4-sha> # v4.2.2
 
       - name: Set up Python
         uses: actions/setup-python@<latest-v5-sha> # v5.3.0
         with:
           python-version: "3.10"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/package-sanity.yml around lines 12 - 17, Update the pinned
actions in the package-sanity workflow: replace actions/checkout v2.7.0 with the
current v4 release SHA and actions/setup-python v2.3.4 with the current v5
release SHA, preserving SHA pinning and updating the inline version comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/package-sanity.yml:
- Around line 12-17: Update the pinned actions in the package-sanity workflow:
replace actions/checkout v2.7.0 with the current v4 release SHA and
actions/setup-python v2.3.4 with the current v5 release SHA, preserving SHA
pinning and updating the inline version comments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2ce93f9a-f178-47a5-aa59-f761d40ff99e

📥 Commits

Reviewing files that changed from the base of the PR and between 326adef and e70d81f.

📒 Files selected for processing (3)
  • .github/workflows/package-sanity.yml
  • pyproject.toml
  • tools/check_package_contents.py

The published wheel and sdist included test modules under qdrant_client/
(qdrant_client/local/tests/* and qdrant_client/hybrid/test_reranking.py),
three of which import pytest at module level. That made pytest an
effective import-time dependency of the package and led to broken
releases where a clean `pip install` failed.

Exclude test-only files from the built distribution via the poetry
`exclude` key. They stay in the source tree and are still collected by
pytest, so nothing changes for local development or CI test runs; they
just no longer ship to end users.

Add a `package-sanity` CI workflow plus tools/check_package_contents.py
that builds the package and fails if any shipped module is a test file
or imports pytest, so this cannot silently regress before a release.

Fixes qdrant#958
@hassanzafarr hassanzafarr force-pushed the ci/package-sanity-check branch from e70d81f to d15fdf3 Compare July 10, 2026 21:12

Copilot AI 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.

Pull request overview

This PR prevents test-only modules (and accidental pytest import-time dependencies) from being included in published qdrant-client distributions, and adds a CI guard to catch future regressions.

Changes:

  • Adds Poetry packaging excludes to prevent qdrant_client/**/tests and qdrant_client/**/test_*.py from shipping in wheels/sdists.
  • Introduces a stdlib-only checker script to scan built artifacts for shipped test modules or forbidden imports (pytest, _pytest).
  • Adds a GitHub Actions workflow that builds the package and runs the checker on pushes and PRs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tools/check_package_contents.py New script that inspects built wheels/sdists for test modules and forbidden imports.
pyproject.toml Adds packaging excludes to prevent test code from being included in release artifacts.
.github/workflows/package-sanity.yml New CI job that runs poetry build then validates artifact contents via the new checker.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pyproject.toml
Comment on lines +12 to +15
exclude = [
"qdrant_client/**/tests",
"qdrant_client/**/test_*.py",
]
Comment on lines +75 to +83
path = Path(target)
if path.is_dir():
dists.extend(sorted(path.glob("*.whl")))
dists.extend(sorted(path.glob("*.tar.gz")))
elif path.is_file():
dists.append(path)
else:
raise SystemExit(f"error: no such file or directory: {target}")
return dists

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
.github/workflows/package-sanity.yml (1)

12-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider upgrading pinned action versions.

actions/checkout@v2.7.0 and actions/setup-python@v2.3.4 are significantly outdated (current versions are v4.x and v5.x respectively). While pinning to commit hashes is good practice, these older versions may miss security patches and performance improvements.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/package-sanity.yml around lines 12 - 15, Upgrade the
pinned actions in the workflow: replace the outdated checkout action referenced
by the checkout step with a current v4 commit SHA, and replace the setup-python
action referenced by the “Set up Python” step with a current v5 commit SHA;
update the version comments to match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/package-sanity.yml:
- Line 12: Update the actions/checkout step in the workflow to set
persist-credentials: false, ensuring the GITHUB_TOKEN is not stored in the
repository’s Git configuration after checkout.

In `@tools/check_package_contents.py`:
- Around line 55-68: _iter_python_files silently accepts unsupported
distribution types, allowing check_distribution to report success without
inspection. Add an explicit error or warning for paths that are neither wheel
files nor .tar.gz archives, using the existing check_distribution/main
error-reporting flow so the command cannot report “passed” for an uninspected
file.

---

Nitpick comments:
In @.github/workflows/package-sanity.yml:
- Around line 12-15: Upgrade the pinned actions in the workflow: replace the
outdated checkout action referenced by the checkout step with a current v4
commit SHA, and replace the setup-python action referenced by the “Set up
Python” step with a current v5 commit SHA; update the version comments to match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00b6cf12-b3b2-4ba1-ab46-f1913cc073f6

📥 Commits

Reviewing files that changed from the base of the PR and between e70d81f and d15fdf3.

📒 Files selected for processing (3)
  • .github/workflows/package-sanity.yml
  • pyproject.toml
  • tools/check_package_contents.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml

name: Check built package contents

steps:
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Set persist-credentials: false on checkout.

The actions/checkout action defaults to persist-credentials: true, which stores the GITHUB_TOKEN in .git/config. While this workflow doesn't upload artifacts, the token isn't needed after checkout, so disabling persistence is a simple security hardening.

🔒️ Proposed fix
       - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 12-12: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/package-sanity.yml at line 12, Update the actions/checkout
step in the workflow to set persist-credentials: false, ensuring the
GITHUB_TOKEN is not stored in the repository’s Git configuration after checkout.

Source: Linters/SAST tools

Comment on lines +55 to +68
def _iter_python_files(dist: Path) -> Iterator[tuple[str, str]]:
"""Yield ``(member_path, source)`` for every ``.py`` file in a distribution."""
if dist.suffix == ".whl":
with zipfile.ZipFile(dist) as archive:
for name in archive.namelist():
if name.endswith(".py"):
yield name, archive.read(name).decode("utf-8", "replace")
elif dist.name.endswith(".tar.gz"):
with tarfile.open(dist, "r:gz") as archive:
for member in archive.getmembers():
if member.isfile() and member.name.endswith(".py"):
handle = archive.extractfile(member)
if handle is not None:
yield member.name, handle.read().decode("utf-8", "replace")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Warn on unsupported distribution file types.

_iter_python_files silently yields nothing for files that aren't .whl or .tar.gz. If a user passes such a file directly, check_distribution returns an empty problem list and main reports "passed" without actually inspecting the file, giving a false sense of security.

🛡️ Proposed fix
 def _iter_python_files(dist: Path) -> Iterator[tuple[str, str]]:
     """Yield ``(member_path, source)`` for every ``.py`` file in a distribution."""
     if dist.suffix == ".whl":
         with zipfile.ZipFile(dist) as archive:
             for name in archive.namelist():
                 if name.endswith(".py"):
                     yield name, archive.read(name).decode("utf-8", "replace")
     elif dist.name.endswith(".tar.gz"):
         with tarfile.open(dist, "r:gz") as archive:
             for member in archive.getmembers():
                 if member.isfile() and member.name.endswith(".py"):
                     handle = archive.extractfile(member)
                     if handle is not None:
                         yield member.name, handle.read().decode("utf-8", "replace")
+    else:
+        raise SystemExit(f"error: unsupported distribution format: {dist.name}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _iter_python_files(dist: Path) -> Iterator[tuple[str, str]]:
"""Yield ``(member_path, source)`` for every ``.py`` file in a distribution."""
if dist.suffix == ".whl":
with zipfile.ZipFile(dist) as archive:
for name in archive.namelist():
if name.endswith(".py"):
yield name, archive.read(name).decode("utf-8", "replace")
elif dist.name.endswith(".tar.gz"):
with tarfile.open(dist, "r:gz") as archive:
for member in archive.getmembers():
if member.isfile() and member.name.endswith(".py"):
handle = archive.extractfile(member)
if handle is not None:
yield member.name, handle.read().decode("utf-8", "replace")
def _iter_python_files(dist: Path) -> Iterator[tuple[str, str]]:
"""Yield ``(member_path, source)`` for every ``.py`` file in a distribution."""
if dist.suffix == ".whl":
with zipfile.ZipFile(dist) as archive:
for name in archive.namelist():
if name.endswith(".py"):
yield name, archive.read(name).decode("utf-8", "replace")
elif dist.name.endswith(".tar.gz"):
with tarfile.open(dist, "r:gz") as archive:
for member in archive.getmembers():
if member.isfile() and member.name.endswith(".py"):
handle = archive.extractfile(member)
if handle is not None:
yield member.name, handle.read().decode("utf-8", "replace")
else:
raise SystemExit(f"error: unsupported distribution format: {dist.name}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/check_package_contents.py` around lines 55 - 68, _iter_python_files
silently accepts unsupported distribution types, allowing check_distribution to
report success without inspection. Add an explicit error or warning for paths
that are neither wheel files nor .tar.gz archives, using the existing
check_distribution/main error-reporting flow so the command cannot report
“passed” for an uninspected file.

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 release sanity check to CI

2 participants