Repository: NVIDIA/cuda-python
Pull request #493: Ensure `cuda` is a namespace package & Improve warnings
Author: login: leofang
Labels: name: bug
description: Something isn't working
name: enhancement
description: Any code-related improvements
name: P0
description: High priority - Must do!
name: cuda.bindings
description: Everything related to the cuda.bindings module
Created: 2025-03-06T03:37:21Z | Merged: 2025-04-21T15:42:09Z

Close #477. Close #162.

Linked issue #477: Remove `cuda/__init__.py` to avoid confusing various tools
It seems hatchling (according to @rwgk from https://github.com/NVIDIA/cccl/pull/3201#issue-2751245974) and pip (#476) are confused by the conflict between our intention of `cuda` being a namespace package and `cuda/__init__.py` still exists today.

Diff:
diff --git a/cuda_bindings/cuda/__init__.pxd b/cuda_bindings/cuda/__init__.pxd
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/cuda_bindings/cuda/__init__.py b/cuda_bindings/cuda/__init__.py
deleted file mode 100644
index 56b8d5a8c62..00000000000
--- a/cuda_bindings/cuda/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-def __getattr__(name):
-    if name == "__version__":
-        import warnings
-
-        warnings.warn(
-            "accessing cuda.__version__ is deprecated, please switch to use cuda.bindings.__version__ instead",
-            DeprecationWarning,
-            stacklevel=2,
-        )
-        from . import bindings
-
-        return bindings.__version__
-
-    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
diff --git a/cuda_bindings/cuda/ccuda.pxd b/cuda_bindings/cuda/ccuda.pxd
index 73f3fc5cb4f..2a0f2672fb7 100644
--- a/cuda_bindings/cuda/ccuda.pxd
+++ b/cuda_bindings/cuda/ccuda.pxd
@@ -2,6 +2,11 @@ from cuda.bindings.cydriver cimport *
 
 cdef extern from *:
     """
+    #ifdef _MSC_VER
     #pragma message ( "The cuda.ccuda module is deprecated and will be removed in a future release, " \
                       "please switch to use the cuda.bindings.cydriver module instead." )
+    #else
+    #warning The cuda.ccuda module is deprecated and will be removed in a future release, \
+             please switch to use the cuda.bindings.cydriver module instead.
+    #endif
     """
diff --git a/cuda_bindings/cuda/ccuda.pyx b/cuda_bindings/cuda/ccuda.pyx
index a07525bbdaa..0ad01d58f0f 100644
--- a/cuda_bindings/cuda/ccuda.pyx
+++ b/cuda_bindings/cuda/ccuda.pyx
@@ -1,12 +1,4 @@
 from cuda.bindings.cydriver cimport *
-
-cdef extern from *:
-    """
-    #pragma message ( "The cuda.ccuda module is deprecated and will be removed in a future release, " \
-                      "please switch to use the cuda.bindings.cydriver module instead." )
-    """
-
-
 from cuda.bindings import cydriver
 __pyx_capi__ = cydriver.__pyx_capi__
 del cydriver
diff --git a/cuda_bindings/cuda/ccudart.pxd b/cuda_bindings/cuda/ccudart.pxd
index b32eece8149..f7c3def5d8b 100644
--- a/cuda_bindings/cuda/ccudart.pxd
+++ b/cuda_bindings/cuda/ccudart.pxd
@@ -2,6 +2,11 @@ from cuda.bindings.cyruntime cimport *
 
 cdef extern from *:
     """
+    #ifdef _MSC_VER
     #pragma message ( "The cuda.ccudart module is deprecated and will be removed in a future release, " \
                       "please switch to use the cuda.bindings.cyruntime module instead." )
+    #else
+    #warning The cuda.ccudart module is deprecated and will be removed in a future release, \
+             please switch to use the cuda.bindings.cyruntime module instead.
+    #endif
     """
diff --git a/cuda_bindings/cuda/ccudart.pyx b/cuda_bindings/cuda/ccudart.pyx
index 3cef081123c..17b3c1ea8ed 100644
--- a/cuda_bindings/cuda/ccudart.pyx
+++ b/cuda_bindings/cuda/ccudart.pyx
@@ -1,12 +1,4 @@
 from cuda.bindings.cyruntime cimport *
-
-cdef extern from *:
-    """
-    #pragma message ( "The cuda.ccudart module is deprecated and will be removed in a future release, " \
-                      "please switch to use the cuda.bindings.cyruntime module instead." )
-    """
-
-
 from cuda.bindings import cyruntime
 __pyx_capi__ = cyruntime.__pyx_capi__
 del cyruntime
diff --git a/cuda_bindings/cuda/cnvrtc.pxd b/cuda_bindings/cuda/cnvrtc.pxd
index d4034084e2f..ed81707139b 100644
--- a/cuda_bindings/cuda/cnvrtc.pxd
+++ b/cuda_bindings/cuda/cnvrtc.pxd
@@ -2,6 +2,11 @@ from cuda.bindings.cynvrtc cimport *
 
 cdef extern from *:
     """
+    #ifdef _MSC_VER
     #pragma message ( "The cuda.cnvrtc module is deprecated and will be removed in a future release, " \
                       "please switch to use the cuda.bindings.cynvrtc module instead." )
+    #else
+    #warning The cuda.cnvrtc module is deprecated and will be removed in a future release, \
+             please switch to use the cuda.bindings.cynvrtc module instead.
+    #endif
     """
diff --git a/cuda_bindings/cuda/cnvrtc.pyx b/cuda_bindings/cuda/cnvrtc.pyx
index 405d7de00e9..319512fa589 100644
--- a/cuda_bindings/cuda/cnvrtc.pyx
+++ b/cuda_bindings/cuda/cnvrtc.pyx
@@ -1,12 +1,4 @@
 from cuda.bindings.cynvrtc cimport *
-
-cdef extern from *:
-    """
-    #pragma message ( "The cuda.cnvrtc module is deprecated and will be removed in a future release, " \
-                      "please switch to use the cuda.bindings.cynvrtc module instead." )
-    """
-
-
 from cuda.bindings import cynvrtc
 __pyx_capi__ = cynvrtc.__pyx_capi__
 del cynvrtc
diff --git a/cuda_bindings/cuda/cuda.pyx b/cuda_bindings/cuda/cuda.pyx
index f8b197f7163..981b62228cb 100644
--- a/cuda_bindings/cuda/cuda.pyx
+++ b/cuda_bindings/cuda/cuda.pyx
@@ -5,10 +5,15 @@ from cuda.bindings.driver import *
 
 cdef extern from *:
     """
+    #ifdef _MSC_VER
     #pragma message ( "The cuda.cuda module is deprecated and will be removed in a future release, " \
                       "please switch to use the cuda.bindings.driver module instead." )
+    #else
+    #warning The cuda.cuda module is deprecated and will be removed in a future release, \
+             please switch to use the cuda.bindings.driver module instead.
+    #endif
     """
 
 
 _warnings.warn("The cuda.cuda module is deprecated and will be removed in a future release, "
-               "please switch to use the cuda.bindings.driver module instead.", DeprecationWarning, stacklevel=2)
+               "please switch to use the cuda.bindings.driver module instead.", FutureWarning, stacklevel=2)
diff --git a/cuda_bindings/cuda/cudart.pyx b/cuda_bindings/cuda/cudart.pyx
index 8c342df8050..e3d2986bb76 100644
--- a/cuda_bindings/cuda/cudart.pyx
+++ b/cuda_bindings/cuda/cudart.pyx
@@ -5,10 +5,15 @@ from cuda.bindings.runtime import *
 
 cdef extern from *:
     """
+    #ifdef _MSC_VER
     #pragma message ( "The cuda.cudart module is deprecated and will be removed in a future release, " \
                       "please switch to use the cuda.bindings.runtime module instead." )
+    #else
+    #warning The cuda.cudart module is deprecated and will be removed in a future release, \
+             please switch to use the cuda.bindings.runtime module instead.
+    #endif
     """
 
 
 _warnings.warn("The cuda.cudart module is deprecated and will be removed in a future release, "
-               "please switch to use the cuda.bindings.runtime module instead.", DeprecationWarning, stacklevel=2)
+               "please switch to use the cuda.bindings.runtime module instead.", FutureWarning, stacklevel=2)
diff --git a/cuda_bindings/cuda/nvrtc.pyx b/cuda_bindings/cuda/nvrtc.pyx
index 4a9e048f272..09ff61b8c46 100644
--- a/cuda_bindings/cuda/nvrtc.pyx
+++ b/cuda_bindings/cuda/nvrtc.pyx
@@ -5,10 +5,15 @@ from cuda.bindings.nvrtc import *
 
 cdef extern from *:
     """
+    #ifdef _MSC_VER
     #pragma message ( "The cuda.nvrtc module is deprecated and will be removed in a future release, " \
                       "please switch to use the cuda.bindings.nvrtc module instead." )
+    #else
+    #warning The cuda.nvrtc module is deprecated and will be removed in a future release, \
+             please switch to use the cuda.bindings.nvrtc module instead.
+    #endif
     """
 
 
 _warnings.warn("The cuda.nvrtc module is deprecated and will be removed in a future release, "
-               "please switch to use the cuda.bindings.nvrtc module instead.", DeprecationWarning, stacklevel=2)
+               "please switch to use the cuda.bindings.nvrtc module instead.", FutureWarning, stacklevel=2)
diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml
index 8921cc5a21f..e6a9492f5be 100644
--- a/cuda_bindings/pyproject.toml
+++ b/cuda_bindings/pyproject.toml
@@ -25,6 +25,7 @@ classifiers = [
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
     "Environment :: GPU :: NVIDIA CUDA",
 ]
 dynamic = [
diff --git a/cuda_bindings/setup.py b/cuda_bindings/setup.py
index e14d060cf63..d9fa93d5ceb 100644
--- a/cuda_bindings/setup.py
+++ b/cuda_bindings/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2024 NVIDIA Corporation.  All rights reserved.
+# Copyright 2021-2025 NVIDIA Corporation.  All rights reserved.
 #
 # Please refer to the NVIDIA end user license agreement (EULA) associated
 # with this source code for terms and conditions that govern your use of
@@ -10,6 +10,7 @@
 import contextlib
 import glob
 import os
+import pathlib
 import platform
 import shutil
 import sys
@@ -23,6 +24,8 @@
 from setuptools import find_packages, setup
 from setuptools.command.bdist_wheel import bdist_wheel
 from setuptools.command.build_ext import build_ext
+from setuptools.command.build_py import build_py
+from setuptools.command.editable_wheel import _TopLevelFinder, editable_wheel
 from setuptools.extension import Extension
 
 # ----------------------------------------------------------------------
@@ -402,9 +405,79 @@ def build_extension(self, ext):
         super().build_extension(ext)
 
 
+################################################################################
+# Adapted from NVIDIA/numba-cuda
+# TODO: Remove this block once we get rid of cuda.__version__ and the .pth files
+
+REDIRECTOR_PTH = "_cuda_bindings_redirector.pth"
+REDIRECTOR_PY = "_cuda_bindings_redirector.py"
+SITE_PACKAGES = pathlib.Path("site-packages")
+
+
+class build_py_with_redirector(build_py):  # noqa: N801
+    """Include the redirector files in the generated wheel."""
+
+    def copy_redirector_file(self, source, destination="."):
+        destination = pathlib.Path(self.build_lib) / destination
+        self.copy_file(str(source), str(destination), preserve_mode=0)
+
+    def run(self):
+        super().run()
+        self.copy_redirector_file(SITE_PACKAGES / REDIRECTOR_PTH)
+        self.copy_redirector_file(SITE_PACKAGES / REDIRECTOR_PY)
+
+    def get_source_files(self):
+        src = super().get_source_files()
+        src.extend(
+            [
+                str(SITE_PACKAGES / REDIRECTOR_PTH),
+                str(SITE_PACKAGES / REDIRECTOR_PY),
+            ]
+        )
+        return src
+
+    def get_output_mapping(self):
+        mapping = super().get_output_mapping()
+        build_lib = pathlib.Path(self.build_lib)
+        mapping[str(build_lib / REDIRECTOR_PTH)] = REDIRECTOR_PTH
+        mapping[str(build_lib / REDIRECTOR_PY)] = REDIRECTOR_PY
+        return mapping
+
+
+class TopLevelFinderWithRedirector(_TopLevelFinder):
+    """Include the redirector files in the editable wheel."""
+
+    def get_implementation(self):
+        for item in super().get_implementation():  # noqa: UP028
+            yield item
+
+        with open(SITE_PACKAGES / REDIRECTOR_PTH) as f:
+            yield (REDIRECTOR_PTH, f.read())
+
+        with open(SITE_PACKAGES / REDIRECTOR_PY) as f:
+            yield (REDIRECTOR_PY, f.read())
+
+
+class editable_wheel_with_redirector(editable_wheel):
+    def _select_strategy(self, name, tag, build_lib):
+        # The default mode is "lenient" - others are "strict" and "compat".
+        # "compat" is deprecated. "strict" creates a tree of links to files in
+        # the repo. It could be implemented, but we only handle the default
+        # case for now.
+        if self.mode is not None and self.mode != "lenient":
+            raise RuntimeError(f"Only lenient mode is supported for editable install. Current mode is {self.mode}")
+
+        return TopLevelFinderWithRedirector(self.distribution, name)
+
+
+################################################################################
+
+
 cmdclass = {
     "bdist_wheel": WheelsBuildExtensions,
     "build_ext": ParallelBuildExtensions,
+    "build_py": build_py_with_redirector,
+    "editable_wheel": editable_wheel_with_redirector,
 }
 
 # ----------------------------------------------------------------------
diff --git a/cuda_bindings/site-packages/_cuda_bindings_redirector.pth b/cuda_bindings/site-packages/_cuda_bindings_redirector.pth
new file mode 100644
index 00000000000..c8a1ca9e6b3
--- /dev/null
+++ b/cuda_bindings/site-packages/_cuda_bindings_redirector.pth
@@ -0,0 +1 @@
+import _cuda_bindings_redirector
diff --git a/cuda_bindings/site-packages/_cuda_bindings_redirector.py b/cuda_bindings/site-packages/_cuda_bindings_redirector.py
new file mode 100644
index 00000000000..c5e7a144aec
--- /dev/null
+++ b/cuda_bindings/site-packages/_cuda_bindings_redirector.py
@@ -0,0 +1,28 @@
+# Copyright 2025 NVIDIA Corporation. All rights reserved.
+
+import sys
+from types import ModuleType
+
+
+class LazyCudaModule(ModuleType):
+
+    def __getattr__(self, name):
+        if name == '__version__':
+            import warnings
+            warnings.warn(
+                "accessing cuda.__version__ is deprecated, " "please switch to use cuda.bindings.__version__ instead",
+                FutureWarning,
+                stacklevel=2,
+            )
+            from cuda.bindings import __version__
+
+            return __version__
+
+        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+
+
+# Important: We need to populate the cuda namespace module first, otherwise
+# we'd lose access to any of its submodules. This is a cheap op because there
+# is nothing under cuda.bindings.
+import cuda.bindings
+sys.modules['cuda'].__class__ = LazyCudaModule


Reviews:
user: login: rwgk
body: This certainly looks great to me!

I'm wondering though, what exactly was the reason for adding these files? How much trouble is it to leave some hints in the PR description?
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T04:49:24Z
user: login: vzhurba01
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T01:57:11Z
user: login: leofang
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T16:52:19Z
user: login: kkraus14
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T17:53:56Z
user: login: kkraus14
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T17:58:41Z
user: login: leofang
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T02:35:39Z
user: login: leofang
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T04:27:27Z
user: login: vzhurba01
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T19:29:02Z
user: login: kkraus14
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-21T02:07:16Z

Review comments:
diff_hunk: @@ -1,14 +0,0 @@
-def __getattr__(name):
-    if name == "__version__":
-        import warnings
-
-        warnings.warn(
-            "accessing cuda.__version__ is deprecated, " "please switch to use cuda.bindings.__version__ instead",
-            DeprecationWarning,
path: cuda_bindings/cuda/__init__.py
user: login: leofang
body: @vzhurba01 I did a few quick searches on my cell and noticed that some projects still use this attribute. Maybe for consistency we should just change this to `UserWarning` instead of removing it entirely in 12.9.0? We can remove it in say 12.9.1 instead. WDYT?
created_at: 2025-04-08T16:52:18Z
author_association: MEMBER
diff_hunk: @@ -1,14 +0,0 @@
-def __getattr__(name):
-    if name == "__version__":
-        import warnings
-
-        warnings.warn(
-            "accessing cuda.__version__ is deprecated, " "please switch to use cuda.bindings.__version__ instead",
-            DeprecationWarning,
path: cuda_bindings/cuda/__init__.py
user: login: kkraus14
body: Introducing a breaking change like this is unideal in general in a minor / patch release. Can we defer removing it until the next major release?
created_at: 2025-04-08T17:53:56Z
author_association: COLLABORATOR
diff_hunk: @@ -1,14 +0,0 @@
-def __getattr__(name):
-    if name == "__version__":
-        import warnings
-
-        warnings.warn(
-            "accessing cuda.__version__ is deprecated, " "please switch to use cuda.bindings.__version__ instead",
-            DeprecationWarning,
path: cuda_bindings/cuda/__init__.py
user: login: kkraus14
body: If we just don't want the `DeprecationWarning` getting swallowed by default we should use `FutureWarning` instead of a generic `UserWarning`
created_at: 2025-04-08T17:58:41Z
author_association: COLLABORATOR
diff_hunk: @@ -1,14 +0,0 @@
-def __getattr__(name):
-    if name == "__version__":
-        import warnings
-
-        warnings.warn(
-            "accessing cuda.__version__ is deprecated, " "please switch to use cuda.bindings.__version__ instead",
-            DeprecationWarning,
path: cuda_bindings/cuda/__init__.py
user: login: leofang
body: The major problem is we want `cuda` to be a namespace package, but the existence of `cuda/__init__.py` breaks a bunch of tools. We need to get rid of it sooner than later to not block any of our projects. Let me see if a `.pth` based hack can serve the same purpose.
created_at: 2025-04-18T02:35:39Z
author_association: MEMBER
diff_hunk: @@ -1,14 +0,0 @@
-def __getattr__(name):
-    if name == "__version__":
-        import warnings
-
-        warnings.warn(
-            "accessing cuda.__version__ is deprecated, " "please switch to use cuda.bindings.__version__ instead",
-            DeprecationWarning,
path: cuda_bindings/cuda/__init__.py
user: login: leofang
body: > Let me see if a .pth based hack can serve the same purpose.

Commit [2537375](https://github.com/NVIDIA/cuda-python/pull/493/commits/25373752904f4fd8c0d64c8713dad773b970f356) seems to do the trick!

> we should use FutureWarning instead of a generic UserWarning

Done in commit [cf28b44](https://github.com/NVIDIA/cuda-python/pull/493/commits/cf28b44d4a488012d8b2cd8dcba840e160c3f8fd)
created_at: 2025-04-18T04:27:27Z
author_association: MEMBER

PR comments:
user: login: copy-pr-bot[bot]
created_at: 2025-03-06T03:37:24Z
body: This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities [here](https://docs.gha-runners.nvidia.com/cpr/vetters).

Contributors can view more details about this message [here](https://docs.gha-runners.nvidia.com/cpr/contributors).

author_association: CONTRIBUTOR
user: login: leofang
created_at: 2025-03-06T03:38:28Z
body: /ok to test
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-03-06T03:56:42Z
body: Doc Preview CI
:---:
| <p></p> :rocket: View preview at <br> https://nvidia.github.io/cuda-python/pr-preview/pr-493/ <br>
| <br> https://nvidia.github.io/cuda-python/pr-preview/pr-493/cuda-core/ <br>
| <br> https://nvidia.github.io/cuda-python/pr-preview/pr-493/cuda-bindings/ <br><br>
| <h6><br> Preview will be ready when the GitHub Pages deployment is complete. <br><br></h6>
<!-- Sticky Pull Request Commentpr-preview -->
author_association: NONE
user: login: leofang
created_at: 2025-04-04T23:37:12Z
body: /ok to test
author_association: MEMBER
user: login: leofang
created_at: 2025-04-04T23:43:44Z
body: > what exactly was the reason for adding these files? How much trouble is it to leave some hints in the PR description?

As part of the epic #493, we need to remove `cuda/__init__.py` to ensure `cuda` is a [namespace package](https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#native-namespace-packages). The existence of this file has been a source of multiple traps and bugs.

We still kept it around because it used to provide `cuda.__version__`. Since we have raised a deprecation warning for a few releases, and it is a minor thing, we now remove it entirely.
author_association: MEMBER
user: login: leofang
created_at: 2025-04-07T22:18:56Z
body: This is ready for review. I was hoping to address https://github.com/NVIDIA/cuda-python/pull/517#discussion_r2009201045, but it seems the original issue (leading to our weird `MANIFEST.in`) still persists.
author_association: MEMBER
user: login: leofang
created_at: 2025-04-18T04:23:54Z
body: /ok to test
author_association: MEMBER
user: login: copy-pr-bot[bot]
created_at: 2025-04-18T04:23:58Z
body: > /ok to test

@leofang, there was an error processing your request: `E1`

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/1/
author_association: CONTRIBUTOR
user: login: leofang
created_at: 2025-04-18T04:24:59Z
body: /ok to test 88144ee
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-04-21T16:00:50Z
body: Doc Preview CI
:---:
Preview removed because the pull request was closed or merged.
<!-- Sticky Pull Request Commentpr-preview -->
author_association: NONE