Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ on:
jobs:
tests:
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} - Python ${{ matrix.python-version }}
strategy:
matrix:
include:
- os: ubuntu-24.04
python-version: "3.10"
torch-pin: "==2.3.*"
numpy-pin: "==2.0"
- os: ubuntu-24.04
python-version: "3.14"
- os: macos-15
Expand All @@ -34,3 +37,6 @@ jobs:

- name: Run tests
run: tox -e tests
env:
CTYPES_DLPACK_TORCH_PIN: ${{ matrix.torch-pin }}
CTYPES_DLPACK_NUMPY_PIN: ${{ matrix.numpy-pin }}
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
# ctypes-dlpack

<h4 align="center">

[![tests status](https://github.com/metatensor/ctypes-dlpack/actions/workflows/tests.yml/badge.svg)](https://github.com/metatensor/ctypes-dlpack/actions?query=branch%3Amain)
[![documentation](https://img.shields.io/badge/📚_documentation-latest-sucess)](https://docs.metatensor.org/ctypes-dlpack/)
</h4>

The [DLPack](https://dmlc.github.io/dlpack/latest/) standard allows to share
arrays/tensors data between different frameworks. It is defined as a C API, and
the standard way to use it from Python is the `__dlpack__` protocol defined in
the [array
api](https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack__.html),
which relies on `PyCapsule` to pass the pointer around through Python code.

However, there might be times where one does not have a `PyCapsule` but just a
raw pointer to the DLPack C struct. In particular, this might be the case when
adding bindings to a C library through `ctypes` instead of going through a
CPython extension module. This library is here to bridge this gap, creating
ctypes pointers from the `__dlpack__` protocol, and making existing pointers
compatible with this protocol.

This library exposes both the C structs/enums/constants from the DLPack C header
as `ctypes` types (containing among other the version of the DLPack library
that was used to generate the ctypes declarations), and a Python API to convert
between DLPack pointers and Python objects. See the [documentation](http://docs.metatensor.org/ctypes-dlpack/) for more details.

You can install the code with `pip install ctypes-dlpack`.

### License and contributions

This project is distributed under the terms of the [BSD license](LICENSE), and
[maintained](https://github.com/lab-cosmo/.github/blob/main/Maintainers.md) by
[@Luthaf](https://github.com/Luthaf), who will reply to issues and pull requests
opened on this repository as soon as possible. You can mention them directly if
you did not receive an answer after a couple of days.
15 changes: 9 additions & 6 deletions scripts/update-declarations.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ def __new__(metacls, name, bases, namespace):
members = namespace["_members_"]

namespace["_reverse_map_"] = {v: k for k, v in members.items()}
cls = type(ctypes.c_int32).__new__(metacls, name, bases, namespace)
for key, value in cls._members_.items():
globals()[key] = value
return cls
return type(ctypes.c_int32).__new__(metacls, name, bases, namespace)

def __repr__(self):
return f"<Enum {self.__name__}>"
Expand All @@ -55,9 +52,13 @@ def __repr__(self):
def __eq__(self, other):
if isinstance(other, int):
return self.value == other
if type(self) is type(other):
return self.value == other.value
return NotImplemented

return type(self) is type(other) and self.value == other.value
""".strip()
def __hash__(self):
return hash(self.value)
"""


class EnumDef:
Expand Down Expand Up @@ -294,6 +295,8 @@ def generate_python_module(
for name in sorted(defines):
file.write(f"{name} = {defines[name]}\n")

file.write("\n")

file.write(ENUM_HELPERS)

for enum in parsed.enums:
Expand Down
64 changes: 49 additions & 15 deletions src/ctypes_dlpack/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,61 @@
from . import _c_api
from ._c_api import (
DLPACK_FLAG_BITMASK_IS_COPIED,
DLPACK_FLAG_BITMASK_IS_SUBBYTE_TYPE_PADDED,
DLPACK_FLAG_BITMASK_READ_ONLY,
DLPACK_MAJOR_VERSION,
DLPACK_MINOR_VERSION,
DLDataType,
DLDataTypeCode,
DLDevice,
DLDeviceType,
DLManagedTensor,
DLManagedTensorVersioned,
DLPackCurrentWorkStream,
DLPackDLTensorFromPyObjectNoSync,
DLPackExchangeAPI,
DLPackExchangeAPIHeader,
DLPackManagedTensorAllocator,
DLPackManagedTensorFromPyObjectNoSync,
DLPackManagedTensorToPyObjectNoSync,
DLPackVersion,
DLTensor,
)
from ._dlpack import DLPackArray, array_as_dlpack


__version__ = "0.1.0"


# expose all public members of the C API at the package level
_public_members = {
name: getattr(_c_api, name) for name in dir(_c_api) if not name.startswith("_")
}

for name, value in _public_members.items():
globals()[name] = value

if getattr(value, "__module__", None) == _c_api.__name__:
value.__module__ = __name__


__all__ = [
*_public_members.keys(),
"DLPACK_FLAG_BITMASK_IS_COPIED",
"DLPACK_FLAG_BITMASK_IS_SUBBYTE_TYPE_PADDED",
"DLPACK_FLAG_BITMASK_READ_ONLY",
"DLPACK_MAJOR_VERSION",
"DLPACK_MINOR_VERSION",
"DLPackManagedTensorAllocator",
"DLPackManagedTensorFromPyObjectNoSync",
"DLPackDLTensorFromPyObjectNoSync",
"DLPackCurrentWorkStream",
"DLPackManagedTensorToPyObjectNoSync",
"DLDeviceType",
"DLDataTypeCode",
"DLPackVersion",
"DLDevice",
"DLDataType",
"DLTensor",
"DLManagedTensor",
"DLManagedTensorVersioned",
"DLPackExchangeAPIHeader",
"DLPackExchangeAPI",
"DLPackArray",
"array_as_dlpack",
]


del _public_members
for _name in __all__:
_value = globals()[_name]
if getattr(_value, "__module__", None) == "ctypes_dlpack._c_api":
_value.__module__ = __name__


del _name, _value
14 changes: 9 additions & 5 deletions src/ctypes_dlpack/_c_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
DLPACK_FLAG_BITMASK_READ_ONLY = 1 << 0
DLPACK_MAJOR_VERSION = 1
DLPACK_MINOR_VERSION = 3


class _EnumType(type(ctypes.c_int32)):
def __new__(metacls, name, bases, namespace):
if "_members_" not in namespace:
Expand All @@ -18,10 +20,7 @@ def __new__(metacls, name, bases, namespace):
members = namespace["_members_"]

namespace["_reverse_map_"] = {v: k for k, v in members.items()}
cls = type(ctypes.c_int32).__new__(metacls, name, bases, namespace)
for key, value in cls._members_.items():
globals()[key] = value
return cls
return type(ctypes.c_int32).__new__(metacls, name, bases, namespace)

def __repr__(self):
return f"<Enum {self.__name__}>"
Expand All @@ -37,8 +36,13 @@ def __repr__(self):
def __eq__(self, other):
if isinstance(other, int):
return self.value == other
if type(self) is type(other):
return self.value == other.value
return NotImplemented

def __hash__(self):
return hash(self.value)

return type(self) is type(other) and self.value == other.value

class DLDeviceType(_Enum):
pass
Expand Down
Loading