Skip to content

Commit d83231e

Browse files
authored
Add automatically-generated .pyi files to cuda_core (#2061)
* Add infrastructure for type-checking cuda_core * Update all type annotations so they pass type-checking * Add new .pyi files * Pin Cython version * Address feedback in the PR * Fix tests * Complete type annotations for public APIs * Fix type annotations * Fix previous merge * Remove pre-cuda.bindings imports * Fix isinstance check * Add CONTRIBUTING docs * More type annotations * Fix build * Fix types
1 parent 9b3d3cf commit d83231e

118 files changed

Lines changed: 10615 additions & 646 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ repos:
1919
hooks:
2020
- id: ruff-check
2121
args: [--fix, --show-fixes]
22-
exclude: ^cuda_bindings/cuda/bindings/_internal/_fast_enum\.py$
22+
exclude: (^cuda_bindings/cuda/bindings/_internal/_fast_enum\.py$)|(.*\.pyi$)
2323
- id: ruff-format
24+
exclude: .*\.pyi$
2425

2526
- repo: local
2627
hooks:
@@ -42,6 +43,16 @@ repos:
4243
language: system
4344
files: '^.*/docs/source/.*\.md$'
4445

46+
- id: stubgen-pyx-cuda-core
47+
name: Generate .pyi stubs for cuda_core
48+
entry: stubgen-pyx cuda_core/cuda --continue-on-error --include-private
49+
language: python
50+
files: ^cuda_core/cuda/.*\.(pyx|pxd)$
51+
pass_filenames: false
52+
additional_dependencies:
53+
- stubgen-pyx==0.2.6
54+
- Cython==3.2.4
55+
4556
# Standard hooks
4657
- repo: https://github.com/pre-commit/pre-commit-hooks
4758
rev: "3e8a8703264a2f4a69428a0aa4dcb512790b2c8c" # frozen: v6.0.0
@@ -56,7 +67,7 @@ repos:
5667
- id: check-yaml
5768
- id: debug-statements
5869
- id: end-of-file-fixer
59-
exclude: &gen_exclude '^(?:cuda_python/README\.md|cuda_bindings/cuda/bindings/.*\.in?|cuda_bindings/docs/source/module/.*\.rst?)$'
70+
exclude: &gen_exclude '^(?:cuda_python/README\.md|cuda_bindings/cuda/bindings/.*\.in?|cuda_bindings/docs/source/module/.*\.rst?|.*\.pyi)$'
6071
- id: mixed-line-ending
6172
- id: trailing-whitespace
6273
exclude: |
@@ -79,9 +90,18 @@ repos:
7990
rev: 8e5c80792e2ec0c87804d8ef915bf35e2caea6da # frozen: v1.20.0
8091
hooks:
8192
- id: mypy
93+
alias: mypy-pathfinder
8294
name: mypy-pathfinder
8395
files: ^cuda_pathfinder/cuda/.*\.py$ # Exclude tests directory
8496
args: [--config-file=cuda_pathfinder/pyproject.toml]
97+
- id: mypy
98+
alias: mypy-cuda-core
99+
name: mypy-cuda-core
100+
files: ^cuda_core/cuda/.*\.(py|pyi)$
101+
pass_filenames: false
102+
args: [--config-file=cuda_core/pyproject.toml, cuda_core/cuda/core]
103+
additional_dependencies:
104+
- numpy
85105

86106
- repo: https://github.com/rhysd/actionlint
87107
rev: "914e7df21a07ef503a81201c76d2b11c789d3fca" # frozen: v1.7.12

.spdx-ignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ cuda_core/cuda/core/_include/dlpack.h
1313
cuda_core/cuda/core/_include/aoti_shim.h
1414
cuda_core/cuda/core/_include/aoti_shim.def
1515

16+
# Generated by stubgen-pyx; regenerated on every commit so a header would be lost
17+
cuda_core/cuda/**/*.pyi
18+
1619
qa/ctk-next.drawio.svg

CONTRIBUTING.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,42 @@ Thank you for your interest in contributing to CUDA Python! Based on the type of
1717

1818
## Table of Contents
1919

20-
- [Pre-commit](#pre-commit)
21-
- [Code signing](#code-signing)
22-
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
23-
- [CI infrastructure overview](#ci-infrastructure-overview)
24-
20+
- [Contributing to CUDA Python](#contributing-to-cuda-python)
21+
- [Table of Contents](#table-of-contents)
22+
- [Type stubs for cuda.core](#type-stubs-for-cudacore)
23+
- [Pre-commit](#pre-commit)
24+
- [Code signing](#code-signing)
25+
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
26+
- [CI infrastructure overview](#ci-infrastructure-overview)
27+
- [CI Pipeline Flow](#ci-pipeline-flow)
28+
- [Pipeline Execution Details](#pipeline-execution-details)
29+
- [Branch-specific Artifact Flow](#branch-specific-artifact-flow)
30+
- [Main Branch](#main-branch)
31+
- [Backport Branches](#backport-branches)
32+
- [Key Infrastructure Details](#key-infrastructure-details)
33+
- [Code coverage](#code-coverage)
34+
35+
36+
## Type stubs for cuda.core
37+
38+
`cuda.core` is a PEP 561-compliant package: it ships a `py.typed` marker and
39+
`.pyi` stub files alongside the Cython extensions. The stubs
40+
are checked into the repository.
41+
42+
**You do not need to run stubgen-pyx manually.** A pre-commit hook
43+
regenerates the corresponding `.pyi` files automatically when you commit.
44+
The results are then also tested with `mypy`.
45+
46+
A few things to keep in mind:
47+
48+
- **Do not edit `.pyi` files by hand.** They are regenerated from the Cython
49+
sources on every commit that touches those sources; manual edits will be
50+
overwritten.
51+
- **Type annotations belong in the `.pyx`/`.pxd` source.** stubgen-pyx reads
52+
Cython type annotations and docstrings to build the stubs, so keeping the
53+
source well-annotated is the right way to improve stub quality.
54+
- **To run mypy manually (outside of pre-commit)**: `python -m mypy
55+
--config-file cuda_core/pyproject.toml
2556

2657
## Pre-commit
2758
This project uses [pre-commit.ci](https://pre-commit.ci/) with GitHub Actions. All pull requests are automatically checked for pre-commit compliance, and any pre-commit failures will block merging until resolved.

cuda_core/MANIFEST.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5-
recursive-include cuda/core *.pyx *.pxd *.pxi
5+
recursive-include cuda/core *.pyx *.pxd *.pxi *.pyi
66
recursive-include cuda/core/_cpp *.cpp *.hpp
77
recursive-include cuda/core/_include *.h *.hpp
8+
include cuda/core/py.typed

cuda_core/cuda/core/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from cuda.core._version import __version__
66

77

8-
def _import_versioned_module():
8+
def _import_versioned_module() -> None:
99
import importlib
1010

1111
from cuda import bindings
@@ -28,7 +28,7 @@ def _import_versioned_module():
2828
del _import_versioned_module
2929

3030

31-
def _patch_rlcompleter_for_cython_properties():
31+
def _patch_rlcompleter_for_cython_properties() -> None:
3232
# TODO: This can be removed when Python 3.13 is our minimum-supported version:
3333
# https://github.com/python/cpython/pull/149577
3434

@@ -55,13 +55,13 @@ def _patch_rlcompleter_for_cython_properties():
5555
# member_descriptor types, which are what Cython uses for properties on cdef
5656
# classes.
5757
class _PatchedPropMeta(type):
58-
def __instancecheck__(cls, inst):
58+
def __instancecheck__(cls, inst: object) -> bool:
5959
return isinstance(inst, (property, GetSetDescriptorType, MemberDescriptorType))
6060

6161
class _PatchedProperty(metaclass=_PatchedPropMeta):
6262
pass
6363

64-
rlcompleter.property = _PatchedProperty
64+
rlcompleter.property = _PatchedProperty # type: ignore[attr-defined]
6565

6666

6767
_patch_rlcompleter_for_cython_properties()

cuda_core/cuda/core/_context.pyi

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# This file was generated by stubgen-pyx v0.2.6 from cuda_core/cuda/core/_context.pyx
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Sequence
6+
from dataclasses import dataclass
7+
8+
import cuda.bindings.driver
9+
from cuda.core._device_resources import (DeviceResources, SMResource,
10+
WorkqueueResource)
11+
from cuda.core._stream import Stream
12+
13+
14+
class Context:
15+
"""CUDA context wrapper.
16+
17+
Context objects represent CUDA contexts and cannot be instantiated directly.
18+
Use Device or Stream APIs to obtain context objects.
19+
"""
20+
21+
def close(self):
22+
"""Release this context wrapper's underlying CUDA handles."""
23+
24+
def __init__(self, *args, **kwargs) -> None:
25+
...
26+
27+
@property
28+
def handle(self) -> cuda.bindings.driver.CUcontext | None:
29+
"""Return the underlying CUcontext handle."""
30+
31+
@property
32+
def _handle(self) -> cuda.bindings.driver.CUcontext | None:
33+
...
34+
35+
@property
36+
def is_green(self) -> bool:
37+
"""True if this context was created from device resources."""
38+
39+
@property
40+
def resources(self) -> DeviceResources:
41+
"""Query the hardware resources provisioned for this context.
42+
43+
For green contexts, returns the resources this context was created
44+
with (SM partition, workqueue config). For primary contexts, returns
45+
the full device resources.
46+
47+
Raises :class:`RuntimeError` if the context has been closed.
48+
"""
49+
50+
def create_stream(self, options: object=None) -> Stream:
51+
"""Create a new stream bound to this green context.
52+
53+
This method is only available on green contexts. For primary
54+
contexts, use :meth:`Device.create_stream` instead.
55+
56+
Parameters
57+
----------
58+
options : :obj:`~_stream.StreamOptions`, optional
59+
Customizable dataclass for stream creation options.
60+
61+
Returns
62+
-------
63+
:obj:`~_stream.Stream`
64+
Newly created stream object.
65+
"""
66+
67+
def __eq__(self, other: object) -> bool:
68+
...
69+
70+
def __hash__(self) -> int:
71+
...
72+
73+
def __repr__(self) -> str:
74+
...
75+
76+
@dataclass
77+
class ContextOptions:
78+
"""Options for context creation.
79+
80+
Attributes
81+
----------
82+
resources : :obj:`~cuda.core.typing.DeviceResourcesType`
83+
Device resources used to create a green context.
84+
"""
85+
resources: DeviceResourcesType
86+
__all__ = ['Context', 'ContextOptions']
87+
DeviceResourcesType = Sequence[SMResource | WorkqueueResource]

cuda_core/cuda/core/_context.pyx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ from __future__ import annotations
66

77
from collections.abc import Sequence
88
from dataclasses import dataclass
9+
from typing import TYPE_CHECKING
910

1011
from cuda.bindings cimport cydriver
1112
from cuda.core._device_resources cimport DeviceResources, SMResource, WorkqueueResource
@@ -20,9 +21,12 @@ from cuda.core._resource_handles cimport (
2021
as_intptr,
2122
as_py,
2223
)
23-
from cuda.core._stream import Stream, StreamOptions
24+
from cuda.core._stream import Stream
2425
from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
2526

27+
if TYPE_CHECKING:
28+
import cuda.bindings.driver # no-cython-lint
29+
2630

2731
__all__ = ['Context', 'ContextOptions']
2832

@@ -37,7 +41,7 @@ cdef class Context:
3741
Use Device or Stream APIs to obtain context objects.
3842
"""
3943

40-
def __init__(self, *args, **kwargs):
44+
def __init__(self, *args, **kwargs) -> None:
4145
raise RuntimeError("Context objects cannot be instantiated directly. Please use Device or Stream APIs.")
4246

4347
@staticmethod
@@ -58,7 +62,7 @@ cdef class Context:
5862
return Context._from_handle(cls, h_context, device_id)
5963

6064
@property
61-
def handle(self):
65+
def handle(self) -> cuda.bindings.driver.CUcontext | None:
6266
"""Return the underlying CUcontext handle."""
6367
if not self._h_context:
6468
return None
@@ -67,7 +71,7 @@ cdef class Context:
6771
return as_py(self._h_context)
6872

6973
@property
70-
def _handle(self):
74+
def _handle(self) -> cuda.bindings.driver.CUcontext | None:
7175
return self.handle
7276

7377
@property
@@ -91,7 +95,7 @@ cdef class Context:
9195
raise RuntimeError("Cannot query resources on a closed context")
9296
return DeviceResources._init_from_ctx(self._h_context, self._device_id)
9397

94-
def create_stream(self, options: StreamOptions | None = None):
98+
def create_stream(self, options: object = None) -> Stream:
9599
"""Create a new stream bound to this green context.
96100

97101
This method is only available on green contexts. For primary
@@ -130,7 +134,7 @@ cdef class Context:
130134
)
131135
self._h_context.reset()
132136

133-
def __eq__(self, other):
137+
def __eq__(self, other: object) -> bool:
134138
if not isinstance(other, Context):
135139
return NotImplemented
136140
cdef Context _other = <Context>other

0 commit comments

Comments
 (0)