From f231e5a9cd02371487bf6b7a830b94a242bb8502 Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Wed, 22 Oct 2025 10:54:05 +0200 Subject: [PATCH 1/7] Renamed template-python to cunumpy --- .github/workflows/testing.yml | 4 ---- README.md | 18 ++++++++++++------ docs/source/conf.py | 4 ++-- docs/source/quickstart.md | 18 +++++++++++++----- pyproject.toml | 9 ++++----- src/{app => cunumpy}/main.py | 0 6 files changed, 31 insertions(+), 22 deletions(-) rename src/{app => cunumpy}/main.py (100%) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index f84e48c..0c86b54 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -44,10 +44,6 @@ jobs: pip install --upgrade pip pip install ".[dev]" - - name: Check installation - run: | - template-python - - name: Run tests run: | pytest . diff --git a/README.md b/README.md index 8ca65e2..673993d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# template-python +# CuNumpy -Template repository for python projects - -Documentation: https://max-models.github.io/template-python/ +Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cunumpy as xp`. # Install @@ -20,10 +18,18 @@ Install the code and requirements with pip pip install -e . ``` -Run the code with +Example usage: ``` -template-python +export ARRAYS_BACKEND=cupy +``` + +```python +import cunumpy as xp +arr = xp.array([1,2]) + +print(type(arr)) +print(xp.__version__) ``` # Build docs diff --git a/docs/source/conf.py b/docs/source/conf.py index 5a54542..cc4c23f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,7 @@ def setup(app): # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = "python-template" +project = "cunumpy" copyright = "2025, Max" author = "Max" @@ -68,7 +68,7 @@ def setup(app): "icon_links": [ { "name": "GitHub", - "url": "https://github.com/max-models/template-python", + "url": "https://github.com/max-models/cunumpy", "icon": "fab fa-github", "type": "fontawesome", }, diff --git a/docs/source/quickstart.md b/docs/source/quickstart.md index a2d6ec8..bb76a81 100644 --- a/docs/source/quickstart.md +++ b/docs/source/quickstart.md @@ -6,6 +6,8 @@ Clone the repo git clone ... ``` +# Install + Create and activate python environment ``` @@ -20,19 +22,25 @@ Install the code and requirements with pip pip install -e . ``` -Run the code with +Example usage: ``` -template-python +export ARRAYS_BACKEND=cupy +``` + +```python +import cunumpy as xp +arr = xp.array([1,2]) + +print(type(arr)) +print(xp.__version__) ``` # Build docs + ``` make html cd ../ open docs/_build/html/index.html ``` - -```{toctree} -:maxdepth: 1 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 53b75b8..3c336b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta" requires = [ "setuptools", "wheel" ] [project] -name = "template-python" +name = "cunumpy" version = "0.1" -description = "Template python repository." +description = "Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cunumpy as xp`." readme = "README.md" keywords = [ "python" ] license = { file = "LICENSE.txt" } @@ -30,7 +30,7 @@ optional-dependencies.dev = [ "black[jupyter]", "isort", "ruff", - "template-python[test,docs]", + "cunumpy[test,docs]", ] # https://medium.com/@pratikdomadiya123/build-project-documentation-quickly-with-the-sphinx-python-2a9732b66594 optional-dependencies.docs = [ @@ -45,8 +45,7 @@ optional-dependencies.docs = [ "sphinx-book-theme", ] optional-dependencies.test = [ "coverage", "pytest" ] -urls."Source" = "https://github.com/max-models/template-python" -scripts.template-python = "app.main:main" +urls."Source" = "https://github.com/max-models/cunumpy" [tool.setuptools.packages.find] where = [ "src" ] diff --git a/src/app/main.py b/src/cunumpy/main.py similarity index 100% rename from src/app/main.py rename to src/cunumpy/main.py From a3169b27d3db489c236f2f5f08cebc630f55d79c Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Wed, 22 Oct 2025 10:58:17 +0200 Subject: [PATCH 2/7] Added arrays.py --- README.md | 2 +- docs/source/conf.py | 2 +- docs/source/quickstart.md | 2 +- src/cunumpy/arrays.py | 64 +++++++++++++++++++++++++++++++++++++++ tests/unit/test_app.py | 14 ++++++--- 5 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 src/cunumpy/arrays.py diff --git a/README.md b/README.md index 673993d..debfdf1 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ pip install -e . Example usage: ``` -export ARRAYS_BACKEND=cupy +export ARRAY_BACKEND=cupy ``` ```python diff --git a/docs/source/conf.py b/docs/source/conf.py index cc4c23f..1689def 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,6 +6,7 @@ import os import shutil + def copy_tutorials(app): src = os.path.abspath("../tutorials") dst = os.path.abspath("source/tutorials") @@ -74,4 +75,3 @@ def setup(app): }, ], } - diff --git a/docs/source/quickstart.md b/docs/source/quickstart.md index bb76a81..f42c8bd 100644 --- a/docs/source/quickstart.md +++ b/docs/source/quickstart.md @@ -25,7 +25,7 @@ pip install -e . Example usage: ``` -export ARRAYS_BACKEND=cupy +export ARRAY_BACKEND=cupy ``` ```python diff --git a/src/cunumpy/arrays.py b/src/cunumpy/arrays.py new file mode 100644 index 0000000..6438575 --- /dev/null +++ b/src/cunumpy/arrays.py @@ -0,0 +1,64 @@ +import os +from types import ModuleType +from typing import TYPE_CHECKING, Literal + +BackendType = Literal["numpy", "cupy"] + + +class ArrayBackend: + def __init__( + self, + backend: BackendType = "numpy", + verbose: bool = False, + ) -> None: + assert backend.lower() in [ + "numpy", + "cupy", + ], "Array backend must be either 'numpy' or 'cupy'." + + self._backend: BackendType = "cupy" if backend.lower() == "cupy" else "numpy" + + # Import numpy/cupy + if self.backend == "cupy": + try: + import cupy as cp + + self._xp = cp + except ImportError: + if verbose: + print("CuPy not available.") + self._backend = "numpy" + + if self.backend == "numpy": + import numpy as np + + self._xp = np + + assert isinstance(self.xp, ModuleType) + + if verbose: + print(f"Using {self.xp.__name__} backend.") + + @property + def backend(self) -> BackendType: + return self._backend + + @property + def xp(self) -> ModuleType: + return self._xp + + +# TODO: Make this configurable via environment variable or config file. +array_backend = ArrayBackend( + backend=( + "cupy" if os.getenv("ARRAY_BACKEND", "numpy").lower() == "cupy" else "numpy" + ), + verbose=False, +) + +# TYPE_CHECKING is True when type checking (e.g., mypy), but False at runtime. +# This allows us to use autocompletion for xp (i.e., numpy/cupy) as if numpy was imported. +if TYPE_CHECKING: + import numpy as xp +else: + xp = array_backend.xp diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py index 5e9299e..c026e20 100644 --- a/tests/unit/test_app.py +++ b/tests/unit/test_app.py @@ -1,9 +1,13 @@ -def test_import_app(): - from app.main import main +from cunumpy.arrays import xp - print("app imported") - main() + +def test_xp_array(): + + arr = xp.array([1, 2]) + + print(type(arr)) + print(xp.__name__) if __name__ == "__main__": - test_import_app() + test_xp_array() From f509d2d93d492ae3123fe61de42efd2870992913 Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Wed, 22 Oct 2025 11:07:30 +0200 Subject: [PATCH 3/7] Added src/cunumpy/__init__.py so that xp can be imported directly with from cunumpy import xp --- src/cunumpy/__init__.py | 9 +++++++++ src/cunumpy/{arrays.py => xp.py} | 0 tests/unit/test_app.py | 7 +++---- 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 src/cunumpy/__init__.py rename src/cunumpy/{arrays.py => xp.py} (100%) diff --git a/src/cunumpy/__init__.py b/src/cunumpy/__init__.py new file mode 100644 index 0000000..1f3f07c --- /dev/null +++ b/src/cunumpy/__init__.py @@ -0,0 +1,9 @@ +# cunumpy/__init__.py +from . import xp + +__all__ = ["xp"] + +def __getattr__(name: str): + """Set cunumpy. to cunumpy.xp. (NumPy/CuPy).""" + return getattr(xp.xp, name) + diff --git a/src/cunumpy/arrays.py b/src/cunumpy/xp.py similarity index 100% rename from src/cunumpy/arrays.py rename to src/cunumpy/xp.py diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py index c026e20..78858dc 100644 --- a/tests/unit/test_app.py +++ b/tests/unit/test_app.py @@ -1,13 +1,12 @@ -from cunumpy.arrays import xp +import cunumpy as xp def test_xp_array(): arr = xp.array([1, 2]) + arr *= 2 - print(type(arr)) - print(xp.__name__) - + print(f"{arr = } {type(arr) = }") if __name__ == "__main__": test_xp_array() From 195fbd9875f89e5c507b7fa0081786839d1301f4 Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Wed, 22 Oct 2025 11:08:37 +0200 Subject: [PATCH 4/7] Removed ruff dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c336b9..f86dae4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,6 @@ dependencies = [ optional-dependencies.dev = [ "black[jupyter]", "isort", - "ruff", "cunumpy[test,docs]", ] # https://medium.com/@pratikdomadiya123/build-project-documentation-quickly-with-the-sphinx-python-2a9732b66594 From 3dc347647f77f66546cec50d88cb6699e8b3122c Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Wed, 22 Oct 2025 11:10:00 +0200 Subject: [PATCH 5/7] Formatting and updated tutorial --- src/cunumpy/__init__.py | 2 +- tests/unit/test_app.py | 1 + tutorials/example_tutorial.ipynb | 9 +++++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/cunumpy/__init__.py b/src/cunumpy/__init__.py index 1f3f07c..32c5da8 100644 --- a/src/cunumpy/__init__.py +++ b/src/cunumpy/__init__.py @@ -3,7 +3,7 @@ __all__ = ["xp"] + def __getattr__(name: str): """Set cunumpy. to cunumpy.xp. (NumPy/CuPy).""" return getattr(xp.xp, name) - diff --git a/tests/unit/test_app.py b/tests/unit/test_app.py index 78858dc..ccb4e6d 100644 --- a/tests/unit/test_app.py +++ b/tests/unit/test_app.py @@ -8,5 +8,6 @@ def test_xp_array(): print(f"{arr = } {type(arr) = }") + if __name__ == "__main__": test_xp_array() diff --git a/tutorials/example_tutorial.ipynb b/tutorials/example_tutorial.ipynb index 4f568bb..770bc27 100644 --- a/tutorials/example_tutorial.ipynb +++ b/tutorials/example_tutorial.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "68ad1562-4953-444c-96c7-9026bcc54cc7", "metadata": {}, "outputs": [ @@ -23,7 +23,12 @@ } ], "source": [ - "print(\"Example tutorial which will be published in the docs!\")" + "import cunumpy as xp\n", + "\n", + "arr = xp.array([1, 2])\n", + "arr *= 2\n", + "\n", + "print(f\"{arr = } {type(arr) = }\")" ] } ], From b7b03bc754a2d299d774fb160841800166423726 Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Wed, 22 Oct 2025 11:45:06 +0200 Subject: [PATCH 6/7] Updated tutorial --- tutorials/example_tutorial.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorials/example_tutorial.ipynb b/tutorials/example_tutorial.ipynb index 770bc27..fc03a2d 100644 --- a/tutorials/example_tutorial.ipynb +++ b/tutorials/example_tutorial.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "68ad1562-4953-444c-96c7-9026bcc54cc7", "metadata": {}, "outputs": [ @@ -18,7 +18,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Example tutorial which will be published in the docs!\n" + "arr = array([2, 4]) type(arr) = \n" ] } ], @@ -48,7 +48,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.3" + "version": "3.12.3" } }, "nbformat": 4, From 75aa43c14812eeb6e72f7f695b253cda6c07915d Mon Sep 17 00:00:00 2001 From: Max Lindqvist Date: Wed, 22 Oct 2025 11:46:09 +0200 Subject: [PATCH 7/7] uncommented Publish to PyPI --- .github/workflows/publish.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d91ffc6..eaff1cd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,10 +26,9 @@ jobs: - name: Build the package run: python -m build - # Uncomment to publish on pypi - #- name: Publish to PyPI - # env: - # TWINE_USERNAME: __token__ # Use API token - # TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - # run: twine upload dist/* + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ # Use API token + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload dist/*