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/* 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..debfdf1 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 ARRAY_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..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") @@ -26,7 +27,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,10 +69,9 @@ 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..f42c8bd 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 ARRAY_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..f86dae4 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" } @@ -29,8 +29,7 @@ dependencies = [ 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 +44,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/cunumpy/__init__.py b/src/cunumpy/__init__.py new file mode 100644 index 0000000..32c5da8 --- /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/app/main.py b/src/cunumpy/main.py similarity index 100% rename from src/app/main.py rename to src/cunumpy/main.py diff --git a/src/cunumpy/xp.py b/src/cunumpy/xp.py new file mode 100644 index 0000000..6438575 --- /dev/null +++ b/src/cunumpy/xp.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..ccb4e6d 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 +import cunumpy as xp - print("app imported") - main() + +def test_xp_array(): + + arr = xp.array([1, 2]) + arr *= 2 + + print(f"{arr = } {type(arr) = }") if __name__ == "__main__": - test_import_app() + test_xp_array() diff --git a/tutorials/example_tutorial.ipynb b/tutorials/example_tutorial.ipynb index 4f568bb..fc03a2d 100644 --- a/tutorials/example_tutorial.ipynb +++ b/tutorials/example_tutorial.ipynb @@ -18,12 +18,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "Example tutorial which will be published in the docs!\n" + "arr = array([2, 4]) type(arr) = \n" ] } ], "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) = }\")" ] } ], @@ -43,7 +48,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.3" + "version": "3.12.3" } }, "nbformat": 4,