File size: 13,394 Bytes
0bd2a62 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 | # Pybind11 bindings for the Abseil C++ Common Libraries

[TOC]
## Overview
These adapters make Abseil types work with Pybind11 bindings. For more
information on using Pybind11, see
g3doc/third_party/pybind11/google3_utils/README.md.
To use the converters listed below, just include the header
in the .cc file with your bindings:
```
#include "pybind11_abseil/absl_casters.h"
```
## Installation
pybind11_abseil can be built with Bazel or CMake. Instructions for both are below.
### Bazel
In your BUILD file:
```
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
```
#### Bzlmod
You can depend on the Bazel module and dependencies via one of the following commands in your MODULE.bazel:
To depend on a release:
```
bazel_dep(
name = "pybind11_abseil",
version = "<selected_version>",
)
```
To depend on floating master:
```
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "pybind11_bazel",
strip_prefix = "pybind11_bazel-master",
urls = ["https://github.com/pybind/pybind11_bazel/archive/refs/heads/master.tar.gz"],
)
http_archive(
name = "pybind11",
build_file = "@pybind11_bazel//:pybind11-BUILD.bazel",
strip_prefix = "pybind11-master",
urls = ["https://github.com/pybind/pybind11/archive/refs/heads/master.tar.gz"],
)
http_archive(
name = "pybind11_abseil",
strip_prefix = "pybind11_abseil-master",
urls = ["https://github.com/pybind/pybind11_abseil/archive/refs/heads/master.tar.gz"],
)
```
#### WORKSPACE
Bazel workspace support is deprecated and will be removed at a later date.
You will need to depend on `pybind11`, `pybind11_bazel`(see
[doc](https://github.com/pybind/pybind11_bazel#installation), and on
`pybind11_abseil`, e.g.
```
http_archive(
name = "pybind11_bazel",
strip_prefix = "pybind11_bazel-master",
urls = ["https://github.com/pybind/pybind11_bazel/archive/refs/heads/master.tar.gz"],
)
http_archive(
name = "pybind11",
build_file = "@pybind11_bazel//:pybind11-BUILD.bazel",
strip_prefix = "pybind11-master",
urls = ["https://github.com/pybind/pybind11/archive/refs/heads/master.tar.gz"],
)
http_archive(
name = "pybind11_abseil",
strip_prefix = "pybind11_abseil-master",
urls = ["https://github.com/pybind/pybind11_abseil/archive/refs/heads/master.tar.gz"],
)
```
### CMake
In your project, add a FetchContent for pybind11_abseil. This will also fetch the
appropriate versions of Abseil and pybind11 which your project can use
(eliminating the need for submoduling Abseil or using find_package).
Add the following to your CMakeLists.txt:
```
include(FetchContent)
FetchContent_Declare {
pybind11_abseil
GIT_REPOSITORY https://github.com/pybind/pybind11_abseil.git
GIT_TAG master
}
FetchContent_MakeAvailable(pybind11 abseil-cpp pybind11_abseil)
```
To install the package so that it is accessible from system Python, run cmake
with the flag `-DCMAKE_INSTALL_PYDIR` set to a directory on your PYTHONPATH and
subsequently run `make install`. This also works on projects that include
pybind11_abseil via FetchContent.
## absl::Duration
`absl::Duration` objects are converted to/ from python datetime.timedelta objects.
Therefore, C code cannot mutate any datetime.timedelta objects from python.
## absl::Time
`absl::Time` objects are converted to/from python datetime.datetime objects.
Additionally, datetime.date objects can be converted to `absl::Time` objects.
C code cannot mutate any datetime.datetime objects from python.
Python date objects effectively truncate the time to 0 (i.e., midnight).
Python time objects are not supported because `absl::Time` would implicitly
assume a year, which could be confusing.
### Time zones
Python `datetime` objects include timezone information, while
`absl::Time` does not. When converting from Python to C++, if a timezone is
specified then it will be used to determine the `absl::Time` instant. If no
timezone is specified by the Python `datetime` object, the local timezone is
assumed.
When converting back from C++ to Python, the resultant time will be presented in
the local timezone and the `tzinfo` property set on the `datetime` object to
reflect that. This means that the caller may receive a datetime formatted
in a different timezone to the one they passed in. To handle this safely, the
caller should take care to check the `tzinfo` of any returned `datetime`s.
## absl::CivilTime
`absl::CivilTime` objects are converted to/from Python datetime.datetime
objects. Fractional Python datetime components are truncated when converting to
less granular C++ types, and time zone information is ignored.
## absl::Span
### Loading
Some python types can be loaded (Python->C++) without copying or converting the
list, while some require copying/ converting the list. The non-converting load
methods will be tried first, and, if the span elements are const, the converting
load methods will be tried next.
Arguments cast to a span with *non-const* elements can never be copied/converted.
To prevent an argument cast to a span with *const* elements from being copied or
converted, mark it as `noconvert()` (see go/pybind11-non-converting-arguments).
The following python types can be loaded *without* copying or converting:
- Numpy array (or anything else that supports [buffer protocol](
https://docs.python.org/3/c-api/buffer.htm)) => `Span<{const or non-const} T>`
if *all* of the following conditions are satisfied:
- The buffer is 1-D.
- T is a numeric type.
- The array dtype matches T exactly.
- If T is not const, the buffer allows writing.
- The stride does not indicate to skip elements or go in reverse order.
- [Opaque](https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html#making-opaque-types) `std::vector<T>` => `Span<{const or non-const} T>`.
- T can be any type, including converted or pointer types, but must
match exactly between C++ and python.
- Opaque vectors are *not* currently compatible with the smart holder.
The following python types must be copied/converted to be loaded:
- Python sequence of elements that require conversion (numbers, strings,
datetimes, etc) => `Span<const T>`.
- The elements will be copied/ converted, so that conversion must be legal.
- T *cannot* be a pointer.
- Python sequence of elements that do *not* require conversion (ie, classes
wrapped with py::class_) => `Span<const T>` (elements *will* be copied) or
`Span<{const or non-const} T* const>` (elements will *not* be copied).
Specifically, this conversion will *fail* if any of the following are true:
- `noconvert()` was specified (see go/pybind11-non-converting-arguments).
- The element conversion is not allowed (eg, floating point to integer).
- The sequence is being loaded into a `Span<{non-const} T>` or
`Span<{const or non-const} T* {non-const}>`.
- The elements require conversion *and* the sequence is being loaded into a
`Span<T*>` (regardless of any `const`s; the element caster which owns the
converted value would be destroyed before `load` is complete, resulting in
dangling references).
- The span is nested (ie, `absl::Span<absl::Span<T>>`, regardless of any `const`s).
Note: These failure conditions only apply to *converted* python types.
### Casting
Spans are cast (C++->Python) with the standard list caster, which always
converts the list. This could be changed in the future (eg, using [buffer protocol](
https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html#buffer-protocol))
but generally using spans as return values is not recommended.
## absl::string_view
Supported exactly the same way pybind11 supports `std::string_view`.
## absl::optional
Supported exactly the same way pybind11 supports `std::optional`.
## absl::flat_hash_map and absl::btree_map
Supported exactly the same way pybind11 supports `std::map`.
## absl::flat_hash_set
Supported exactly the same way pybind11 supports `std::set`.
## absl::Status[Or]
To use the Status[Or] casters:
1. Include the header file `pybind11_abseil/status_casters.h`
in the .cc file with your bindings.
1. Call `pybind11::google::ImportStatusModule();` in your `PYBIND11_MODULE`
definition.
(For use outside google3:
The path used for the `status` module may be changed by altering the value of
`PYBIND11_ABSEIL_STATUS_MODULE_PATH` defined in `import_status_module.h`.)
By default, an ok status will be converted into `None`, and a non-ok status will
raise a `status.StatusNotOk` exception. This has a `status` attribute which can
be used to access the status object and check the code/ message.
To get a `status.Status` object rather than having an exception thrown, pass
either the `Status` object or a function returning a `Status` to
`pybind11::google::DoNotThrowStatus` before casting or binding. This works with
references and pointers to `absl::Status` objects too.
It isn't possible to specify separate return value policies for a `StatusOr`
object and its payload. Since `StatusOr` is processed and not ever actually
represented in Python, the return value policy applies to the payload. E.g., if
you return a StatusOr<MyObject*> (note the * is inside the `StatusOr`) with a
take_ownership return val policy and the status is OK (i.e., it has a payload),
Python will take ownership of that payload and free it when it is garbage
collected.
However, if you return a `StatusOr<MyObject>*` (note: the `*` is outside the
`StatusOr` rather than inside it now) with a `take_ownership` return val policy,
Python does not take ownership of the `StatusOr` and will not free it (because
again, that policy applies to `MyObject`, not `StatusOr`).
See `status_utils.cc` in this directory for details about what methods are
available in wrapped `absl::Status` objects.
Example:
```cpp
#include "pybind11_abseil/status_casters.h"
absl::Status StatusReturningFunction() {
return absl::Status(...);
}
pybind11::object StatusHandlingFunction() {
return pybind11::cast(pybind11::google::DoNotThrowStatus(StatusReturningFunction()));
}
PYBIND11_MODULE(test_bindings, m) {
pybind11::google::ImportStatusModule();
m.def("return_status", &StatusReturningFunction,
"Return None if StatusCode is OK, otherwise raise an error.");
m.def("make_status", google::DoNotThrowStatus(&StatusReturningFunction),
"Return a wrapped status object without raising an error.");
m.def("status_handling_function", &StatusHandlingFunction,
"Same effect as make_status, but cast is done internally.");
};
```
Python:
```python
from pybind11_abseil import status
import test_bindings
my_status = make_status()
if my_status.code():
...
try:
return_status()
except status.StatusNotOk as e:
print(e.status)
```
### absl::StatusOr
`absl::StatusOr` objects behave exactly like `absl::Status` objects, except:
- There is no support for passing StatusOr objects. You can only return them.
- Instead of returning None or a wrapped status with OK, this casts and
returns the payload when there is no error.
As with `absl::Status`, the default behavior is to throw an error when casting
a non-ok status. You may pass a StatusOr object or StatusOr returning function
to `pybind11::google::DoNotThrowStatus` in exactly the same way as with
`absl::Status` to change this behavior.
`absl::StatusOr` objects must be returned by value (not reference or pointer).
Why? Because the implementation takes advantage of the fact that python is a
dynamically typed language to cast and return the payload *or* the
`absl::Status` object (or raise an exeception). Python has no concept of a
`absl::StatusOr` object, so it's also impossible to apply the
return_value_policy to a `absl::StatusOr`. Therefore returning a reference or
pointer to a `absl::StatusOr` is meaningless.
Pointers *can* be used as the payload type, and the return_value_policy will
be applied to the payload if the status is OK. However, references cannot be
used as the payload type, because that's a restriction on `absl::StatusOr` in
general, not pybind11 (see https://yaqs/5903163345338368).
This can handle any type of payload that pybind knows about. unique_ptrs (i.e.,
`absl::StatusOr<std::unique_ptr<...>>`) to wrapped classes or structs (i.e., any
type which you created bindings for using `pybind11::class_<...>`) can be used,
but unique_ptrs to converted types (e.g., `int`, `string`, `absl::Time`,
`absl::Duration`, etc.) cannot be used.
### absl::StatusCode
The `status` module provides `pybind11::enum_` bindings for `absl::StatusCode`.
These use python constant style, e.g. `status.StatusCode.OK`,
`status.StatusCode.CANCELLED`, etc.
Warning: Pybind enums are their own type, and will never compare equally to
integers due to being a different type, regardless of their value. In particular,
note that the [status proto](http://google3/util/task/status.proto)
`code` field is an integer, so it will never directly compare as equal to a
`StatusCode`. To fix this, convert an integer to a `StatusCode` or vice-versa.
```python
status_code = 0 # An integer.
if status_code == status.StatusCode.OK: # Wrong: always evaluates to false.
...
if status.StatusCode(status_code) == status.StatusCode.OK: # Correct.
...
if status_code == int(status.StatusCode.OK): # Also correct.
...
```
|