Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ultraplot/*.pyi linguist-generated=true
ultraplot/**/*.pyi linguist-generated=true
52 changes: 51 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
- 'environment.yml'
- '.github/workflows/**'
- 'tools/ci/**'
- 'tools/generate_stubs.py'

select-tests:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -99,6 +100,7 @@ jobs:
--always-full 'pyproject.toml' \
--always-full 'environment.yml' \
--always-full 'ultraplot/__init__.py' \
--always-full 'tools/generate_stubs.py' \
--ignore 'docs/**' \
--ignore 'README.rst'
echo "Selection output:"
Expand Down Expand Up @@ -138,6 +140,53 @@ jobs:
echo "Detected test matrix: $(echo "$OUTPUT" | jq -c '.test_matrix')"
python tools/ci/version_support.py --format github-output >> $GITHUB_OUTPUT

stubs:
name: Static API stubs
runs-on: ubuntu-latest
needs:
- run-if-changes
if: always() && needs.run-if-changes.outputs.run == 'true'
steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: pip

- name: Install UltraPlot and typing tools
run: pip install -e ".[typing]"

- name: Verify generated stubs
run: python tools/generate_stubs.py --check

- name: Check Pylance-compatible consumption
run: basedpyright tools/ci/stub_consumer.py --level error

- name: Check Pyrefly consumption and generated syntax
run: |
pyrefly check tools/ci/stub_consumer.py \
--search-path . \
--python-interpreter-path "$(command -v python)" \
--progress-bar no
pyrefly check 'ultraplot/**/*.pyi' \
--search-path . \
--python-interpreter-path "$(command -v python)" \
--ignore-missing-imports icecream \
--ignore-missing-imports cartopy \
--ignore-missing-imports cartopy.crs \
--ignore-missing-imports cartopy.feature \
--ignore-missing-imports cartopy.io \
--ignore-missing-imports cartopy.mpl.feature_artist \
--ignore-missing-imports cartopy.mpl.geoaxes \
--ignore-missing-imports cartopy.mpl.gridliner \
--ignore-missing-imports cartopy.mpl.path \
--ignore-missing-imports cartopy.mpl.ticker \
--ignore-missing-imports cftime \
--ignore-missing-imports mpl_toolkits.basemap \
--ignore-missing-imports matplotlib.fontconfig_pattern \
--progress-bar no

coverage:
name: Coverage
runs-on: ubuntu-latest
Expand Down Expand Up @@ -213,14 +262,15 @@ jobs:
needs:
- build
- run-if-changes
- stubs
if: always()
runs-on: ubuntu-latest
steps:
- run: |
if [[ '${{ needs.run-if-changes.outputs.run }}' == 'false' ]]; then
echo "No changes detected, tests skipped."
else
if [[ '${{ needs.build.result }}' == 'success' ]]; then
if [[ '${{ needs.build.result }}' == 'success' && '${{ needs.stubs.result }}' == 'success' ]]; then
echo "All tests passed successfully!"
else
echo "Tests failed!"
Expand Down
16 changes: 16 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# Import statements
import datetime
import inspect
import logging
import os
import re
Expand Down Expand Up @@ -637,5 +638,20 @@ def _replace_snippet(match):
pass


def process_signature(
app, what, name, obj, options, signature, return_annotation
):
"""Use compact signatures marked by UltraPlot only in generated docs."""
marked = getattr(obj, "__ultraplot_doc_signature__", None)
if marked is None and inspect.ismethod(obj):
marked = getattr(obj.__func__, "__ultraplot_doc_signature__", None)
if marked is None and inspect.isclass(obj):
marked = getattr(obj.__init__, "__ultraplot_doc_signature__", None)
if marked is not None:
return marked, return_annotation
return signature, return_annotation


def setup(app):
app.connect("autodoc-process-docstring", process_docstring)
app.connect("autodoc-process-signature", process_signature)
31 changes: 31 additions & 0 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,37 @@ When adding a new submodule, make sure it is compatible with the lazy loader:
By following these steps, your module will integrate cleanly with the lazy loading
system without requiring manual registry updates.

Editor type information and docstrings
--------------------------------------

UltraPlot ships generated ``.pyi`` files so static analysis tools such as Pylance
and Pyrefly can see the public API and fully expanded docstrings without importing
the package. The runtime modules remain the source of truth and continue to use the
lazy loader.

After changing a Python signature, annotation, public import, or docstring snippet,
install the pinned typing tools, regenerate the stubs from the repository root, and
commit the updated ``.pyi`` files:

.. code-block:: bash

pip install -e ".[typing]"
python tools/generate_stubs.py

Installation does not generate or modify these files. Release artifacts include the
stubs that were generated and checked into the repository. The generator runs
Pyrefly against an isolated source-only package, merges its inferred annotations
into a complete syntax-derived representation of the package, and statically
expands registered docstring snippets. This preserves declarations that Pyrefly
cannot discover through decorators or lazy loading.

To rerun inference and verify that every committed stub is up to date without
changing files, run:

.. code-block:: bash

python tools/generate_stubs.py --check


.. _contrib_pr:

Expand Down
Loading