Skip to content

Automatically generate Python Typing stub files for Cython extensions (_p4p, _gw)#212

Open
Monarda wants to merge 30 commits into
epics-base:masterfrom
Monarda:typing_stubs_automate
Open

Automatically generate Python Typing stub files for Cython extensions (_p4p, _gw)#212
Monarda wants to merge 30 commits into
epics-base:masterfrom
Monarda:typing_stubs_automate

Conversation

@Monarda

@Monarda Monarda commented Jun 29, 2026

Copy link
Copy Markdown

This is a continuation of PR #209, the changes are significant enough that I have made them on a new branch which has necessitated a new PR. Apologies for the churn!

The changes outside of setup.py are relatively minor and some have already been noted in #209. mypy has now been added to the CI/CD build.yml and pyproject.yml for Python versions >=3.8. stubgen appears to have been relatively stable since Python 3.9.

There are more significant changes to setup.py which has had GenerateStubs added. This function is automatically called when a wheel is built through subclassing BuildExt. Lines 314 & 315 are where the chain of logic therefore begins. It is also possible to manually generate stub files by running the command python setup.py generate_stubs.

GenerateStubs is relatively straightforward, made more complex by needing to be cross-platform compatible and coping with the wheel build environment. It:

  • finds stubgen
  • makes sure the environment variables allow stubgen to find pvxslibs
  • runs stubgen and verifies that it has generated the .pyi files
  • copies the files into the build environment at this point so that they will be correctly picked up.

Any errors above do not terminate setup but instead print warnings to stderr. I'm happy to change this if you prefer.

Note, this code was generated with Claude and then manually checked and improved. I thought it was important to include this disclaimer.

Comment thread setup.py
import sys
import platform
if sys.platform=='linux2' and not sysconfig.get_config_var('Py_DEBUG'):
if sys.platform.startswith('linux') and not sysconfig.get_config_var('Py_DEBUG'):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This appears to fix a small bug in the existing versions of setup.py.

sys.platform will return linux on Python3 so this will not trigger. sys.platform.startswith('linux') makes it compatible with Python 2.7 and 3.

Comment thread setup.py
cxxflags += ['-g0']

elif platform.system()=='Darwin':
elif sys.platform == 'darwin':

@Monarda Monarda Jun 29, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Made consistent with other usages. Removes the need to import platform.

Comment thread .github/workflows/build.yml Outdated
Comment on lines +97 to +98
cothread setuptools_scm
if python -c "import sys; sys.exit(0 if sys.version_info >= (3, 8) else 1)"; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pip understands conditionals already, no need for special machinery.

        "mypy; python_version>='3.8'"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Perhaps I'm misunderstanding the syntax here, but isn't this part of build.yml not using the information in pyproject.toml?

Experimenting shows that

pip download $PRE -d output \
                --only-binary numpy,Cython \
                "${{ matrix.cy || 'Cython' }}" \
                "${{ matrix.numpy || 'numpy' }}" \
                setuptools_dso pvxslibs wheel ply nose2 \
                cothread setuptools_scm mypy

produces a GitHub CI/CD error warning of ERROR: You need Python 3.5 or later to use mypy.

I could replace this with pip download . but that would presumably interfere with the matrix?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pip download ... accepts the same syntax as would be found in pyproject.toml.

            pip download $PRE -d output \
                --only-binary numpy,Cython \
                "${{ matrix.cy || 'Cython' }}" \
                "${{ matrix.numpy || 'numpy' }}" \
                setuptools_dso pvxslibs wheel ply nose2 \
                cothread setuptools_scm \
                "mypy; python_version>='3.8'"

@Monarda Monarda Jun 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

D'oh! I understand; I've made the change now.

Comment thread setup.py Outdated
Comment on lines +82 to +83
stubgen = shutil.which('stubgen', path=sysconfig.get_path('scripts')) \
or shutil.which('stubgen')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does stubgen have an import-able main()? Like pip ... vs. python -m pip .... Enables use of sys.executable as an entry point, which avoids possible confusion with $PATH referencing other environments.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, I've switched to using sys.executable as you suggested which means _find_stubgen() is gone. I also experimented with calling stubgen.main() directly, but it wouldn't be any simpler on Windows due to its using multiprocessing.

Comment thread setup.py

# Fully-qualified module names for which .pyi stubs are generated.
# Must match the Extension name= values below.
_STUB_MODULES = ['p4p._p4p', 'p4p._gw']

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I wasn't sure whether to use a glob here (*.pyi) or specify the files explicitly. I've taken the more conservative approach of being explicit.

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