Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 4.3k
GH-38325: [Python] Implement PyCapsule interface for Device data in PyArrow#40717
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jorisvandenbossche
merged 7 commits into
apache:main
from
jorisvandenbossche:38325-capsule-device-implJun 26, 2024
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
9c6ff7d
GH-38325: [Python] Implement PyCapsule interface for Device data in P…
jorisvandenbossche f123925
Merge remote-tracking branch 'upstream/main' into 38325-capsule-devic…
jorisvandenbossche e81c5eb
switch order of consuming __arrow_c_array__ and __arrow_c_device_array__
jorisvandenbossche c922f44
Merge remote-tracking branch 'upstream/main' into 38325-capsule-devic…
jorisvandenbossche 6946d19
add kwarg handling
jorisvandenbossche b2ad739
Merge remote-tracking branch 'upstream/main' into 38325-capsule-devic…
jorisvandenbossche 671efda
document kwargs + raise error when trying to cast non-CPU data
jorisvandenbossche File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3619,7 +3619,7 @@ cdef class RecordBatch(_Tabular): | ||
| requested_schema : PyCapsule | None | ||
| A PyCapsule containing a C ArrowSchema representation of a requested | ||
| schema. PyArrow will attempt to cast the batch to this schema. | ||
| If None, the schema will be returned as-is, with a schema matching the | ||
| If None, the batch will be returned as-is, with a schema matching the | ||
| one returned by :meth:`__arrow_c_schema__()`. | ||
| Returns | ||
| @@ -3637,9 +3637,7 @@ cdef class RecordBatch(_Tabular): | ||
| if target_schema != self.schema: | ||
| try: | ||
| # We don't expose .cast() on RecordBatch, only on Table. | ||
| casted_batch = Table.from_batches([self]).cast( | ||
| target_schema, safe=True).to_batches()[0] | ||
| casted_batch = self.cast(target_schema, safe=True) | ||
| inner_batch = pyarrow_unwrap_batch(casted_batch) | ||
| except ArrowInvalid as e: | ||
| raise ValueError( | ||
| @@ -3680,8 +3678,8 @@ cdef class RecordBatch(_Tabular): | ||
| @staticmethod | ||
| def _import_from_c_capsule(schema_capsule, array_capsule): | ||
| """ | ||
| Import RecordBatch from a pair of PyCapsules containing a C ArrowArray | ||
| and ArrowSchema, respectively. | ||
| Import RecordBatch from a pair of PyCapsules containing a C ArrowSchema | ||
| and ArrowArray, respectively. | ||
| Parameters | ||
| ---------- | ||
| @@ -3772,6 +3770,121 @@ cdef class RecordBatch(_Tabular): | ||
| c_device_array, c_schema)) | ||
| return pyarrow_wrap_batch(c_batch) | ||
| def __arrow_c_device_array__(self, requested_schema=None, **kwargs): | ||
| """ | ||
| Get a pair of PyCapsules containing a C ArrowDeviceArray representation | ||
| of the object. | ||
| Parameters | ||
| ---------- | ||
| requested_schema : PyCapsule | None | ||
| A PyCapsule containing a C ArrowSchema representation of a requested | ||
| schema. PyArrow will attempt to cast the batch to this data type. | ||
| If None, the batch will be returned as-is, with a type matching the | ||
| one returned by :meth:`__arrow_c_schema__()`. | ||
| kwargs | ||
| Currently no additional keyword arguments are supported, but | ||
| this method will accept any keyword with a value of ``None`` | ||
| for compatibility with future keywords. | ||
jorisvandenbossche marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| Returns | ||
| ------- | ||
| Tuple[PyCapsule, PyCapsule] | ||
| A pair of PyCapsules containing a C ArrowSchema and ArrowDeviceArray, | ||
| respectively. | ||
| """ | ||
| cdef: | ||
| ArrowDeviceArray* c_array | ||
| ArrowSchema* c_schema | ||
| shared_ptr[CRecordBatch] inner_batch | ||
| non_default_kwargs = [ | ||
| name for name, value in kwargs.items() if value is not None | ||
| ] | ||
| if non_default_kwargs: | ||
| raise NotImplementedError( | ||
| f"Received unsupported keyword argument(s): {non_default_kwargs}" | ||
| ) | ||
| if requested_schema is not None: | ||
| target_schema = Schema._import_from_c_capsule(requested_schema) | ||
| if target_schema != self.schema: | ||
| if not self.is_cpu: | ||
| raise NotImplementedError( | ||
| "Casting to a requested schema is only supported for CPU data" | ||
| ) | ||
| try: | ||
| casted_batch = self.cast(target_schema, safe=True) | ||
| inner_batch = pyarrow_unwrap_batch(casted_batch) | ||
| except ArrowInvalid as e: | ||
| raise ValueError( | ||
| f"Could not cast {self.schema} to requested schema {target_schema}: {e}" | ||
| ) | ||
| else: | ||
| inner_batch = self.sp_batch | ||
| else: | ||
| inner_batch = self.sp_batch | ||
| schema_capsule = alloc_c_schema(&c_schema) | ||
| array_capsule = alloc_c_device_array(&c_array) | ||
| with nogil: | ||
| check_status(ExportDeviceRecordBatch( | ||
| deref(inner_batch), <shared_ptr[CSyncEvent]>NULL, c_array, c_schema)) | ||
| return schema_capsule, array_capsule | ||
| @staticmethod | ||
| def _import_from_c_device_capsule(schema_capsule, array_capsule): | ||
| """ | ||
| Import RecordBatch from a pair of PyCapsules containing a | ||
| C ArrowSchema and ArrowDeviceArray, respectively. | ||
| Parameters | ||
| ---------- | ||
| schema_capsule : PyCapsule | ||
| A PyCapsule containing a C ArrowSchema representation of the schema. | ||
| array_capsule : PyCapsule | ||
| A PyCapsule containing a C ArrowDeviceArray representation of the array. | ||
| Returns | ||
| ------- | ||
| pyarrow.RecordBatch | ||
| """ | ||
| cdef: | ||
| ArrowSchema* c_schema | ||
| ArrowDeviceArray* c_array | ||
| shared_ptr[CRecordBatch] batch | ||
| c_schema = <ArrowSchema*> PyCapsule_GetPointer(schema_capsule, 'arrow_schema') | ||
| c_array = <ArrowDeviceArray*> PyCapsule_GetPointer( | ||
| array_capsule, 'arrow_device_array' | ||
| ) | ||
| with nogil: | ||
| batch = GetResultValue(ImportDeviceRecordBatch(c_array, c_schema)) | ||
| return pyarrow_wrap_batch(batch) | ||
| @property | ||
| def device_type(self): | ||
| """ | ||
| The device type where the arrays in the RecordBatch reside. | ||
| Returns | ||
| ------- | ||
| DeviceAllocationType | ||
| """ | ||
| return _wrap_device_allocation_type(self.sp_batch.get().device_type()) | ||
| @property | ||
| def is_cpu(self): | ||
| """ | ||
| Whether the RecordBatch's arrays are CPU-accessible. | ||
| """ | ||
| return self.device_type == DeviceAllocationType.CPU | ||
| def _reconstruct_record_batch(columns, schema): | ||
| """ | ||
| @@ -5636,7 +5749,8 @@ def record_batch(data, names=None, schema=None, metadata=None): | ||
| data : dict, list, pandas.DataFrame, Arrow-compatible table | ||
| A mapping of strings to Arrays or Python lists, a list of Arrays, | ||
| a pandas DataFame, or any tabular object implementing the | ||
| Arrow PyCapsule Protocol (has an ``__arrow_c_array__`` method). | ||
| Arrow PyCapsule Protocol (has an ``__arrow_c_array__`` or | ||
| ``__arrow_c_device_array__`` method). | ||
| names : list, default None | ||
| Column names if list of arrays passed as data. Mutually exclusive with | ||
| 'schema' argument. | ||
| @@ -5770,6 +5884,18 @@ def record_batch(data, names=None, schema=None, metadata=None): | ||
| raise ValueError( | ||
| "The 'names' argument is not valid when passing a dictionary") | ||
| return RecordBatch.from_pydict(data, schema=schema, metadata=metadata) | ||
| elif hasattr(data, "__arrow_c_device_array__"): | ||
| if schema is not None: | ||
| requested_schema = schema.__arrow_c_schema__() | ||
| else: | ||
| requested_schema = None | ||
| schema_capsule, array_capsule = data.__arrow_c_device_array__(requested_schema) | ||
| batch = RecordBatch._import_from_c_device_capsule(schema_capsule, array_capsule) | ||
| if schema is not None and batch.schema != schema: | ||
| # __arrow_c_device_array__ coerces schema with best effort, so we might | ||
| # need to cast it if the producer wasn't able to cast to exact schema. | ||
| batch = batch.cast(schema) | ||
| return batch | ||
| elif hasattr(data, "__arrow_c_array__"): | ||
| if schema is not None: | ||
| requested_schema = schema.__arrow_c_schema__() | ||
| @@ -5799,8 +5925,8 @@ def table(data, names=None, schema=None, metadata=None, nthreads=None): | ||
| data : dict, list, pandas.DataFrame, Arrow-compatible table | ||
| A mapping of strings to Arrays or Python lists, a list of arrays or | ||
| chunked arrays, a pandas DataFame, or any tabular object implementing | ||
| the Arrow PyCapsule Protocol (has an ``__arrow_c_array__`` or | ||
| ``__arrow_c_stream__`` method). | ||
| the Arrow PyCapsule Protocol (has an ``__arrow_c_array__``, | ||
| ``__arrow_c_device_array__`` or ``__arrow_c_stream__`` method). | ||
| names : list, default None | ||
| Column names if list of arrays passed as data. Mutually exclusive with | ||
| 'schema' argument. | ||
| @@ -5940,7 +6066,7 @@ def table(data, names=None, schema=None, metadata=None, nthreads=None): | ||
| # need to cast it if the producer wasn't able to cast to exact schema. | ||
| table = table.cast(schema) | ||
| return table | ||
| elif hasattr(data, "__arrow_c_array__"): | ||
| elif hasattr(data, "__arrow_c_array__") or hasattr(data, "__arrow_c_device_array__"): | ||
| if names is not None or metadata is not None: | ||
| raise ValueError( | ||
| "The 'names' and 'metadata' arguments are not valid when " | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.