Commit 32cf30f1 by pybind11_abseil authors Committed by Jean-Baptiste

Internal change

PiperOrigin-RevId: 348478233
parent d4b36a06
# Pybind11 bindings for the Abseil C++ Common Libraries # Pybind11 bindings for the Abseil C++ Common Libraries
## WORK IN PROGRESS [TOC]
We are currently working on automatically syncing this repo with ## Overview
Google-internal developments. You can inspect the current state on the
experimental branch:
https://github.com/pybind/pybind11_abseil/tree/experimental These adapters make Abseil types work with Pybind11 bindings. For more
information on using Pybind11, see
g3doc/third_party/pybind11/google3_utils/README.md.
**IMPORTANT**: The `experimental` branch may be purged and re-populated at To use the converters listed below, just include the header
any moment. Do NOT use the `experimental` branch for developments, and in the .cc file with your bindings:
do NOT `git pull`. To update, always use:
```
#include "pybind11_abseil/absl_casters.h"
```
Support for non-const `absl::Span` for numeric types is also available by
including a separated header file:
```
#include "pybind11_abseil/absl_numpy_span_caster.h"
```
## 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 (ie, 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
For non-const `absl::Span` and conversion from `numpy` arrays, see
[non-const absl::Span](#non-const-abslspan) later.
When `absl::Span<const T>` (i.e. the `const` version) is considered, there is
full support to mapping into Python sequences.
Currently, this will always result in the list being copied, so you lose the
efficiency gains of spans in native C++, but you still get the API versatility.
The value type in the span can be any type that pybind knows about. However, it
must be immutable (ie, `absl::Span<const ValueType>`). Theoretically mutable
ValueTypes could be supported, but with some subtle limitations, and this is
not needed right now, so the implementation has been deferred.
The `convert` and `return_value_policy` parameters will apply to the *elements*.
The list containing those elements will aways be converted/copied.
### non-const absl::Span
Support for non-cost `absl::Span`, for numeric types only, is provided for
`numpy` arrays. Support is only for output function parameters and not for
returned value. The rationale behind this decision is that, if a `absl::Span`
were to be returned, the C++ object would have needed to outlive the mapped
Python object. Given the complexity of memory management across languages, we
did not add support of returned `absl::Span`.
That is the following is supported:
```
void Foo(absl::Span<double> some_span);
```
while the following is not (it will generate a compile error):
```
absl::Span<double> Bar();
```
Note: It is possible to use the non-const `absl::Span` bindings to wrap a
function with `absl::Span<const T>` argument if you are using `numpy` arrays
and you do not want a copy to be performed. This can be done by defining a
lambda function in the `pybind11` wrapper, as in the following example. See
b/155596364 for more details.
```
void MyConstSpanFunction(absl::Span<const double> a_span);
...
PYBIND11_MODULE(bindings, m) {
m.def(
"wrap_span",
[](absl::Span<double> span) {
MyConstSpanFunction(span);
});
}
```
## 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
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.
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.
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.");
};
``` ```
git fetch
git checkout experimental Python:
git reset --hard experimental
```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
(ie, `absl::StatusOr<std::unique_ptr<...>>`) to wrapped classes or
structs (ie, any type which you created bindings for using
`pybind11::class_<...>`) can be used, but unique_ptrs to converted types (eg,
`int`, `string`, `absl::Time`, `absl::Duration`, etc) cannot be used.
### Aliasing parts of the status module
The need to import the `status` module can be eliminated by aliasing the parts
of the status module that are needed in your own module:
```cpp
PYBIND11_MODULE(test_bindings, m) {
auto status_module = pybind11::google::ImportStatusModule();
m.attr("StatusNotOk") = status_module.attr("StatusNotOk");
...
}
```
Python:
```python
import test_bindings
try:
return_status()
except test_bindings.StatusNotOk as e:
print(e.status)
```
### Importing the status module
The status module uses the same import mechansim as the proto module; see [its
documentation](../pybind11_protobuf/README.md#importing-the-proto-module)
for details. For now there is a `#ifdef` to allow `ImportStatusModule` to work
with python 2 rather than giving an error, but this will be removed eventually.
If modifying the following functions, make the same changes in the
corresponding proto functions:
- ImportStatusModule
- IsStatusModuleImported
- CheckStatusModuleImported
### Use Outside of Google3
The path used for the status module may be changed by altering the value of
`PYBIND11_ABSEIL_STATUS_MODULE_PATH` defined in `status_casters.h`. This uses
the same mechanism as the proto module, so see [its documentation]
(../pybind11_protobuf/README.md?cl=head#use-outside-of-google3)
for details.
workspace(name = "com_google_pybind11_abseil")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# To update a dependency to a new revision,
# a) update URL and strip_prefix to the new git commit hash
# b) get the sha256 hash of the commit by running:
# curl -L https://github.com/<...>.tar.gz | sha256sum
# and update the sha256 with the result.
http_archive(
name = "com_google_absl",
sha256 = "137836d52edb41891cc6d137a228278ae30e76607e3cbd6b8cdb653743c0823e", # SHARED_ABSL_SHA
strip_prefix = "abseil-cpp-6df644c56f31b100bf731e27c3825069745651e3",
urls = [
"https://github.com/abseil/abseil-cpp/archive/6df644c56f31b100bf731e27c3825069745651e3.tar.gz",
],
)
## `pybind11_bazel`
# See https://github.com/pybind/pybind11_bazel
http_archive(
name = "pybind11_bazel",
strip_prefix = "pybind11_bazel-26973c0ff320cb4b39e45bc3e4297b82bc3a6c09",
sha256 = "8f546c03bdd55d0e88cb491ddfbabe5aeb087f87de2fbf441391d70483affe39",
urls = ["https://github.com/pybind/pybind11_bazel/archive/26973c0ff320cb4b39e45bc3e4297b82bc3a6c09.tar.gz"],
)
# We still require the pybind library.
http_archive(
name = "pybind11",
build_file = "@pybind11_bazel//:pybind11.BUILD",
strip_prefix = "pybind11-2.6",
sha256 = "22af7c5c65f1ca5d00cbfee5fa9be6aedcaa1ea0c46af22eaa429526e1b88094",
urls = ["https://github.com/pybind/pybind11/archive/v2.6.tar.gz"],
)
load("@pybind11_bazel//:python_configure.bzl", "python_configure")
python_configure(name = "local_config_python")
# Pybind11 bindings for the Abseil C++ Common Libraries
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension", "pybind_library")
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
exports_files(["LICENSE"])
pybind_library(
name = "absl_casters",
hdrs = ["absl_casters.h"],
deps = [
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:optional",
"@com_google_absl//absl/types:span",
],
)
pybind_library(
name = "absl_numpy_span_caster",
hdrs = ["absl_numpy_span_caster.h"],
deps = ["@com_google_absl//absl/types:span"],
)
cc_library(
name = "status_not_ok_exception",
hdrs = ["status_not_ok_exception.h"],
deps = ["@com_google_absl//absl/status"],
)
pybind_library(
name = "status_utils",
srcs = ["status_utils.cc"],
hdrs = ["status_utils.h"],
deps = [
":absl_casters",
":status_not_ok_exception",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],
)
pybind_library(
name = "status_casters",
hdrs = ["status_casters.h"],
deps = [
":status_utils",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],
)
pybind_extension(
name = "status",
srcs = ["status.cc"],
deps = [":status_utils"],
)
// Copyright (c) 2019 The Pybind Development Team. All rights reserved.
//
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Type conversion utilities for pybind11 and absl data structures.
//
// Usage: Just include this file in the .cc file with your bindings and add the
// appropriate dependency. Any functions which take or return the supported
// types will have those types automatically converted.
//
// Supported types:
// - absl::Duration- converted to/from python datetime.timedelta
// - absl::CivilTime- converted to/from python datetime.datetime and from date.
// - absl::Time- converted to/from python datetime.datetime and from date.
// - absl::Span- const value types only.
// - absl::string_view
// - absl::optional- converts absl::nullopt to/from python None, otherwise
// converts the contained value.
// - absl::flat_hash_map- converts to/from python dict.
// - absl::flat_hash_set- converst to/from python set.
//
// For details, see the README.md.
//
// Author: Ken Oslund
#ifndef PYBIND11_ABSEIL_ABSL_CASTERS_H_
#define PYBIND11_ABSEIL_ABSL_CASTERS_H_
#include <pybind11/cast.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <cmath>
#include <cstdint>
#include <exception>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <typeinfo>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/strings/string_view.h"
#include "absl/time/civil_time.h"
#include "absl/time/time.h"
#include "absl/types/optional.h"
#include "absl/types/span.h"
namespace pybind11 {
namespace detail {
// Helper function to get an int64_t attribute.
inline int64_t GetInt64Attr(handle src, const char* name) {
return src.attr(name).cast<int64_t>();
}
// Given a python date or datetime object, figure out an appropriate
// absl::TimeZone with a fixed offset, or default to the local timezone.
inline absl::TimeZone GetTimeZone(handle src) {
if (!hasattr(src, "tzinfo")) {
// datetime.date objects lack this property, so assume local.
return absl::LocalTimeZone();
}
object tzinfo = src.attr("tzinfo");
if (tzinfo.is_none()) {
// non-tz aware datetime, again assume local time.
return absl::LocalTimeZone();
}
object utc_offset = tzinfo.attr("utcoffset")(src);
if (utc_offset.is_none()) {
return absl::LocalTimeZone();
}
int64_t offset_seconds =
std::lround(utc_offset.attr("total_seconds")().cast<double>());
return absl::FixedTimeZone(offset_seconds);
}
// Convert between absl::Duration and python datetime.timedelta.
template <>
struct type_caster<absl::Duration> {
public:
// This macro establishes the name 'absl::Duration' in function signatures
// and declares a local variable 'value' of type absl::Duration.
PYBIND11_TYPE_CASTER(absl::Duration, _<absl::Duration>());
// Conversion part 1 (Python->C++)
bool load(handle src, bool convert) {
// Ensure that absl::Duration is converted from a Python datetime.timedelta.
if (!convert || !hasattr(src, "days") || !hasattr(src, "seconds") ||
!hasattr(src, "microseconds")) {
return false;
}
value = absl::Hours(24 * GetInt64Attr(src, "days")) +
absl::Seconds(GetInt64Attr(src, "seconds")) +
absl::Microseconds(GetInt64Attr(src, "microseconds"));
return true;
}
// Conversion part 2 (C++ -> Python)
static handle cast(const absl::Duration& src, return_value_policy, handle) {
absl::Duration remainder;
int64_t secs = absl::IDivDuration(src, absl::Seconds(1), &remainder);
int64_t microsecs = absl::ToInt64Microseconds(remainder);
auto py_duration_t = module::import("datetime").attr("timedelta");
auto py_duration =
py_duration_t(arg("seconds") = secs, arg("microseconds") = microsecs);
return py_duration.release();
}
};
// Convert between absl::Time and python datetime.date, datetime.
template <>
struct type_caster<absl::Time> {
public:
// This macro establishes the name 'absl::Time' in function signatures
// and declares a local variable 'value' of type absl::Time.
PYBIND11_TYPE_CASTER(absl::Time, _<absl::Time>());
// Conversion part 1 (Python->C++)
bool load(handle src, bool convert) {
// Ensure that absl::Duration is converted from a Python datetime.date.
if (!convert || !hasattr(src, "year") || !hasattr(src, "month") ||
!hasattr(src, "day")) {
return false;
}
if (hasattr(src, "timestamp")) {
// python datetime.datetime object
double timestamp = src.attr("timestamp")().cast<double>();
int64_t as_micros = static_cast<int64_t>(timestamp * 1e6);
value = absl::FromUnixMicros(as_micros);
}
#if PY_MAJOR_VERSION < 3
else if (hasattr(src, "microsecond")) { // NOLINT(readability/braces)
// python datetime.datetime object
// This doesn't rely on datetime.datetime.timestamp(), which is not
// available in Python 2.
auto utc = module::import("dateutil.tz").attr("UTC");
auto datetime = module_::import("datetime").attr("datetime");
auto gettz = module::import("dateutil.tz").attr("gettz");
auto epoch_dt = datetime.attr("fromtimestamp")(0, utc);
auto tz = (src.attr("tzinfo").is_none()) ? gettz() : src.attr("tzinfo");
auto src_with_tz = src.attr("replace")("tzinfo"_a = tz);
double timestamp =
(src_with_tz - epoch_dt).attr("total_seconds")().cast<double>();
int64_t as_micros = static_cast<int64_t>(timestamp * 1e6);
value = absl::FromUnixMicros(as_micros);
}
#endif
else { // NOLINT(readability/braces)
// python datetime.date object
absl::CivilDay civil_day(GetInt64Attr(src, "year"),
GetInt64Attr(src, "month"),
GetInt64Attr(src, "day"));
value = absl::FromCivil(civil_day, GetTimeZone(src));
}
return true;
}
// Conversion part 2 (C++ -> Python)
static handle cast(const absl::Time& src, return_value_policy, handle) {
// This function truncates fractional microseconds as the python datetime
// objects cannot support a resolution higher than this.
auto py_datetime_t = module::import("datetime").attr("datetime");
auto py_from_timestamp = py_datetime_t.attr("fromtimestamp");
auto py_timezone_t = module::import("dateutil.tz").attr("gettz");
auto py_timezone = py_timezone_t(absl::LocalTimeZone().name());
double as_seconds = static_cast<double>(absl::ToUnixMicros(src)) / 1e6;
auto py_datetime = py_from_timestamp(as_seconds, "tz"_a = py_timezone);
return py_datetime.release();
}
};
template <typename CivilTimeType>
struct absl_civil_time_caster {
public:
PYBIND11_TYPE_CASTER(CivilTimeType, _<CivilTimeType>());
bool load(handle src, bool convert) {
if (!convert || !hasattr(src, "year") || !hasattr(src, "month") ||
!hasattr(src, "day")) {
return false;
}
int64_t hour = 0, minute = 0, second = 0;
if (hasattr(src, "hour") && hasattr(src, "minute") &&
hasattr(src, "second")) {
hour = GetInt64Attr(src, "hour");
minute = GetInt64Attr(src, "minute");
second = GetInt64Attr(src, "second");
}
value = CivilTimeType(GetInt64Attr(src, "year"), GetInt64Attr(src, "month"),
GetInt64Attr(src, "day"), hour, minute, second);
return true;
}
static handle cast(const CivilTimeType& src, return_value_policy, handle) {
auto py_datetime_t = module::import("datetime").attr("datetime");
auto py_datetime = py_datetime_t(src.year(), src.month(), src.day(),
src.hour(), src.minute(), src.second());
return py_datetime.release();
}
};
template <>
struct type_caster<absl::CivilSecond>
: public absl_civil_time_caster<absl::CivilSecond> {};
template <>
struct type_caster<absl::CivilMinute>
: public absl_civil_time_caster<absl::CivilMinute> {};
template <>
struct type_caster<absl::CivilHour>
: public absl_civil_time_caster<absl::CivilHour> {};
template <>
struct type_caster<absl::CivilDay>
: public absl_civil_time_caster<absl::CivilDay> {};
template <>
struct type_caster<absl::CivilMonth>
: public absl_civil_time_caster<absl::CivilMonth> {};
template <>
struct type_caster<absl::CivilYear>
: public absl_civil_time_caster<absl::CivilYear> {};
// Convert between absl::Span and python sequence types.
//
// TODO(kenoslund): It may be possible to avoid copies in some cases:
// Python to C++: Numpy arrays are contiguous, so we could overlay without copy.
// C++ to Python: Python buffer.
// https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html#buffer-protocol
template <typename T>
struct type_caster<absl::Span<const T>> {
// NOTE: In general, it's *unsafe* to have a pointer type for T. It's
// ok for unmutable sequences of pybind11-wrapped objects.
//
// In general if `T` is a pointer or other non-owning reference type, the
// resultant `Span` may immediately contain dangling references to temporary
// objects owned by temporary `type_caster<T>` objects, and cannot be used.
// In particular, passing a list that is mutated before the C++ call
// returns, or a lazy sequence is unsafe. Use `std::vector<pybind11::object>`
// instead and convert the element in C++.
//
// If the Python calling code ensures that the sequence of elements used to
// initialize the Span are kept alive until the pybind11-bound function
// returns, then the Span can safely be used, e.g. using a tuple or a
// not-mutated list of pybind11-wrapped objects is ok.
//
// The static_assert below prevents spans of pointers to converted types from
// being used, since the caster (which owns the converted value) would be
// destroyed before the function that uses it executes, resulting in a
// dangling reference.
static_assert(
!std::is_pointer<T>::value ||
std::is_base_of<type_caster_base<pybind11::detail::intrinsic_t<T>>,
pybind11::detail::make_caster<T>>::value,
"Spans of pointers are not supported for converted types.");
type_caster() : vector_converter_(), value_(get_vector()) {}
// Copy and Move constructors need to ensure the span points to the copied
// or moved vector, not the original one.
type_caster(const type_caster<absl::Span<const T>>& other)
: vector_converter_(other.vector_converter_), value_(get_vector()) {}
type_caster(type_caster<absl::Span<const T>>&& other)
: vector_converter_(std::move(other.vector_converter_)),
value_(get_vector()) {}
static constexpr auto name = _("Span[") + make_caster<T>::name + _("]");
// We do not allow moving because 1) spans are super lightweight, so there's
// no advantage to moving and 2) the span cannot exist without the caster,
// so moving leaves an implicit dependency (while a reference or pointer
// make that dependency explicit).
operator absl::Span<const T>*() { return &value_; }
operator absl::Span<const T>&() { return value_; }
template <typename T_>
using cast_op_type = cast_op_type<T_>;
bool load(handle src, bool convert) {
if (!vector_converter_.load(src, convert)) return false;
// std::vector implicitly converted to absl::Span.
value_ = get_vector();
return true;
}
template <typename CType>
static handle cast(CType&& src, return_value_policy policy, handle parent) {
return VectorConverter::cast(src, policy, parent);
}
private:
std::vector<T>& get_vector() {
return static_cast<std::vector<T>&>(vector_converter_);
}
using VectorConverter = make_caster<std::vector<T>>;
VectorConverter vector_converter_;
absl::Span<const T> value_;
};
// Convert between absl::flat_hash_map and python dict.
template <typename Key, typename Value, typename Hash, typename Equal,
typename Alloc>
struct type_caster<absl::flat_hash_map<Key, Value, Hash, Equal, Alloc>>
: map_caster<absl::flat_hash_map<Key, Value, Hash, Equal, Alloc>, Key,
Value> {};
// Convert between absl::flat_hash_set and python set.
template <typename Key, typename Hash, typename Equal, typename Alloc>
struct type_caster<absl::flat_hash_set<Key, Hash, Equal, Alloc>>
: set_caster<absl::flat_hash_set<Key, Hash, Equal, Alloc>, Key> {};
// Convert between absl::string_view and python.
//
// pybind11 supports std::string_view, and absl::string_view is meant to be a
// drop-in replacement for std::string_view, so we can just use the built in
// implementation. This is only needed until absl::string_view becomes an alias
// for std::string_view.
#ifndef ABSL_USES_STD_STRING_VIEW
template <>
struct type_caster<absl::string_view> : string_caster<absl::string_view, true> {
};
#endif
// Convert between absl::optional and python.
//
// pybind11 supports std::optional, and absl::optional is meant to be a
// drop-in replacement for std::optional, so we can just use the built in
// implementation.
#ifndef ABSL_USES_STD_OPTIONAL
template <typename T>
struct type_caster<absl::optional<T>>
: public optional_caster<absl::optional<T>> {};
template <>
struct type_caster<absl::nullopt_t> : public void_caster<absl::nullopt_t> {};
#endif
} // namespace detail
} // namespace pybind11
#endif // PYBIND11_ABSEIL_ABSL_CASTERS_H_
#ifndef PYBIND11_ABSEIL_ABSL_NUMPY_SPAN_CASTER_H_
#define PYBIND11_ABSEIL_ABSL_NUMPY_SPAN_CASTER_H_
#include <type_traits>
#include "absl/types/span.h"
#include "pybind11/cast.h"
#include "pybind11/numpy.h"
// Support for conversion from NumPy array to template specializations of
// absl::Span. Note that no data is copied, the `Span`ned memory is owned by the
// NumPy array.
//
// Only one direction of conversion is provided: a Span argument can be exposed
// to Python as a NumPy array argument. No conversion is provided for a Span
// return value.
//
// Lifetime management will become complicated if a `Span` returned by a C++
// object is converted into a NumPy array which outlives the C++ object. Such
// memory management concerns are normal for C++ but undesirable to introduce to
// Python.
//
// A `Span` argument can be used to expose an output argument to Python so that
// Python is wholly responsible for memory management of the output object.
// Using an output argument rather than a return value means that memory can be
// reused.
//
// C++:
// Simulation::Simulation(int buffer_size);
// void Simulation::RenderFrame(int frame_index, Span<uint8> buffer);
//
// Python:
// buffer = np.zeroes(1024*768, dtype='uint8')
// simulation = Simulation(1024*768)
// simulation.renderFrame(0, buffer)
// # RGB data can now be read from the buffer.
namespace pybind11::detail {
// Conversion of non-const Span.
// Note that there are two template specialisations for `absl::Span`: the one in
// this file and the one for `const T` in `absl_caster.h`.
// The use of `std::enable_if_t`, and in particular of the `std::is_const_v`
// condition check, is really important and prevents unpleasant ODR (one
// definition rule) violations when linking together different object files with
// different includes.
// TODO(b/155596364) merge this implementation with the absl::Span<const T>
// caster.
template <typename T>
class type_caster<absl::Span<T>,
// C++11 compatibility.
typename std::enable_if<std::is_arithmetic<T>::value &&
!std::is_const<T>::value>::type> {
public:
// Instead of using the macro PYBIND11_TYPE_CASTER we explicitly define value
// and functions. This is because, the macro has default implementations for
// move constructors and cast method.
// Note: we also disable the linter on these methods and variables.
static constexpr auto name = // NOLINT
_("Span[") + make_caster<T>::name + _("]");
// We do not allow moving because 1) spans are super lightweight, so there is
// no advantage to moving and 2) the span cannot exist without the caster,
// so moving leaves an implicit dependency (while a reference or pointer
// make that dependency explicit).
operator absl::Span<T>*() { // NOLINT(google-explicit-constructor)
return &value_;
}
operator absl::Span<T>&() { // NOLINT(google-explicit-constructor)
return value_;
}
template <typename T_>
using cast_op_type = cast_op_type<T_>;
// Conversion Python->C++: convert a NumPy array into a Span.
bool load(handle src, bool /* convert */) {
// Extract PyObject from handle.
if (!pybind11::isinstance<pybind11::array_t<T>>(src)) {
return false;
}
auto array = src.cast<pybind11::array_t<T>>();
if (!array || array.ndim() != 1 || array.strides()[0] != sizeof(T) ||
!array.writeable()) {
return false;
}
value_ = absl::Span<T>(static_cast<T*>(array.mutable_data()), array.size());
return true;
}
protected:
absl::Span<T> value_;
};
} // namespace pybind11::detail
#endif // PYBIND11_ABSEIL_ABSL_NUMPY_SPAN_CASTER_H_
#include <pybind11/pybind11.h>
#include "pybind11_abseil/status_utils.h"
namespace pybind11 {
namespace google {
PYBIND11_MODULE(status, m) { RegisterStatusBindings(m); }
} // namespace google
} // namespace pybind11
// Type conversion utilities for pybind11 and absl::Status/StatusOr.
//
// Usage:
// 1) Include this file in the .cc file with your bindings.
// 2) Call `pybind11::google::ImportStatusModule()` in your PYBIND11_MODULE
// definition.
//
// Supported types:
// - absl::Status- converts a non-ok return status into a python exception.
// Can be passed as an argument too if you import the status pybind module.
// - absl::StatusOr- converts a non-ok return status into a python exception,
// otherwise converts/returns the payload. Can only be used as a return value.
//
// For details, see the README.md.
//
// Author: Ken Oslund (kenoslund@)
#ifndef PYBIND11_ABSEIL_STATUS_CASTERS_H_
#define PYBIND11_ABSEIL_STATUS_CASTERS_H_
#include <pybind11/cast.h>
#include <pybind11/pybind11.h>
#include <stdexcept>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "pybind11_abseil/status_utils.h"
namespace pybind11 {
namespace google {
// The value of PYBIND11_ABSEIL_STATUS_MODULE_PATH will be different depending
// on whether this is being used inside or outside of google3. The value used
// inside of google3 is defined here. Outside of google3, change this value by
// passing "-DPYBIND11_ABSEIL_STATUS_MODULE_PATH=..." on the commandline.
#ifndef PYBIND11_ABSEIL_STATUS_MODULE_PATH
#define PYBIND11_ABSEIL_STATUS_MODULE_PATH \
pybind11_abseil.status
#endif
// Imports the bindings for the status types. This not thread safe and should
// only be called from a PYBIND11_MODULE definition. If modifying this,
// see g3doc/pybind11_abseil/README.md#importing-the-status-module
inline module ImportStatusModule() {
#if PY_MAJOR_VERSION >= 3
auto m = reinterpret_borrow<module>(PyImport_AddModule(
PYBIND11_TOSTRING(PYBIND11_ABSEIL_STATUS_MODULE_PATH)));
if (!IsStatusModuleImported()) RegisterStatusBindings(m);
// else no-op because bindings are already loaded.
return m;
#else
return module::import(PYBIND11_TOSTRING(PYBIND11_ABSEIL_STATUS_MODULE_PATH));
#endif
}
} // namespace google
namespace detail {
// Convert NoThrowStatus by dispatching to a caster for StatusType with the
// argument throw_exception = false. StatusType should be an absl::Status
// (rvalue, lvalue, reference, or pointer), or an absl::StatusOr value.
// Only return values trigger exceptions, so NoThrowStatus has no meaning for
// input values. Therefore only C++->Python casting is supported.
template <typename StatusType>
struct type_caster<google::NoThrowStatus<StatusType>> {
using InputType = google::NoThrowStatus<StatusType>;
using StatusCaster = make_caster<StatusType>;
static constexpr auto name = StatusCaster::name;
// Convert C++->Python.
static handle cast(const InputType& src, return_value_policy policy,
handle parent) {
// pybind11::cast applies a const qualifier, so this takes a const reference
// argument. The qualifiers we care about are in StatusType, and we will
// forward those, but to do so, we must strip the const off the InputType.
return StatusCaster::cast(
std::forward<StatusType>(const_cast<InputType&>(src).status), policy,
parent, false);
}
};
// Convert absl::Status.
template <>
struct type_caster<absl::Status> : public type_caster_base<absl::Status> {
public:
// Convert C++ -> Python.
static handle cast(const absl::Status* src, return_value_policy policy,
handle parent, bool throw_exception = true) {
if (!src) return none().release();
return cast_impl<const absl::Status&>(*src, policy, parent,
throw_exception);
}
static handle cast(const absl::Status& src, return_value_policy policy,
handle parent, bool throw_exception = true) {
return cast_impl<const absl::Status&>(src, policy, parent, throw_exception);
}
static handle cast(absl::Status&& src, return_value_policy policy,
handle parent, bool throw_exception = true) {
return cast_impl<absl::Status&&>(std::move(src), policy, parent,
throw_exception);
}
private:
template <typename CType>
static handle cast_impl(CType src, return_value_policy policy, handle parent,
bool throw_exception) {
google::CheckStatusModuleImported();
if (!throw_exception) {
// Use the built-in/standard pybind11 caster.
return type_caster_base<absl::Status>::cast(std::forward<CType>(src),
policy, parent);
} else if (!src.ok()) {
// Convert a non-ok status into an exception.
throw google::StatusNotOk(std::forward<CType>(src));
} else {
// Return none for an ok status.
return none().release();
}
}
};
// Convert an absl::StatusOr.
template <typename PayloadType>
struct type_caster<absl::StatusOr<PayloadType>> {
public:
using PayloadCaster = make_caster<PayloadType>;
using StatusCaster = make_caster<absl::Status>;
static constexpr auto name = _("StatusOr[") + PayloadCaster::name + _("]");
// Conversion part 2 (C++ -> Python).
static handle cast(absl::StatusOr<PayloadType>&& src,
return_value_policy policy, handle parent,
bool throw_exception = true) {
google::CheckStatusModuleImported();
if (src.ok()) {
// Convert and return the payload.
return PayloadCaster::cast(std::forward<PayloadType>(*src), policy,
parent);
} else {
// Convert and return the error.
return StatusCaster::cast(std::move(src.status()),
return_value_policy::move, parent,
throw_exception);
}
}
};
} // namespace detail
} // namespace pybind11
#endif // PYBIND11_ABSEIL_STATUS_CASTERS_H_
#ifndef PYBIND11_ABSEIL_STATUS_NOT_OK_EXCEPTION_H_
#define PYBIND11_ABSEIL_STATUS_NOT_OK_EXCEPTION_H_
#include <exception>
#include "absl/status/status.h"
namespace pybind11 {
namespace google {
// Exception class which represents a non-ok status.
//
// This is in the pybind::google namespace because it was originally created to
// use with pybind11, but it does NOT depend on the pybind11 library.
class StatusNotOk : public std::exception {
public:
StatusNotOk(absl::Status&& status)
: status_(std::move(status)), what_(status_.ToString()) {}
StatusNotOk(const absl::Status& status)
: status_(status), what_(status_.ToString()) {}
const absl::Status& status() const& { return status_; }
absl::Status&& status() && { return std::move(status_); }
const char* what() const noexcept override { return what_.c_str(); }
private:
absl::Status status_;
std::string what_;
};
} // namespace google
} // namespace pybind11
#endif // PYBIND11_ABSEIL_STATUS_NOT_OK_EXCEPTION_H_
#include "pybind11_abseil/status_utils.h"
#include <pybind11/pybind11.h>
#include "pybind11_abseil/absl_casters.h"
namespace pybind11 {
namespace google {
namespace {
// Returns false if status_or represents a non-ok status object, and true in all
// other cases (including the case that this is passed a non-status object).
bool IsOk(handle status_or) {
detail::make_caster<absl::Status> caster;
// "Not a status" means "ok" for StatusOr.
if (!caster.load(status_or, true)) return true;
return static_cast<absl::Status &>(caster).ok();
}
} // namespace
void RegisterStatusBindings(module m) {
enum_<absl::StatusCode>(m, "StatusCode")
.value("OK", absl::StatusCode::kOk)
.value("CANCELLED", absl::StatusCode::kCancelled)
.value("UNKNOWN", absl::StatusCode::kUnknown)
.value("INVALID_ARGUMENT", absl::StatusCode::kInvalidArgument)
.value("DEADLINE_EXCEEDED", absl::StatusCode::kDeadlineExceeded)
.value("NOT_FOUND", absl::StatusCode::kNotFound)
.value("ALREADY_EXISTS", absl::StatusCode::kAlreadyExists)
.value("PERMISSION_DENIED", absl::StatusCode::kPermissionDenied)
.value("RESOURCE_EXHAUSTED", absl::StatusCode::kResourceExhausted)
.value("FAILED_PRECONDITION", absl::StatusCode::kFailedPrecondition)
.value("ABORTED", absl::StatusCode::kAborted)
.value("OUT_OF_RANGE", absl::StatusCode::kOutOfRange)
.value("UNIMPLEMENTED", absl::StatusCode::kUnimplemented)
.value("INTERNAL", absl::StatusCode::kInternal)
.value("UNAVAILABLE", absl::StatusCode::kUnavailable)
.value("DATA_LOSS", absl::StatusCode::kDataLoss)
.value("UNAUTHENTICATED", absl::StatusCode::kUnauthenticated);
class_<absl::Status>(m, "Status")
.def(init())
.def(init<absl::StatusCode, std::string>())
.def("ok", &absl::Status::ok)
.def("code", &absl::Status::code)
.def("message", &absl::Status::message)
.def(
"update",
(void (absl::Status::*)(const absl::Status &)) & absl::Status::Update,
arg("other"))
.def("to_string", &absl::Status::ToString)
.def("__repr__", &absl::Status::ToString);
m.def("is_ok", &IsOk, arg("status_or"),
"Returns false only if passed a non-ok status; otherwise returns true. "
"This can be used on the return value of a function which returns a "
"StatusOr without raising an exception. The .ok() method cannot be "
"used in this case because an ok status is never returned; instead, a "
"non-status object is returned, which doesn't have a .ok() method.");
// status_casters.h has not been included, so the functions below will
// return a wrapped status, not raise an exception.
// Return canonical errors.
m.def("aborted_error", &absl::AbortedError, arg("message"));
m.def("already_exists_error", &absl::AlreadyExistsError, arg("message"));
// CanceledError has an overload which takes no arguments, so we must cast it.
m.def("cancelled_error",
(absl::Status(*)(absl::string_view)) & absl::CancelledError,
arg("message"));
m.def("data_loss_error", &absl::DataLossError, arg("message"));
m.def("deadline_exceeded_error", &absl::DeadlineExceededError,
arg("message"));
m.def("failed_precondition_error", &absl::FailedPreconditionError,
arg("message"));
m.def("internal_error", &absl::InternalError, arg("message"));
m.def("invalid_argument_error", &absl::InvalidArgumentError, arg("message"));
m.def("not_found_error", &absl::NotFoundError, arg("message"));
m.def("out_of_range_error", &absl::OutOfRangeError, arg("message"));
m.def("permission_denied_error", &absl::PermissionDeniedError,
arg("message"));
m.def("resource_exhausted_error", &absl::ResourceExhaustedError,
arg("message"));
m.def("unauthenticated_error", &absl::UnauthenticatedError, arg("message"));
m.def("unavailable_error", &absl::UnavailableError, arg("message"));
m.def("unimplemented_error", &absl::UnimplementedError, arg("message"));
m.def("unknown_error", &absl::UnknownError, arg("message"));
// Register the exception.
static pybind11::exception<StatusNotOk> status_not_ok(m, "StatusNotOk");
// Register a custom handler which converts a C++ StatusNotOk to a Python
// StatusNotOk exception and adds the status field.
register_exception_translator([](std::exception_ptr p) {
try {
if (p) std::rethrow_exception(p);
} catch (StatusNotOk& e) {
auto rvalue_e = std::move(e);
status_not_ok.attr("status") = cast(rvalue_e.status());
status_not_ok(rvalue_e.what());
}
});
}
} // namespace google
} // namespace pybind11
// Utility classes functions for absl::Status objects.
// These are needed by both the status module and casters.
#ifndef PYBIND11_ABSEIL_STATUS_UTILS_H_
#define PYBIND11_ABSEIL_STATUS_UTILS_H_
#include <pybind11/pybind11.h>
#include <exception>
#include <stdexcept>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "pybind11_abseil/status_not_ok_exception.h"
namespace pybind11 {
namespace google {
// Wrapper type to signal to the type_caster that a non-ok status should not
// be converted into an object rather than a thrown exception.
template <typename StatusType>
struct NoThrowStatus {
NoThrowStatus(StatusType status_in)
: status(std::forward<StatusType>(status_in)) {}
StatusType status;
};
// Convert a absl::Status(Or) into a NoThrowStatus.
template <typename StatusType>
NoThrowStatus<StatusType> DoNotThrowStatus(StatusType status) {
return NoThrowStatus<StatusType>(std::forward<StatusType>(status));
}
// Convert a function returning a absl::Status(Or) into a function
// returning a NoThrowStatus.
template <typename StatusType, typename... Args>
std::function<NoThrowStatus<StatusType>(Args...)> DoNotThrowStatus(
std::function<StatusType(Args...)> f) {
return [f = std::move(f)](Args... args) {
return NoThrowStatus<StatusType>(
std::forward<StatusType>(f(std::forward<Args>(args)...)));
};
}
template <typename StatusType, typename... Args>
std::function<NoThrowStatus<StatusType>(Args...)> DoNotThrowStatus(
StatusType (*f)(Args...)) {
return [f](Args... args) {
return NoThrowStatus<StatusType>(
std::forward<StatusType>(f(std::forward<Args>(args)...)));
};
}
template <typename StatusType, typename Class, typename... Args>
std::function<NoThrowStatus<StatusType>(Class*, Args...)> DoNotThrowStatus(
StatusType (Class::*f)(Args...)) {
return [f](Class* c, Args... args) {
return NoThrowStatus<StatusType>(
std::forward<StatusType>((c->*f)(std::forward<Args>(args)...)));
};
}
template <typename StatusType, typename Class, typename... Args>
std::function<NoThrowStatus<StatusType>(const Class*, Args...)>
DoNotThrowStatus(StatusType (Class::*f)(Args...) const) {
return [f](const Class* c, Args... args) {
return NoThrowStatus<StatusType>(
std::forward<StatusType>((c->*f)(std::forward<Args>(args)...)));
};
}
// Registers the bindings for the status types in the given module. Can only
// be called once; subsequent calls will fail due to duplicate registrations.
void RegisterStatusBindings(module m);
// If modifying the functions below, see
// g3doc/pybind11_abseil/README.md#importing-the-status-module
// Returns true if the status module has been imported.
inline bool IsStatusModuleImported() {
return detail::get_type_info(typeid(absl::Status));
}
// In debug mode, throws a type error if the proto module is not imported.
// No-opt if NDEBUG is defined, and inlined so the compiler can optimize it out.
inline void CheckStatusModuleImported() {
#ifndef NDEBUG
if (!IsStatusModuleImported())
throw type_error(
"Status module has not been imported. Did you call ::pybind11::google"
"::ImportStatusModule() in your PYBIND11_MODULE definition?");
#endif
}
} // namespace google
} // namespace pybind11
#endif // PYBIND11_ABSEIL_STATUS_UTILS_H_
# Tests and examples for pybind11_abseil.
# load("//devtools/python/blaze:python3.bzl", "py2and3_strict_test")
load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
licenses(["notice"])
pybind_extension(
name = "absl_example",
srcs = ["absl_example.cc"],
deps = [
"//pybind11_abseil:absl_casters",
"//pybind11_abseil:absl_numpy_span_caster",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:optional",
"@com_google_absl//absl/types:span",
],
)
py_test(
name = "absl_test",
srcs = ["absl_test.py"],
data = [":absl_example.so"],
srcs_version = "PY2AND3",
)
pybind_extension(
name = "missing_import",
srcs = ["missing_import.cc"],
copts = ["-UNDEBUG"],
deps = [
"//pybind11_abseil:status_casters",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],
)
py_test(
name = "missing_import_test",
srcs = ["missing_import_test.py"],
data = [":missing_import.so"],
srcs_version = "PY2AND3",
)
pybind_extension(
name = "status_example",
srcs = ["status_example.cc"],
deps = [
"//pybind11_abseil:status_casters",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],
)
py_test(
name = "status_test",
srcs = ["status_test.py"],
data = [
":status_example.so",
"//pybind11_abseil:status.so",
],
srcs_version = "PY2AND3",
)
// Copyright (c) 2019 The Pybind Development Team. All rights reserved.
//
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include <pybind11/pybind11.h>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/time/civil_time.h"
#include "absl/time/time.h"
#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "pybind11_abseil/absl_casters.h"
#include "pybind11_abseil/absl_numpy_span_caster.h"
namespace pybind11 {
namespace test {
absl::Duration MakeDuration(double secs) { return absl::Seconds(secs); }
bool CheckDuration(const absl::Duration& duration, double secs) {
return duration == MakeDuration(secs);
}
absl::Time MakeTime(double secs) {
int64_t microsecs = static_cast<int64_t>(secs * 1e6);
return absl::FromUnixMicros(microsecs);
}
bool CheckDatetime(const absl::Time& datetime, double secs) {
return datetime == MakeTime(secs);
}
bool CheckSpan(absl::Span<const int> span, const std::vector<int>& values) {
if (span.size() != values.size()) return false;
for (int i = 0; i < span.size(); ++i) {
if (span[i] != values[i]) return false;
}
return true;
}
absl::CivilSecond MakeCivilSecond(double secs) {
return absl::ToCivilSecond(absl::FromUnixSeconds(static_cast<int64_t>(secs)),
absl::UTCTimeZone());
}
absl::CivilMinute MakeCivilMinute(double secs) {
return absl::ToCivilMinute(absl::FromUnixSeconds(static_cast<int64_t>(secs)),
absl::UTCTimeZone());
}
absl::CivilHour MakeCivilHour(double secs) {
return absl::ToCivilHour(absl::FromUnixSeconds(static_cast<int64_t>(secs)),
absl::UTCTimeZone());
}
absl::CivilDay MakeCivilDay(double secs) {
return absl::ToCivilDay(absl::FromUnixSeconds(static_cast<int64_t>(secs)),
absl::UTCTimeZone());
}
absl::CivilMonth MakeCivilMonth(double secs) {
return absl::ToCivilMonth(absl::FromUnixSeconds(static_cast<int64_t>(secs)),
absl::UTCTimeZone());
}
absl::CivilYear MakeCivilYear(double secs) {
return absl::ToCivilYear(absl::FromUnixSeconds(static_cast<int64_t>(secs)),
absl::UTCTimeZone());
}
bool CheckCivilSecond(absl::CivilSecond datetime, double secs) {
return datetime == MakeCivilSecond(secs);
}
bool CheckCivilMinute(absl::CivilMinute datetime, double secs) {
return datetime == MakeCivilMinute(secs);
}
bool CheckCivilHour(absl::CivilHour datetime, double secs) {
return datetime == MakeCivilHour(secs);
}
bool CheckCivilDay(absl::CivilDay datetime, double secs) {
return datetime == MakeCivilDay(secs);
}
bool CheckCivilMonth(absl::CivilMonth datetime, double secs) {
return datetime == MakeCivilMonth(secs);
}
bool CheckCivilYear(absl::CivilYear datetime, double secs) {
return datetime == MakeCivilYear(secs);
}
// Since a span does not own its elements, we must create a class to own them
// and persist beyond the function that constructs the span for testing.
class VectorContainer {
public:
absl::Span<const int> MakeSpan(const std::vector<int>& values) {
values_ = values;
return values_;
}
private:
std::vector<int> values_;
};
bool CheckStringView(absl::string_view view, const std::string& values) {
return view == values;
}
// Since a string view does not own its data, we must create a class to own
// them and persist beyond the function that constructs the span for testing.
class StringContainer {
public:
absl::string_view MakeStringView(const std::string& values) {
values_ = values;
return values_;
}
private:
std::string values_;
};
bool CheckOptional(const absl::optional<int> optional, bool given, int value) {
if (!given && !optional.has_value()) return true;
if (given && optional.has_value() && optional.value() == value) return true;
return false;
}
absl::optional<int> MakeOptional() { return absl::nullopt; }
absl::optional<int> MakeOptional(int value) { return value; }
absl::flat_hash_map<int, int> MakeMap(
const std::vector<std::pair<int, int>>& keys_and_values) {
absl::flat_hash_map<int, int> map;
for (const auto& kvp : keys_and_values) {
map.insert(kvp);
}
return map;
}
bool CheckMap(const absl::flat_hash_map<int, int>& map,
const std::vector<std::pair<int, int>>& keys_and_values) {
for (const auto& kvp : keys_and_values) {
auto found = map.find(kvp.first);
if (found == map.end()) {
return false;
}
if (found->second != kvp.second) {
return false;
}
}
return true;
}
absl::flat_hash_set<int> MakeSet(const std::vector<int>& values) {
return absl::flat_hash_set<int>(values.begin(), values.end());
}
bool CheckSet(const absl::flat_hash_set<int>& set,
const std::vector<int>& values) {
absl::flat_hash_set<int> check(values.begin(), values.end());
return set == check;
}
// Non-const Span.
template <typename T>
void FillNonConstSpan(T value, absl::Span<T> output_span) {
for (auto& i : output_span) {
i = value;
}
}
struct ObjectForSpan {
explicit ObjectForSpan(int v) : value(v) {}
int value;
};
int SumObjectPointersSpan(absl::Span<ObjectForSpan* const> inputs) {
int result = 0;
for (ObjectForSpan* item : inputs) {
result += item->value;
}
return result;
}
template <typename T>
void DefineNonConstSpan(module* py_m, absl::string_view type_name) {
py_m->def(absl::StrCat("fill_non_const_span_", type_name).c_str(),
&FillNonConstSpan<T>, arg("value"), arg("output_span").noconvert());
}
PYBIND11_MODULE(absl_example, m) {
// absl::Time/Duration bindings.
m.def("make_duration", &MakeDuration, arg("secs"));
m.def("check_duration", &CheckDuration, arg("duration"), arg("secs"));
m.def("make_datetime", &MakeTime, arg("secs"));
m.def("check_datetime", &CheckDatetime, arg("datetime"), arg("secs"));
// absl::CivilTime bindings
m.def("make_civilsecond", &MakeCivilSecond, arg("secs"));
m.def("check_civilsecond", &CheckCivilSecond, arg("datetime"), arg("secs"));
m.def("make_civilminute", &MakeCivilMinute, arg("secs"));
m.def("check_civilminute", &CheckCivilMinute, arg("datetime"), arg("secs"));
m.def("make_civilhour", &MakeCivilHour, arg("secs"));
m.def("check_civilhour", &CheckCivilHour, arg("datetime"), arg("secs"));
m.def("make_civilday", &MakeCivilDay, arg("secs"));
m.def("check_civilday", &CheckCivilDay, arg("datetime"), arg("secs"));
m.def("make_civilmonth", &MakeCivilMonth, arg("secs"));
m.def("check_civilmonth", &CheckCivilMonth, arg("datetime"), arg("secs"));
m.def("make_civilyear", &MakeCivilYear, arg("secs"));
m.def("check_civilyear", &CheckCivilYear, arg("datetime"), arg("secs"));
// absl::Span bindings.
m.def("check_span", &CheckSpan, arg("span"), arg("values"));
class_<VectorContainer>(m, "VectorContainer")
.def(init())
.def("make_span", &VectorContainer::MakeSpan, arg("values"));
// non-const absl::Span bindings.
DefineNonConstSpan<double>(&m, "double");
DefineNonConstSpan<int>(&m, "int");
// Wrap a const Span with a non-const Span lambda to avoid copying data.
m.def(
"check_span_no_copy",
[](absl::Span<int> span, const std::vector<int>& values) -> bool {
return CheckSpan(span, values);
},
arg("span"), arg("values"));
class_<ObjectForSpan>(m, "ObjectForSpan").def(init<int>());
m.def("SumObjectPointersSpan", &SumObjectPointersSpan);
// absl::string_view bindings.
m.def("check_string_view", &CheckStringView, arg("view"), arg("values"));
class_<StringContainer>(m, "StringContainer")
.def(init())
.def("make_string_view", &StringContainer::MakeStringView, arg("values"));
// absl::optional bindings.
m.def("check_optional", &CheckOptional, arg("optional") = absl::nullopt,
arg("given") = false, arg("value") = 0);
m.def("make_optional", (absl::optional<int>(*)()) & MakeOptional);
m.def("make_optional", (absl::optional<int>(*)(int)) & MakeOptional,
arg("value"));
// absl::flat_hash_map bindings
m.def("make_map", &MakeMap, arg("keys_and_values"));
m.def("check_map", &CheckMap, arg("map"), arg("keys_and_values"));
// absl::flat_hash_set bindings
m.def("make_set", &MakeSet, arg("values"));
m.def("check_set", &CheckSet, arg("set"), arg("values"));
}
} // namespace test
} // namespace pybind11
# Copyright (c) 2019 The Pybind Development Team. All rights reserved.
#
# All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
"""Tests for absl pybind11 casters."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import datetime
import sys
from absl.testing import absltest
from absl.testing import parameterized
from dateutil import tz
import numpy as np
from pybind11_abseil.tests import absl_example
if sys.version_info.major > 2:
DateTime = datetime.datetime
else:
class DateTime(datetime.datetime):
def timestamp(self):
if self.tzinfo:
with_tz = self
else:
with_tz = self.replace(tzinfo=tz.gettz()) # pylint: disable=g-tzinfo-replace
epoch = datetime.datetime(1970, 1, 1, tzinfo=tz.tzutc()) # pylint: disable=g-tzinfo-datetime
return (with_tz - epoch).total_seconds()
class AbslTimeTest(parameterized.TestCase):
SECONDS_IN_DAY = 24 * 60 * 60
POSITIVE_SECS = 3 * SECONDS_IN_DAY + 2.5
NEGATIVE_SECS = -3 * SECONDS_IN_DAY + 2.5
TEST_DATETIME = DateTime(2000, 1, 2, 3, 4, 5, int(5e5))
# Linter error relevant for pytz only.
# pylint: disable=g-tzinfo-replace
TEST_DATETIME_UTC = TEST_DATETIME.replace(tzinfo=tz.tzutc())
# pylint: enable=g-tzinfo-replace
TEST_DATE = datetime.date(2000, 1, 2)
def test_return_positive_duration(self):
duration = absl_example.make_duration(self.POSITIVE_SECS)
self.assertEqual(duration.days, 3)
self.assertEqual(duration.seconds, 2)
self.assertEqual(duration.microseconds, 5e5)
def test_return_negative_duration(self):
duration = absl_example.make_duration(self.NEGATIVE_SECS)
self.assertEqual(duration.days, -3)
self.assertEqual(duration.seconds, 2)
self.assertEqual(duration.microseconds, 5e5)
def test_pass_positive_duration(self):
duration = datetime.timedelta(seconds=self.POSITIVE_SECS)
self.assertTrue(absl_example.check_duration(duration, self.POSITIVE_SECS))
def test_pass_negative_duration(self):
duration = datetime.timedelta(seconds=self.NEGATIVE_SECS)
self.assertTrue(absl_example.check_duration(duration, self.NEGATIVE_SECS))
def test_return_datetime(self):
secs = self.TEST_DATETIME.timestamp()
local_tz = tz.gettz()
# pylint: disable=g-tzinfo-datetime
# Warning about tzinfo applies to pytz, but we are using dateutil.tz
expected_datetime = DateTime(
year=self.TEST_DATETIME.year,
month=self.TEST_DATETIME.month,
day=self.TEST_DATETIME.day,
hour=self.TEST_DATETIME.hour,
minute=self.TEST_DATETIME.minute,
second=self.TEST_DATETIME.second,
microsecond=self.TEST_DATETIME.microsecond,
tzinfo=local_tz)
# pylint: enable=g-tzinfo-datetime
# datetime handling code will set the local timezone on C++ times for want
# of a better alternative
self.assertEqual(expected_datetime, absl_example.make_datetime(secs))
def test_pass_date(self):
secs = DateTime(
self.TEST_DATE.year,
self.TEST_DATE.month,
self.TEST_DATE.day).timestamp()
self.assertTrue(absl_example.check_datetime(self.TEST_DATE, secs))
def test_pass_datetime(self):
secs = self.TEST_DATETIME.timestamp()
self.assertTrue(absl_example.check_datetime(self.TEST_DATETIME, secs))
def test_pass_datetime_with_timezone(self):
pacific_tz = tz.gettz('America/Los_Angeles')
# pylint: disable=g-tzinfo-datetime
# Warning about tzinfo applies to pytz, but we are using dateutil.tz
dt_with_tz = DateTime(
year=2020, month=2, day=1, hour=20, tzinfo=pacific_tz)
# pylint: enable=g-tzinfo-datetime
secs = dt_with_tz.timestamp()
self.assertTrue(absl_example.check_datetime(dt_with_tz, secs))
def test_pass_datetime_dst_with_timezone(self):
pacific_tz = tz.gettz('America/Los_Angeles')
# pylint: disable=g-tzinfo-datetime
dst_end = DateTime(2020, 11, 1, 2, 0, 0, tzinfo=pacific_tz)
# pylint: enable=g-tzinfo-datetime
secs = dst_end.timestamp()
self.assertTrue(absl_example.check_datetime(dst_end, secs))
def test_pass_datetime_dst(self):
dst_end = DateTime(2020, 11, 1, 2, 0, 0)
secs = dst_end.timestamp()
self.assertTrue(absl_example.check_datetime(dst_end, secs))
@parameterized.named_parameters(('before', -1),
('flip', 0),
('after', 1))
def test_dst_datetime_from_timestamp(self, offs):
secs_flip = 1604224799 # 2020-11-01T02:00:00-08:00
secs = secs_flip + offs
time_utc = DateTime.fromtimestamp(secs, tz.tzutc())
time_local_aware = time_utc.astimezone(tz.gettz())
time_local_naive = time_local_aware.replace(tzinfo=None)
for time in (time_utc, time_local_aware, time_local_naive):
self.assertTrue(absl_example.check_datetime(time, secs))
def test_pass_datetime_pre_unix_epoch(self):
dt = DateTime(1969, 7, 16, 10, 56, 7, microsecond=140)
secs = dt.timestamp()
self.assertTrue(absl_example.check_datetime(dt, secs))
def test_return_civilsecond(self):
# We need to use a timezone aware datetime here, otherwise
# datetime.timestamp converts to localtime. UTC is chosen as the convention
# in the test cases.
truncated = self.TEST_DATETIME.replace(microsecond=0)
self.assertEqual(
truncated,
absl_example.make_civilsecond(self.TEST_DATETIME_UTC.timestamp()))
def test_pass_datetime_as_civilsecond(self):
truncated = self.TEST_DATETIME_UTC.replace(microsecond=0)
self.assertTrue(
absl_example.check_civilsecond(self.TEST_DATETIME,
truncated.timestamp()))
def test_return_civilminute(self):
truncated = self.TEST_DATETIME.replace(second=0, microsecond=0)
self.assertEqual(
truncated,
absl_example.make_civilminute(self.TEST_DATETIME_UTC.timestamp()))
def test_pass_datetime_as_civilminute(self):
truncated = self.TEST_DATETIME_UTC.replace(second=0, microsecond=0)
self.assertTrue(
absl_example.check_civilminute(self.TEST_DATETIME,
truncated.timestamp()))
def test_return_civilhour(self):
truncated = self.TEST_DATETIME.replace(minute=0, second=0, microsecond=0)
self.assertEqual(
truncated,
absl_example.make_civilhour(self.TEST_DATETIME_UTC.timestamp()))
def test_pass_datetime_as_civilhour(self):
truncated = self.TEST_DATETIME_UTC.replace(
minute=0, second=0, microsecond=0)
self.assertTrue(
absl_example.check_civilhour(self.TEST_DATETIME, truncated.timestamp()))
def test_return_civilday(self):
truncated = self.TEST_DATETIME.replace(
hour=0, minute=0, second=0, microsecond=0)
self.assertEqual(
truncated,
absl_example.make_civilday(self.TEST_DATETIME_UTC.timestamp()))
def test_pass_datetime_as_civilday(self):
truncated = self.TEST_DATETIME_UTC.replace(
hour=0, minute=0, second=0, microsecond=0)
self.assertTrue(
absl_example.check_civilday(self.TEST_DATETIME, truncated.timestamp()))
def test_return_civilmonth(self):
truncated = self.TEST_DATETIME.replace(
day=1, hour=0, minute=0, second=0, microsecond=0)
self.assertEqual(
truncated,
absl_example.make_civilmonth(self.TEST_DATETIME_UTC.timestamp()))
def test_pass_datetime_as_civilmonth(self):
truncated = self.TEST_DATETIME_UTC.replace(
day=1, hour=0, minute=0, second=0, microsecond=0)
self.assertTrue(
absl_example.check_civilmonth(self.TEST_DATETIME,
truncated.timestamp()))
def test_return_civilyear(self):
truncated = self.TEST_DATETIME.replace(
month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
self.assertEqual(
truncated,
absl_example.make_civilyear(self.TEST_DATETIME_UTC.timestamp()))
def test_pass_datetime_as_civilyear(self):
truncated = self.TEST_DATETIME_UTC.replace(
month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
self.assertTrue(
absl_example.check_civilyear(self.TEST_DATETIME, truncated.timestamp()))
class AbslSpanTest(parameterized.TestCase):
def test_return_span(self):
values = [1, 2, 3, 4]
container = absl_example.VectorContainer()
self.assertSequenceEqual(container.make_span(values), values)
@parameterized.named_parameters(('list', [2, 4, 6]), ('tuple', (1, 3, 5, 7)),
('numpy', np.array([7, 8, 9])))
def test_pass_span_from(self, values):
# Pass values twice- one will be converted to a span, the other to a vector
# (which is known to work), and then they will be compared.
self.assertTrue(absl_example.check_span(values, values))
def test_span_with_pointers(self):
objs = [absl_example.ObjectForSpan(3), absl_example.ObjectForSpan(5)]
self.assertEqual(absl_example.SumObjectPointersSpan(objs), 8)
class AbslNonConstSpanTest(parameterized.TestCase):
_VECTOR_SIZE = 5
_CHECKED_VALUE_DOUBLE = 3.14
_CHECKED_VALUE_INT = 43
@parameterized.named_parameters(
('double', np.zeros(_VECTOR_SIZE, dtype=np.float),
'fill_non_const_span_double', _CHECKED_VALUE_DOUBLE),
('int', np.zeros(_VECTOR_SIZE, dtype=np.int32),
'fill_non_const_span_int', _CHECKED_VALUE_INT))
def test_span_as_out_parameter(self, vector, function_name, value):
span_test_function = getattr(absl_example, function_name)
span_test_function(value, vector)
for e in vector:
self.assertEqual(e, value)
@parameterized.named_parameters(
('double', np.zeros((_VECTOR_SIZE, _VECTOR_SIZE), dtype=np.float),
'fill_non_const_span_double', _CHECKED_VALUE_DOUBLE),
('int', np.zeros(
(_VECTOR_SIZE, _VECTOR_SIZE),
dtype=np.int32), 'fill_non_const_span_int', _CHECKED_VALUE_INT))
def test_fails_for_wrong_numpy_dimensions(self, vector, function_name, value):
span_test_function = getattr(absl_example, function_name)
with self.assertRaises(TypeError):
span_test_function(value, vector)
@parameterized.named_parameters(
('double', np.zeros(_VECTOR_SIZE, dtype=np.float),
'fill_non_const_span_double', _CHECKED_VALUE_DOUBLE),
('int', np.zeros(_VECTOR_SIZE, dtype=np.int32),
'fill_non_const_span_int', _CHECKED_VALUE_INT))
def test_fails_for_non_writable_numpy_vector(self, vector, function_name,
value):
span_test_function = getattr(absl_example, function_name)
vector.flags.writeable = False
with self.assertRaises(TypeError):
span_test_function(value, vector)
@parameterized.named_parameters(
('double', [0.0] * _VECTOR_SIZE, 'fill_non_const_span_double',
_CHECKED_VALUE_DOUBLE), ('int', [0] * _VECTOR_SIZE,
'fill_non_const_span_int', _CHECKED_VALUE_INT))
def test_fails_for_non_numpy_vector(self, vector, function_name, value):
span_test_function = getattr(absl_example, function_name)
with self.assertRaises(TypeError):
span_test_function(value, vector)
@parameterized.named_parameters(
('double', np.zeros(_VECTOR_SIZE, dtype=np.float),
'fill_non_const_span_double', _CHECKED_VALUE_DOUBLE),
('int', np.zeros(_VECTOR_SIZE, dtype=np.int32),
'fill_non_const_span_int', _CHECKED_VALUE_INT))
def test_fails_for_non_contiguous_numpy_vector(self, vector, function_name,
value):
span_test_function = getattr(absl_example, function_name)
# View with step > 1 is a way to get non-contiguous memory.
v_strided = vector[::2]
with self.assertRaises(TypeError):
span_test_function(value, v_strided)
def test_fails_for_not_supported_type(self):
# Checking only the floating version of the function as there is a type
# mismatch in any case.
vector = np.zeros(AbslNonConstSpanTest._VECTOR_SIZE, dtype=np.unicode_)
with self.assertRaises(TypeError):
absl_example.fill_non_const_span_double(
AbslNonConstSpanTest._CHECKED_VALUE_DOUBLE, vector)
def test_const_span_wrapper(self):
# Test that we can use non-const Span as wrapper for const Span to avoid
# copying data.
values = [1, 2, 3, 4]
vector = np.array(values, dtype=np.int32)
self.assertTrue(absl_example.check_span_no_copy(vector, values))
class AbslStringViewTest(absltest.TestCase):
TEST_STRING = 'test string!'
def test_return_view(self):
container = absl_example.StringContainer()
self.assertSequenceEqual(
container.make_string_view(self.TEST_STRING), self.TEST_STRING)
def test_pass_string_view(self):
self.assertTrue(
absl_example.check_string_view(self.TEST_STRING, self.TEST_STRING))
class AbslFlatHashMapTest(absltest.TestCase):
def test_return_map(self):
keys_and_values = [(1, 2), (3, 4), (5, 6)]
expected = dict(keys_and_values)
self.assertEqual(expected, absl_example.make_map(keys_and_values))
def test_pass_map(self):
expected = [(10, 20), (30, 40)]
self.assertTrue(absl_example.check_map(dict(expected), expected))
class AbslFlatHashSetTest(absltest.TestCase):
def test_return_set(self):
values = [1, 3, 7, 5]
expected = set(values)
self.assertEqual(expected, absl_example.make_set(values))
def test_pass_set(self):
expected = [10, 20, 30, 40]
self.assertTrue(absl_example.check_set(set(expected), expected))
class AbslOptionalTest(absltest.TestCase):
def test_pass_default_nullopt(self):
self.assertTrue(absl_example.check_optional())
def test_pass_value(self):
self.assertTrue(absl_example.check_optional(5, True, 5))
def test_pass_none(self):
self.assertTrue(absl_example.check_optional(None, False))
def test_return_value(self):
self.assertEqual(absl_example.make_optional(5), 5)
def test_return_none(self):
self.assertIsNone(absl_example.make_optional())
if __name__ == '__main__':
absltest.main()
// A pybind11 module which uses the status caster but does not call
// ImportStatusModule (as it should), to test the behavior in that case.
#include <pybind11/pybind11.h>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "pybind11_abseil/status_casters.h"
namespace pybind11 {
namespace test {
absl::Status ReturnStatus() { return absl::InternalError("test"); }
absl::StatusOr<int> ReturnStatusOr() { return absl::InternalError("test"); }
PYBIND11_MODULE(missing_import, m) {
m.def("returns_status", &ReturnStatus);
m.def("returns_status_or", &ReturnStatusOr);
}
} // namespace test
} // namespace pybind11
"""Tests that the casters raise a TypeError if the status module is missing."""
from absl.testing import absltest
from pybind11_abseil.tests import missing_import
class MissingStatusImportTest(absltest.TestCase):
message_regex = 'Status module has not been imported.*'
def test_returns_status(self):
with self.assertRaisesRegex(TypeError, self.message_regex):
missing_import.returns_status()
def test_returns_status_or(self):
with self.assertRaisesRegex(TypeError, self.message_regex):
missing_import.returns_status_or()
if __name__ == '__main__':
absltest.main()
#include <pybind11/pybind11.h>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "pybind11_abseil/status_casters.h"
namespace pybind11 {
namespace test {
struct IntValue {
IntValue() = default;
IntValue(int value_in) : value(value_in) {}
int value;
};
class TestClass {
public:
absl::Status MakeStatus(absl::StatusCode code, const std::string& text = "") {
return absl::Status(code, text);
}
absl::Status MakeStatusConst(absl::StatusCode code,
const std::string& text = "") const {
return absl::Status(code, text);
}
absl::StatusOr<int> MakeFailureStatusOr(absl::StatusCode code,
const std::string& text = "") {
return absl::Status(code, text);
}
};
bool CheckStatus(const absl::Status& status, absl::StatusCode code) {
return status.code() == code;
}
absl::Status ReturnStatus(absl::StatusCode code, const std::string& text = "") {
return absl::Status(code, text);
}
pybind11::object ReturnStatusManualCast(absl::StatusCode code,
const std::string& text = "") {
return pybind11::cast(google::DoNotThrowStatus(absl::Status(code, text)));
}
const absl::Status& ReturnStatusRef(absl::StatusCode code,
const std::string& text = "") {
static absl::Status static_status;
static_status = absl::Status(code, text);
return static_status;
}
const absl::Status* ReturnStatusPtr(absl::StatusCode code,
const std::string& text = "") {
static absl::Status static_status;
static_status = absl::Status(code, text);
return &static_status;
}
absl::StatusOr<int> ReturnFailureStatusOr(absl::StatusCode code,
const std::string& text = "") {
return absl::Status(code, text);
}
pybind11::object ReturnFailureStatusOrManualCast(absl::StatusCode code,
const std::string& text = "") {
return pybind11::cast(google::DoNotThrowStatus(absl::Status(code, text)));
}
absl::StatusOr<int> ReturnValueStatusOr(int value) { return value; }
absl::StatusOr<const IntValue*> ReturnPtrStatusOr(int value) {
static IntValue static_object;
static_object.value = value;
return &static_object;
}
absl::StatusOr<std::unique_ptr<IntValue>> ReturnUniquePtrStatusOr(int value) {
return absl::make_unique<IntValue>(value);
}
PYBIND11_MODULE(status_example, m) {
auto status_module = pybind11::google::ImportStatusModule();
m.attr("StatusNotOk") = status_module.attr("StatusNotOk");
class_<IntValue>(m, "IntValue").def_readonly("value", &IntValue::value);
class_<TestClass>(m, "TestClass")
.def(init())
.def("make_status", google::DoNotThrowStatus(&TestClass::MakeStatus),
arg("code"), arg("text") = "")
.def("make_status_const",
google::DoNotThrowStatus(&TestClass::MakeStatusConst), arg("code"),
arg("text") = "")
.def("make_failure_status_or",
google::DoNotThrowStatus(&TestClass::MakeFailureStatusOr),
arg("code"), arg("text") = "");
// absl::Status bindings
m.def("check_status", &CheckStatus, arg("status"), arg("code"));
m.def("return_status", &ReturnStatus, "Raise an error if code is not OK.",
arg("code"), arg("text") = "");
m.def("make_status", google::DoNotThrowStatus(&ReturnStatus),
"Return a status without raising an error, regardless of what it is.",
arg("code"), arg("text") = "");
m.def("make_status_manual_cast", ReturnStatusManualCast,
"Return a status without raising an error, regardless of what it is.",
arg("code"), arg("text") = "");
m.def("make_status_ref", google::DoNotThrowStatus(&ReturnStatusRef),
"Return a reference to a static status value without raising an error.",
arg("code"), arg("text") = "", return_value_policy::reference);
m.def("make_status_ptr", google::DoNotThrowStatus(&ReturnStatusPtr),
"Return a reference to a static status value without raising an error.",
arg("code"), arg("text") = "", return_value_policy::reference);
// absl::StatusOr bindings
m.def("return_value_status_or", &ReturnValueStatusOr, arg("value"));
m.def("return_failure_status_or", &ReturnFailureStatusOr,
"Raise an error with the given code.", arg("code"), arg("text") = "");
m.def("make_failure_status_or",
google::DoNotThrowStatus(&ReturnFailureStatusOr), arg("code"),
arg("text") = "", "Return a status without raising an error.");
m.def("make_failure_status_or_manual_cast", &ReturnFailureStatusOrManualCast,
arg("code"), arg("text") = "", "Return a status.");
m.def("return_ptr_status_or", &ReturnPtrStatusOr, arg("value"),
"Return a reference in a status or to a static value.",
return_value_policy::reference);
m.def("return_unique_ptr_status_or", &ReturnUniquePtrStatusOr, arg("value"));
}
} // namespace test
} // namespace pybind11
"""Tests for google3.third_party.pybind11_abseil.status_casters."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from absl.testing import absltest
from pybind11_abseil import status
from pybind11_abseil.tests import status_example
class StatusTest(absltest.TestCase):
def test_pass_status(self):
test_status = status.Status(status.StatusCode.CANCELLED, 'test')
self.assertTrue(
status_example.check_status(test_status, status.StatusCode.CANCELLED))
def test_return_ok(self):
# The return_status function should convert an ok status to None.
self.assertIsNone(status_example.return_status(status.StatusCode.OK))
def test_return_not_ok(self):
# The return_status function should convert a non-ok status to an exception.
with self.assertRaises(status.StatusNotOk) as cm:
status_example.return_status(status.StatusCode.CANCELLED, 'test')
self.assertEqual(cm.exception.status.code(), status.StatusCode.CANCELLED)
self.assertEqual(cm.exception.status.message(), 'test')
def test_return_not_ok_catch_with_alias(self):
# Catch as status_example.StatusNotOk, an alias of status.StatusNotOk.
with self.assertRaises(status_example.StatusNotOk) as cm:
status_example.return_status(status.StatusCode.CANCELLED, 'test')
self.assertEqual(cm.exception.status.code(), status.StatusCode.CANCELLED)
self.assertEqual(cm.exception.status.message(), 'test')
def test_return_not_ok_catch_as_generic_exception(self):
# Catch as a generic Exception, the base type of StatusNotOk.
with self.assertRaises(Exception):
status_example.return_status(status.StatusCode.CANCELLED, 'test')
def test_make_ok(self):
# The make_status function has been set up to return a status object
# instead of raising an exception (this is done in status_example.cc).
test_status = status_example.make_status(status.StatusCode.OK)
self.assertEqual(test_status.code(), status.StatusCode.OK)
self.assertTrue(test_status.ok())
def test_make_not_ok(self):
# The make_status function should always return a status object, even if
# it is not ok (ie, it should *not* convert it to an exception).
test_status = status_example.make_status(status.StatusCode.CANCELLED)
self.assertEqual(test_status.code(), status.StatusCode.CANCELLED)
self.assertFalse(test_status.ok())
def test_make_not_ok_manual_cast(self):
test_status = status_example.make_status_manual_cast(
status.StatusCode.CANCELLED)
self.assertEqual(test_status.code(), status.StatusCode.CANCELLED)
def test_make_status_ref(self):
result_1 = status_example.make_status_ref(status.StatusCode.OK)
self.assertEqual(result_1.code(), status.StatusCode.OK)
result_2 = status_example.make_status_ref(status.StatusCode.CANCELLED)
self.assertEqual(result_2.code(), status.StatusCode.CANCELLED)
# result_1 and 2 reference the same value, so they should always be equal.
self.assertEqual(result_1.code(), result_2.code())
def test_make_status_ptr(self):
result_1 = status_example.make_status_ptr(status.StatusCode.OK)
self.assertEqual(result_1.code(), status.StatusCode.OK)
result_2 = status_example.make_status_ptr(status.StatusCode.CANCELLED)
self.assertEqual(result_2.code(), status.StatusCode.CANCELLED)
# result_1 and 2 reference the same value, so they should always be equal.
self.assertEqual(result_1.code(), result_2.code())
def test_canonical_error(self):
test_status = status.aborted_error('test')
self.assertEqual(test_status.code(), status.StatusCode.ABORTED)
self.assertEqual(test_status.message(), 'test')
def test_member_method(self):
test_status = status_example.TestClass().make_status(status.StatusCode.OK)
self.assertEqual(test_status.code(), status.StatusCode.OK)
test_status = status_example.TestClass().make_status_const(
status.StatusCode.OK)
self.assertEqual(test_status.code(), status.StatusCode.OK)
def test_is_ok(self):
ok_status = status_example.make_status(status.StatusCode.OK)
self.assertTrue(status.is_ok(ok_status))
failure_status = status_example.make_status(status.StatusCode.CANCELLED)
self.assertFalse(status.is_ok(failure_status))
class StatusOrTest(absltest.TestCase):
def test_return_value(self):
self.assertEqual(status_example.return_value_status_or(5), 5)
def test_return_not_ok(self):
with self.assertRaises(status.StatusNotOk) as cm:
status_example.return_failure_status_or(status.StatusCode.NOT_FOUND)
self.assertEqual(cm.exception.status.code(), status.StatusCode.NOT_FOUND)
def test_make_not_ok(self):
self.assertEqual(
status_example.make_failure_status_or(
status.StatusCode.CANCELLED).code(), status.StatusCode.CANCELLED)
def test_make_not_ok_manual_cast(self):
self.assertEqual(
status_example.make_failure_status_or_manual_cast(
status.StatusCode.CANCELLED).code(), status.StatusCode.CANCELLED)
def test_return_ptr_status_or(self):
result_1 = status_example.return_ptr_status_or(5)
self.assertEqual(result_1.value, 5)
result_2 = status_example.return_ptr_status_or(6)
self.assertEqual(result_2.value, 6)
# result_1 and 2 reference the same value, so they should always be equal.
self.assertEqual(result_1.value, result_2.value)
def test_return_unique_ptr(self):
result = status_example.return_unique_ptr_status_or(5)
self.assertEqual(result.value, 5)
def test_member_method(self):
test_status = status_example.TestClass().make_failure_status_or(
status.StatusCode.ABORTED)
self.assertEqual(test_status.code(), status.StatusCode.ABORTED)
def test_is_ok(self):
ok_result = status_example.return_value_status_or(5)
self.assertEqual(ok_result, 5)
self.assertTrue(status.is_ok(ok_result))
failure_result = status_example.make_failure_status_or(
status.StatusCode.CANCELLED)
self.assertFalse(status.is_ok(failure_result))
if __name__ == '__main__':
absltest.main()
#!/bin/bash
# The following scripts:
# - creates a virtualenv
# - installs the pip package dependencies
# - builds and runs tests
set -e # exit when any command fails
# set -x # Prints all executed command
MYDIR="$(dirname "$(realpath "$0")")"
BAZEL=$(which bazel)
if [ ! -x $BAZEL ]
then
echo -n "Bazel not found (bazel (https://bazel.build/) is needed to "
echo "compile & test). Exiting..."
exit 1
fi
is_in_virtual_env="false"
# if we are in a virtual_env, we will not create a new one inside.
if [[ "$VIRTUAL_ENV" != "" ]]
then
echo -e "\e[1m\e[93mVirtualenv already detected. We do not create a new one.\e[0m"
is_in_virtual_env="true"
fi
echo -e "\e[33mRunning ${0} from $PWD\e[0m"
PYBIN=`which python3`
if [ ! -x $PYBIN ]
then
echo -e "\e[1m\e[93m$PYBIN not found! Skip build and test.\e[0m"
continue
fi
PYVERSION=$($PYBIN -c 'import sys; print(".".join(map(str, sys.version_info[:3])))')
VENV_DIR="./venv"
if [[ $is_in_virtual_env == "false" ]]; then
if ! [ -d "$VENV_DIR" ]; then
echo "Installing..."
echo -e "\e[33mInstalling a virtualenv to $VENV_DIR. The setup is long the first time, please wait.\e[0m"
virtualenv -p $PYBIN $VENV_DIR
else
echo -e "\e[33mReusing virtualenv from $VENV_DIR.\e[0m"
fi
source $VENV_DIR/bin/activate
fi
# We only exit the virtualenv if we created one.
function cleanup {
if [[ $is_in_virtual_env == "true" ]]; then
echo "Exiting virtualenv"
deactivate
fi
}
trap cleanup EXIT
echo -e "\e[33mInstalling the requirements (use --noinstall to skip).\e[0m"
pip3 install --upgrade -r ./requirements.txt
echo "Building and testing in $PWD using 'python' (version $PYVERSION)."
export PYTHON_BIN_PATH=`which python3`
export PYTHON_LIB_PATH=`python3 -c "import sysconfig; print(sysconfig.get_path('include'))"`
echo "Using PYTHON_BIN_PATH: $PYTHON_BIN_PATH"
echo "Using PYTHON_LIB_PATH: $PYTHON_LIB_PATH"
BAZEL_CXXOPTS="-std=c++11" bazel test ... --test_output=errors
BAZEL_CXXOPTS="-std=c++14" bazel test ... --test_output=errors
BAZEL_CXXOPTS="-std=c++17" bazel test ... --test_output=errors
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment