Skip to content

feat: ship PEP 561 type stubs for the public python API - #17

Closed
mdealencar wants to merge 1 commit into
artem-ogre:masterfrom
mdealencar:type-stubs
Closed

mdealencar wants to merge 1 commit into
artem-ogre:masterfrom
mdealencar:type-stubs

Conversation

@mdealencar

Copy link
Copy Markdown
Contributor

Ship type stubs for the public pythoncdt package

Motivation & Comparison with master

In master, commit ab92c53 introduced _core.pyi for the internal compiled extension module pythoncdt._core. However, the top-level public package pythoncdt relied on a wildcard import from ._core import * without an __init__.pyi stub and without declaring __all__.

While this runtime setup allows pythoncdt.Triangulation to work, it has concrete shortcomings for static typing and tooling:

  1. PEP 484 & PEP 561 compliance: Wildcard imports in typed packages do not re-export symbols. In downstream projects using strict type checking (Pyright/Pylance reportPrivateImportUsage or Mypy with --no-implicit-reexport), importing from pythoncdt causes unexported symbol errors.
  2. mypy.stubtest CI gap: Because pythoncdt lacked an __init__.pyi, mypy.stubtest pythoncdt reported error: pythoncdt failed to find stubs and bypassed validating the top-level package entirely, checking only the internal _core submodule.
  3. LSP and IDE autocompletion: Without explicit package stubs and __all__, language servers (such as pyrefly lsp or Pylance) cannot cleanly distinguish the intended public surface from internal module references.
  4. Runtime namespace pollution: Without __all__, runtime consumers executing from pythoncdt import * inadvertently import internal module attributes like _core.

Changes in this PR

  • Ship public package stubs (src/pythoncdt/__init__.pyi): Explicitly declare __all__ and import all public classes, enums, functions, and constants, providing a clean PEP 561 interface for downstream type checkers.
  • Synchronize runtime exports (src/pythoncdt/__init__.py): Replace the wildcard import with explicit imports and declare runtime __all__ in sync with __init__.pyi, resolving mypy.stubtest requirements and preventing _core leakage.
  • Add export regression test (tests/test_bindings.py): Add test_all_exports to dynamically verify that every public symbol from _core is included in __all__ and resolves on pythoncdt, preventing missed bindings without duplicating symbol lists.

Add type stubs for pythoncdt in src/pythoncdt/__init__.pyi and explicitly export the public API symbols in src/pythoncdt/__init__.py.

While pythoncdt provided type stubs for the internal compiled extension in _core.pyi, the top-level package relied on a wildcard import without declaring type stubs or __all__. Static type checkers and language servers could not recognize re-exported symbols when consumers imported pythoncdt directly. Explicitly re-exporting the public classes, functions, and constants from _core in __init__.pyi and declaring __all__ exposes full type information for downstream users.
@artem-ogre

artem-ogre commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Hi, and thanks for the PR.

Could you please elaborate on how to reproduce the stated problems?

Strict type checkers
mypy --strict --no-implicit-reexport and pyright in strict mode (with reportPrivateImportUsage as an error) both come back clean when importing Triangulation, V2d, Edge, area, etc. from pythoncdt. The typing spec treats from ._core import * as a re-export, so the wildcard is fine.

stubtest
python -m mypy.stubtest pythoncdt --allowlist tests/stubtest-allowlist.txt (same as the CI job) prints Success: no issues found in 2 modules. Those two modules are pythoncdt and pythoncdt._core, so the top-level package is already checked. The py.typed marker lets mypy use __init__.py directly.

_core leaking via import *
without __all__, star-imports skip underscore names, so from pythoncdt import * only brings in the public names?

@mdealencar

Copy link
Copy Markdown
Contributor Author

Sorry about that, your assessment is correct. I checked what was done differently in condeltri and mistakenly accepted the AI suggestion that was required for full static checker functionality. This means transitioning from condeltri to pythoncdt is fully drop-in. The only missing bit is the conda package.

@mdealencar mdealencar closed this Oct 12, 2026
@artem-ogre

artem-ogre commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

no worries at all :)

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.

2 participants