.. raw:: html .. raw:: html nanoarrow for Python ==================== The nanoarrow Python package provides bindings to the nanoarrow C library. Like the nanoarrow C library, it provides tools to facilitate the use of the `Arrow C Data `__ and `Arrow C Stream `__ interfaces. Installation ------------ The nanoarrow Python bindings are available from `PyPI `__ and `conda-forge `__: .. code:: shell pip install nanoarrow conda install nanoarrow -c conda-forge Development versions (based on the ``main`` branch) are also available: .. code:: shell pip install --extra-index-url https://pypi.fury.io/arrow-nightlies/ \ --prefer-binary --pre nanoarrow If you can import the namespace, you’re good to go! .. code:: python import nanoarrow as na Data types, arrays, and array streams ------------------------------------- The Arrow C Data and Arrow C Stream interfaces are comprised of three structures: the ``ArrowSchema`` which represents a data type of an array, the ``ArrowArray`` which represents the values of an array, and an ``ArrowArrayStream``, which represents zero or more ``ArrowArray``\ s with a common ``ArrowSchema``. These concepts map to the ``nanoarrow.Schema``, ``nanoarrow.Array``, and ``nanoarrow.ArrayStream`` in the Python package. .. code:: python na.int32() :: int32 .. code:: python na.Array([1, 2, 3], na.int32()) :: nanoarrow.Array[3] 1 2 3 The ``nanoarrow.Array`` can accommodate arrays with any number of chunks, reflecting the reality that many array containers (e.g., ``pyarrow.ChunkedArray``, ``polars.Series``) support this. .. code:: python chunked = na.Array.from_chunks([[1, 2, 3], [4, 5, 6]], na.int32()) chunked :: nanoarrow.Array[6] 1 2 3 4 5 6 Whereas chunks of an ``Array`` are always fully materialized when the object is constructed, the chunks of an ``ArrayStream`` have not necessarily been resolved yet. .. code:: python stream = na.ArrayStream(chunked) stream :: nanoarrow.ArrayStream .. code:: python with stream: for chunk in stream: print(chunk) :: nanoarrow.Array[3] 1 2 3 nanoarrow.Array[3] 4 5 6 The ``nanoarrow.ArrayStream`` also provides an interface to nanoarrow’s `Arrow IPC `__ reader: .. code:: python url = "https://github.com/apache/arrow-experiments/raw/main/data/arrow-commits/arrow-commits.arrows" na.ArrayStream.from_url(url) :: nanoarrow.ArrayStream These objects implement the `Arrow PyCapsule interface `__ for both producing and consuming and are interchangeable with ``pyarrow`` objects in many cases: .. code:: python import pyarrow as pa pa.field(na.int32()) :: pyarrow.Field<: int32> .. code:: python pa.chunked_array(chunked) :: [ [ 1, 2, 3 ], [ 4, 5, 6 ] ] .. code:: python pa.array(chunked.chunk(1)) :: [ 4, 5, 6 ] .. code:: python na.Array(pa.array([10, 11, 12])) :: nanoarrow.Array[3] 10 11 12 .. code:: python na.Schema(pa.string()) :: string Low-level C library bindings ---------------------------- The nanoarrow Python package also provides lower level wrappers around Arrow C interface structures. You can create these using ``nanoarrow.c_schema()``, ``nanoarrow.c_array()``, and ``nanoarrow.c_array_stream()``. Schemas ~~~~~~~ Use ``nanoarrow.c_schema()`` to convert an object to an ``ArrowSchema`` and wrap it as a Python object. This works for any object implementing the `Arrow PyCapsule Interface `__ (e.g., ``pyarrow.Schema``, ``pyarrow.DataType``, and ``pyarrow.Field``). .. code:: python na.c_schema(pa.decimal128(10, 3)) :: - format: 'd:10,3' - name: '' - flags: 2 - metadata: NULL - dictionary: NULL - children[0]: Using ``c_schema()`` is a good fit for testing and for ephemeral schema objects that are being passed from one library to another. To extract the fields of a schema in a more convenient form, use ``Schema()``: .. code:: python schema = na.Schema(pa.decimal128(10, 3)) schema.precision, schema.scale :: (10, 3) The ``CSchema`` object cleans up after itself: when the object is deleted, the underlying ``ArrowSchema`` is released. Arrays ~~~~~~ You can use ``nanoarrow.c_array()`` to convert an array-like object to an ``ArrowArray``, wrap it as a Python object, and attach a schema that can be used to interpret its contents. This works for any object implementing the `Arrow PyCapsule Interface `__ (e.g., ``pyarrow.Array``, ``pyarrow.RecordBatch``). .. code:: python na.c_array(["one", "two", "three", None], na.string()) :: - length: 4 - offset: 0 - null_count: 1 - buffers: (4754305168, 4754307808, 4754310464) - dictionary: NULL - children[0]: Using ``c_array()`` is a good fit for testing and for ephemeral array objects that are being passed from one library to another. For a higher level interface, use ``Array()``: .. code:: python array = na.Array(["one", "two", "three", None], na.string()) array.to_pylist() :: ['one', 'two', 'three', None] .. code:: python array.buffers :: (nanoarrow.c_lib.CBufferView(bool[1 b] 11100000), nanoarrow.c_lib.CBufferView(int32[20 b] 0 3 6 11 11), nanoarrow.c_lib.CBufferView(string[11 b] b'onetwothree')) Advanced users can create arrays directly from buffers using ``c_array_from_buffers()``: .. code:: python na.c_array_from_buffers( na.string(), 2, [None, na.c_buffer([0, 3, 6], na.int32()), b"abcdef"] ) :: - length: 2 - offset: 0 - null_count: 0 - buffers: (0, 5002908320, 4999694624) - dictionary: NULL - children[0]: Array streams ~~~~~~~~~~~~~ You can use ``nanoarrow.c_array_stream()`` to wrap an object representing a sequence of ``CArray``\ s with a common ``CSchema`` to an ``ArrowArrayStream`` and wrap it as a Python object. This works for any object implementing the `Arrow PyCapsule Interface `__ (e.g., ``pyarrow.RecordBatchReader``, ``pyarrow.ChunkedArray``). .. code:: python pa_batch = pa.record_batch({"col1": [1, 2, 3]}) reader = pa.RecordBatchReader.from_batches(pa_batch.schema, [pa_batch]) array_stream = na.c_array_stream(reader) array_stream :: - get_schema(): struct You can pull the next array from the stream using ``.get_next()`` or use it like an iterator. The ``.get_next()`` method will raise ``StopIteration`` when there are no more arrays in the stream. .. code:: python for array in array_stream: print(array) :: > - length: 3 - offset: 0 - null_count: 0 - buffers: (0,) - dictionary: NULL - children[1]: 'col1': - length: 3 - offset: 0 - null_count: 0 - buffers: (0, 2642948588352) - dictionary: NULL - children[0]: Use ``ArrayStream()`` for a higher level interface: .. code:: python reader = pa.RecordBatchReader.from_batches(pa_batch.schema, [pa_batch]) na.ArrayStream(reader).read_all() :: nanoarrow.Array>[3] {'col1': 1} {'col1': 2} {'col1': 3} Development ----------- Python bindings for nanoarrow are managed with `setuptools `__. This means you can build the project using: .. code:: shell git clone https://github.com/apache/arrow-nanoarrow.git cd arrow-nanoarrow/python # Build dependencies: # pip install meson meson-python cython pip install -e . --no-build-isolation Tests use `pytest `__: .. code:: shell # Install dependencies pip install -e ".[test]" # Run tests pytest -vvx CMake is currently required to ensure that the vendored copy of nanoarrow in the Python package stays in sync with the nanoarrow sources in the working tree.