feat(python): Implement extension type/mechanism in python package (#688)

This PR implements the first canonical extension type for
nanoarrow/Python: `nanoarrow.bool8()`. In doing so it also implements
some machinery for "extensions", which is intended to be internal and to
evolve with the requirements of some follow-up canonical extension
types. The extension points are:

- Parsing the metadata when `Schema.extension` is accessed to get
parameter access (and validate the metadata)
- Converting an extension array to Python objects (i.e.,
`array.to_pylist()`)
- Converting an extension array to a Python sequence (i.e.,
`array.to_pysequence()`)
- Constructing an extension array from Python objects (i.e.,
`na.Array([True, False, None], na.bool8())`
- Constructing an extension array from a Python buffer (i.e.,
`na.Array(np.array([True, False, False]), na.bool8())`

I am not sure the extension point implementations via the `Extension`
methods are the final way this should be done, but this PR at least
connects the wires. The tensor extensions are more complex and will
require some modification of this, but I'd like to do that in another
PR.

```python
import nanoarrow as na
import numpy as np

na.Array([True, False, None], na.bool8())
#> nanoarrow.Array<arrow.bool8{int8}>[3]
#> True
#> False
#> None

na.Array(np.array([True, False, True]), na.bool8())
#> nanoarrow.Array<arrow.bool8{int8}>[3]
#> True
#> False
#> True

na.Array([True, False, None], na.bool8()).to_pylist()
#> [True, False, None]

np.array(na.Array([True, False, True], na.bool8()).to_pysequence())
#> array([ True, False,  True])
```
13 files changed
tree: 7914feaa4db67270d0256ef121deb331a4e921db
  1. .github/
  2. ci/
  3. cmake/
  4. dev/
  5. docs/
  6. examples/
  7. python/
  8. r/
  9. src/
  10. subprojects/
  11. thirdparty/
  12. .asf.yaml
  13. .clang-format
  14. .clang-tidy
  15. .cmake-format
  16. .env
  17. .flake8
  18. .gitattributes
  19. .gitignore
  20. .isort.cfg
  21. .pre-commit-config.yaml
  22. CHANGELOG.md
  23. CMakeLists.txt
  24. CMakePresets.json
  25. CMakeUserPresets.json.example
  26. docker-compose.yml
  27. LICENSE.txt
  28. meson.build
  29. meson.options
  30. NOTICE.txt
  31. README.md
  32. valgrind.supp
README.md

nanoarrow

Codecov test coverage Documentation nanoarrow on GitHub

The nanoarrow libraries are a set of helpers to produce and consume Arrow data, including the Arrow C Data, Arrow C Stream, and Arrow C Device, structures and the serialized Arrow IPC format. The vision of nanoarrow is that it should be trivial for libraries to produce and consume Arrow data: it helps fulfill this vision by providing high-quality, easy-to-adopt helpers to produce, consume, and test Arrow data types and arrays.

The nanoarrow libraries were built to be:

  • Small: nanoarrow’s C runtime compiles into a few hundred kilobytes and its R and Python bindings both have an installed size of ~1 MB.
  • Easy to depend on: nanoarrow's C library is distributed as two files (nanoarrow.c and nanoarrow.h) and its R and Python bindings have zero dependencies.
  • Useful: The Arrow Columnar Format includes a wide range of data type and data encoding options. To the greatest extent practicable, nanoarrow strives to support the entire Arrow columnar specification (see the Arrow implementation status page for implementation status).

Getting started

The nanoarrow Python bindings are available from PyPI and conda-forge:

pip install nanoarrow
conda install nanoarrow -c conda-forge

The nanoarrow R package is available from CRAN:

install.packages("nanoarrow")

The C library can be used by generating bundled versions of the core library and its components. This is the version used internally by the R and Python bindings.

python ci/scripts/bundle.py \
  --source-output-dir=dist \
  --include-output-dir=dist \
  --header-namespace= \
  --with-device \
  --with-ipc \
  --with-testing \
  --with-flatcc

CMake is also supported via a build/install with find_package() or using FetchContent:

fetchcontent_declare(nanoarrow
                     URL "https://www.apache.org/dyn/closer.lua?action=download&filename=arrow/nanoarrow-0.5.0/apache-arrow-0.5.0.tar.gz")

fetchcontent_makeavailable(nanoarrow)

The C library can also be used as a Meson subproject installed with:

mkdir subprojects
meson wrap install nanoarrow

...and declared as a dependency with:

nanoarrow_dep = dependency('nanoarrow')
example_exec = executable('example_meson_minimal_app',
                          'src/app.cc',
                          dependencies: [nanoarrow_dep])

See the nanoarrow Documentation for extended tutorials and API reference for the C, C++, Python, and R libraries.

The nanoarrow GitHub repository additionally provides a number of examples covering how to use nanoarrow in a variety of build configurations.

Development

Building with CMake

CMake is the primary build system used to develop and test the nanoarrow C library. You can build nanoarrow with:

mkdir build && cd build
cmake ..
cmake --build .

To build nanoarrow along with tests run:

mkdir build && cd build
cmake .. -DNANOARROW_BUILD_TESTS=ON
cmake --build .

If you are able to install Arrow C++ you can enable more testing:

mkdir build && cd build
cmake .. -DNANOARROW_BUILD_TESTS=ON -DNANOARROW_BUILD_TESTS_WITH_ARROW=ON
cmake --build .

Tests can be run with ctest.

Building with Meson

CMake is the officially supported build system for nanoarrow. However, the Meson backend is an experimental feature you may also wish to try.

meson setup builddir
cd builddir

After setting up your project, be sure to enable the options you want:

meson configure -Dtests=true -Dbenchmarks=true

You can enable better test coverage if Apache Arrow is installed on your system with -Dtest_with_arrow=true. Depending on how you have installed Apache Arrow, you may also need to pass --pkg-config-path <path to directory with arrow.pc>.

With the above out of the way, the compile command should take care of the rest:

meson compile

Upon a successful build you can execute the test suite and benchmarks with the following commands:

meson test nanoarrow:  # default test run
meson test nanoarrow: --wrap valgrind  # run tests under valgrind
meson test nanoarrow: --benchmark --verbose # run benchmarks