diff --git a/docs/classes.rst b/docs/classes.rst index 30fb2a2d5f..4d2b47bd12 100644 --- a/docs/classes.rst +++ b/docs/classes.rst @@ -443,3 +443,46 @@ The entries defined by the enumeration type are exposed in the ``__members__`` p ... By default, these are omitted to conserve space. + +Python 3 enumerations +===================== + +Python 3 provides support for rich enumeration types in ``enum`` module [#enum]_ of the +standard library; it can be also used in Python 2 by installing ``enum34`` backport package. + +In order to expose a C++ enumeration type as a subclass of ``enum.IntEnum`` in Python, +one can use the :class:`py3_enum` wrapper whose interface is similar to that of :class:`enum_`: + +.. code-block:: cpp + + enum class Captain { + Kirk = 0, + Picard + }; + +The binding code will look like this: + +.. code-block:: cpp + + py::py3_enum(m, "Captain") + .value("Kirk", Captain::Kirk) + .value("Picard", Captain::Picard); + +The corresponding Python type is a subclass of ``enum.IntEnum`` and, as a result, is also +a subclass of ``int``: + +.. code-block:: pycon + + >>> Captain.mro() + [, , int, , object] + >>> list(Captain) + [, ] + >>> int(Captain.Picard) + 1 + >>> Captain.Kirk.name, Captain.Kirk.value + ('Kirk', 0) + +For more details on ``IntEnum`` functionality, see the official docs [#intenum]_. + +.. [#enum] https://docs.python.org/3/library/enum.html +.. [#intenum] https://docs.python.org/3/library/enum.html#intenum diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 6ca49a89eb..5e5d00ce4e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -950,6 +950,72 @@ template class type_caster> { std::tuple...> value; }; +template +struct py3_enum_info { + handle type = {}; + std::unordered_map values = {}; + + py3_enum_info() = default; + + py3_enum_info(handle type, const dict& values) : type(type) { + for (auto item : values) { + this->values[item.second.cast()] = type.attr(item.first); + } + } +}; + +template +struct type_caster::value>> { + using underlying_type = typename std::underlying_type::type; + +private: + using base_caster = type_caster_base; + + static std::unique_ptr>& py3_info() { + static std::unique_ptr> info; + return info; + } + + base_caster caster; + T value; + +public: + template using cast_op_type = pybind11::detail::cast_op_type; + + operator T*() { return py3_info() ? &value : static_cast(caster); } + operator T&() { return py3_info() ? value : static_cast(caster); } + + static handle cast(const T& src, return_value_policy rvp, handle parent) { + if (py3_info()) { + auto it = py3_info()->values.find(static_cast(src)); + if (it == py3_info()->values.end()) + return {}; + return it->second.inc_ref(); + } + return base_caster::cast(src, rvp, parent); + } + + bool load(handle src, bool convert) { + if (!src) + return false; + if (py3_info()) { + if (!isinstance(src, py3_info()->type)) + return false; + value = static_cast(src.cast()); + return true; + } + return caster.load(src, convert); + } + + static PYBIND11_DESCR name() { + return base_caster::name(); + } + + static void bind(handle type, const dict& values) { + py3_info().reset(new py3_enum_info(type, values)); + } +}; + /// Helper class which abstracts away certain actions. Users can provide specializations for /// custom holders, but it's only necessary if the type has a non-standard interface. template diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 62ee786243..e816b3f894 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -39,6 +39,8 @@ NAMESPACE_BEGIN(pybind11) +template class class_; + /// Wraps an arbitrary C++ function/method/lambda function/.. into a callable Python object class cpp_function : public function { public: @@ -891,225 +893,258 @@ void call_operator_delete(T *p) { T::operator delete(p); } inline void call_operator_delete(void *p) { ::operator delete(p); } -NAMESPACE_END(detail) - -template -class class_ : public detail::generic_type { - template using is_holder = detail::is_holder_type; - template using is_subtype = detail::bool_constant::value && !std::is_same::value>; - template using is_base = detail::bool_constant::value && !std::is_same::value>; - // struct instead of using here to help MSVC: - template struct is_valid_class_option : - detail::any_of, is_subtype, is_base> {}; +template +class class_interface { + using cls = typename std::conditional< + std::is_convertible&>::value, + class_&, + class_ + >::type; public: - using type = type_; - using type_alias = detail::exactly_one_t; - constexpr static bool has_alias = !std::is_void::value; - using holder_type = detail::exactly_one_t, options...>; - using instance_type = detail::instance; - - static_assert(detail::all_of...>::value, - "Unknown/invalid class_ template parameters provided"); - - PYBIND11_OBJECT(class_, generic_type, PyType_Check) - - template - class_(handle scope, const char *name, const Extra &... extra) { - using namespace detail; - - // MI can only be specified via class_ template options, not constructor parameters - static_assert( - none_of...>::value || // no base class arguments, or: - ( constexpr_sum(is_pyobject::value...) == 1 && // Exactly one base - constexpr_sum(is_base::value...) == 0 && // no template option bases - none_of...>::value), // no multiple_inheritance attr - "Error: multiple inheritance bases must be specified via class_ template options"); - - type_record record; - record.scope = scope; - record.name = name; - record.type = &typeid(type); - record.type_size = sizeof(conditional_t); - record.instance_size = sizeof(instance_type); - record.init_holder = init_holder; - record.dealloc = dealloc; - record.default_holder = std::is_same>::value; - - set_operator_new(&record); - - /* Register base classes specified via template arguments to class_, if any */ - bool unused[] = { (add_base(record), false)..., false }; - (void) unused; - - /* Process optional arguments, if any */ - process_attributes::init(extra..., &record); - - generic_type::initialize(record); - - if (has_alias) { - auto &instances = get_internals().registered_types_cpp; - instances[std::type_index(typeid(type_alias))] = instances[std::type_index(typeid(type))]; - } - } - - template ::value, int> = 0> - static void add_base(detail::type_record &rec) { - rec.add_base(&typeid(Base), [](void *src) -> void * { - return static_cast(reinterpret_cast(src)); - }); - } - - template ::value, int> = 0> - static void add_base(detail::type_record &) { } - template - class_ &def(const char *name_, Func&& f, const Extra&... extra) { - cpp_function cf(std::forward(f), name(name_), is_method(*this), - sibling(getattr(*this, name_, none())), extra...); - attr(cf.name()) = cf; - return *this; + cls def(const char *name_, Func&& f, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + cpp_function cf(std::forward(f), name(name_), is_method(self), + sibling(getattr(self, name_, none())), extra...); + self.attr(cf.name()) = cf; + return self; } - template class_ & - def_static(const char *name_, Func &&f, const Extra&... extra) { + template + cls def_static(const char *name_, Func &&f, const Extra&... extra) { static_assert(!std::is_member_function_pointer::value, "def_static(...) called with a non-static member function pointer"); - cpp_function cf(std::forward(f), name(name_), scope(*this), - sibling(getattr(*this, name_, none())), extra...); - attr(cf.name()) = cf; - return *this; + cls self = static_cast(static_cast(*this)); + cpp_function cf(std::forward(f), name(name_), scope(self), + sibling(getattr(self, name_, none())), extra...); + self.attr(cf.name()) = cf; + return self; } - template - class_ &def(const detail::op_ &op, const Extra&... extra) { - op.execute(*this, extra...); - return *this; + template + cls def(const op_ &op, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + op.execute(self, extra...); + return self; } - template - class_ & def_cast(const detail::op_ &op, const Extra&... extra) { - op.execute_cast(*this, extra...); - return *this; + template + cls def_cast(const op_ &op, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + op.execute_cast(self, extra...); + return self; } template - class_ &def(const detail::init &init, const Extra&... extra) { - init.execute(*this, extra...); - return *this; + cls def(const init &init, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + init.execute(self, extra...); + return self; } template - class_ &def(const detail::init_alias &init, const Extra&... extra) { - init.execute(*this, extra...); - return *this; + cls def(const init_alias &init, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + init.execute(self, extra...); + return self; } - template class_& def_buffer(Func &&func) { + template + cls def_buffer(Func &&func) { + cls self = static_cast(static_cast(*this)); struct capture { Func func; }; capture *ptr = new capture { std::forward(func) }; - install_buffer_funcs([](PyObject *obj, void *ptr) -> buffer_info* { + self.install_buffer_funcs([](PyObject *obj, void *ptr) -> buffer_info* { detail::make_caster caster; if (!caster.load(obj, false)) return nullptr; return new buffer_info(((capture *) ptr)->func(caster)); }, ptr); - return *this; + return self; } template - class_ &def_readwrite(const char *name, D C::*pm, const Extra&... extra) { - cpp_function fget([pm](const C &c) -> const D &{ return c.*pm; }, is_method(*this)), - fset([pm](C &c, const D &value) { c.*pm = value; }, is_method(*this)); - def_property(name, fget, fset, return_value_policy::reference_internal, extra...); - return *this; + cls def_readwrite(const char *name, D C::*pm, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + cpp_function fget([pm](const C &c) -> const D &{ return c.*pm; }, is_method(self)), + fset([pm](C &c, const D &value) { c.*pm = value; }, is_method(self)); + self.def_property(name, fget, fset, return_value_policy::reference_internal, extra...); + return self; } template - class_ &def_readonly(const char *name, const D C::*pm, const Extra& ...extra) { - cpp_function fget([pm](const C &c) -> const D &{ return c.*pm; }, is_method(*this)); - def_property_readonly(name, fget, return_value_policy::reference_internal, extra...); - return *this; + cls def_readonly(const char *name, const D C::*pm, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + cpp_function fget([pm](const C &c) -> const D &{ return c.*pm; }, is_method(self)); + self.def_property_readonly(name, fget, return_value_policy::reference_internal, extra...); + return self; } template - class_ &def_readwrite_static(const char *name, D *pm, const Extra& ...extra) { - cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(*this)), - fset([pm](object, const D &value) { *pm = value; }, scope(*this)); - def_property_static(name, fget, fset, return_value_policy::reference, extra...); - return *this; + cls def_readwrite_static(const char *name, D *pm, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(self)), + fset([pm](object, const D &value) { *pm = value; }, scope(self)); + self.def_property_static(name, fget, fset, return_value_policy::reference, extra...); + return self; } template - class_ &def_readonly_static(const char *name, const D *pm, const Extra& ...extra) { - cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(*this)); - def_property_readonly_static(name, fget, return_value_policy::reference, extra...); - return *this; + cls def_readonly_static(const char *name, const D *pm, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(self)); + self.def_property_readonly_static(name, fget, return_value_policy::reference, extra...); + return self; } /// Uses return_value_policy::reference_internal by default template - class_ &def_property_readonly(const char *name, const Getter &fget, const Extra& ...extra) { - return def_property_readonly(name, cpp_function(fget), return_value_policy::reference_internal, extra...); + cls def_property_readonly(const char *name, const Getter &fget, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_readonly(name, cpp_function(fget), return_value_policy::reference_internal, extra...); } /// Uses cpp_function's return_value_policy by default template - class_ &def_property_readonly(const char *name, const cpp_function &fget, const Extra& ...extra) { - return def_property(name, fget, cpp_function(), extra...); + cls def_property_readonly(const char *name, const cpp_function &fget, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property(name, fget, cpp_function(), extra...); } /// Uses return_value_policy::reference by default template - class_ &def_property_readonly_static(const char *name, const Getter &fget, const Extra& ...extra) { - return def_property_readonly_static(name, cpp_function(fget), return_value_policy::reference, extra...); + cls def_property_readonly_static(const char *name, const Getter &fget, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_readonly_static(name, cpp_function(fget), return_value_policy::reference, extra...); } /// Uses cpp_function's return_value_policy by default template - class_ &def_property_readonly_static(const char *name, const cpp_function &fget, const Extra& ...extra) { - return def_property_static(name, fget, cpp_function(), extra...); + cls def_property_readonly_static(const char *name, const cpp_function &fget, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_static(name, fget, cpp_function(), extra...); } /// Uses return_value_policy::reference_internal by default template - class_ &def_property(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property(name, cpp_function(fget), fset, return_value_policy::reference_internal, extra...); + cls def_property(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property(name, cpp_function(fget), fset, return_value_policy::reference_internal, extra...); } /// Uses cpp_function's return_value_policy by default template - class_ &def_property(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property_static(name, fget, fset, is_method(*this), extra...); + cls def_property(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_static(name, fget, fset, is_method(self), extra...); } /// Uses return_value_policy::reference by default template - class_ &def_property_static(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property_static(name, cpp_function(fget), fset, return_value_policy::reference, extra...); + cls def_property_static(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_static(name, cpp_function(fget), fset, return_value_policy::reference, extra...); } /// Uses cpp_function's return_value_policy by default template - class_ &def_property_static(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { - auto rec_fget = get_function_record(fget), rec_fset = get_function_record(fset); + cls def_property_static(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + auto rec_fget = self.get_function_record(fget), rec_fset = self.get_function_record(fset); char *doc_prev = rec_fget->doc; /* 'extra' field may include a property-specific documentation string */ - detail::process_attributes::init(extra..., rec_fget); + process_attributes::init(extra..., rec_fget); if (rec_fget->doc && rec_fget->doc != doc_prev) { free(doc_prev); rec_fget->doc = strdup(rec_fget->doc); } if (rec_fset) { doc_prev = rec_fset->doc; - detail::process_attributes::init(extra..., rec_fset); + process_attributes::init(extra..., rec_fset); if (rec_fset->doc && rec_fset->doc != doc_prev) { free(doc_prev); rec_fset->doc = strdup(rec_fset->doc); } } - def_property_static_impl(name, fget, fset, rec_fget); - return *this; + self.def_property_static_impl(name, fget, fset, rec_fget); + return self; } +}; + +NAMESPACE_END(detail) + +template +class class_ : public detail::generic_type, + public detail::class_interface, type_, options...> { + template using is_holder = detail::is_holder_type; + template using is_subtype = detail::bool_constant::value && !std::is_same::value>; + template using is_base = detail::bool_constant::value && !std::is_same::value>; + // struct instead of using here to help MSVC: + template struct is_valid_class_option : + detail::any_of, is_subtype, is_base> {}; + + friend detail::class_interface; + +public: + using type = type_; + using type_alias = detail::exactly_one_t; + constexpr static bool has_alias = !std::is_void::value; + using holder_type = detail::exactly_one_t, options...>; + using instance_type = detail::instance; + + static_assert(detail::all_of...>::value, + "Unknown/invalid class_ template parameters provided"); + + PYBIND11_OBJECT(class_, generic_type, PyType_Check) + + template + class_(handle scope, const char *name, const Extra &... extra) { + using namespace detail; + + // MI can only be specified via class_ template options, not constructor parameters + static_assert( + none_of...>::value || // no base class arguments, or: + ( constexpr_sum(is_pyobject::value...) == 1 && // Exactly one base + constexpr_sum(is_base::value...) == 0 && // no template option bases + none_of...>::value), // no multiple_inheritance attr + "Error: multiple inheritance bases must be specified via class_ template options"); + + type_record record; + record.scope = scope; + record.name = name; + record.type = &typeid(type); + record.type_size = sizeof(conditional_t); + record.instance_size = sizeof(instance_type); + record.init_holder = init_holder; + record.dealloc = dealloc; + record.default_holder = std::is_same>::value; + + set_operator_new(&record); + + /* Register base classes specified via template arguments to class_, if any */ + bool unused[] = { (add_base(record), false)..., false }; + (void) unused; + + /* Process optional arguments, if any */ + process_attributes::init(extra..., &record); + + generic_type::initialize(record); + + if (has_alias) { + auto &instances = get_internals().registered_types_cpp; + instances[std::type_index(typeid(type_alias))] = instances[std::type_index(typeid(type))]; + } + } + + template ::value, int> = 0> + static void add_base(detail::type_record &rec) { + rec.add_base(&typeid(Base), [](void *src) -> void * { + return static_cast(reinterpret_cast(src)); + }); + } + + template ::value, int> = 0> + static void add_base(detail::type_record &) { } private: /// Initialize holder object, variant 1: object derives from enable_shared_from_this @@ -1257,6 +1292,79 @@ template class enum_ : public class_ { handle m_parent; }; +template +class py3_enum : public detail::class_interface, T> { +public: + using underlying_type = typename std::underlying_type::type; + + py3_enum(handle scope, const char* name) + : name(name), + scope(scope), + ctor(module::import("enum").attr("IntEnum")), + unique(module::import("enum").attr("unique")) { + kwargs["value"] = cast(name); + kwargs["names"] = entries; + if (scope) { + if (hasattr(scope, "__module__")) + kwargs["module"] = scope.attr("__module__"); + else if (hasattr(scope, "__name__")) + kwargs["module"] = scope.attr("__name__"); +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 + if (hasattr(scope, "__qualname__")) + kwargs["qualname"] = scope.attr("__qualname__").cast() + "." + name; +#endif + } + update(); + } + + /// Add an enumeration entry + py3_enum& value(const char* name, T value) { + add_entry(name, value); + return *this; + } + + /// Export enumeration entries into the parent scope + py3_enum& export_values() { + for (const auto &kv : entries) + scope.attr(kv.first) = type.attr(kv.first); + reexport = true; + return *this; + } + +private: + const char *name; + handle scope; + dict entries; + object ctor; + object unique; + dict kwargs; + object type; + bool locked = false; + bool reexport = false; + + void add_entry(const char *name, T value) { + if (locked) + throw std::runtime_error("Unable to modify a locked enum class"); + entries[name] = cast(static_cast(value)); + update(); + } + + void update() { + type = unique(ctor(**kwargs)); + setattr(scope, name, type); + if (reexport) + export_values(); + detail::type_caster::bind(type, entries); + } + + operator class_() { + locked = true; + return cast>(type); + } + + friend detail::class_interface, T>; +}; + NAMESPACE_BEGIN(detail) template struct init { template = 0> diff --git a/tests/conftest.py b/tests/conftest.py index 5b08004e3f..ca00837e98 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -200,6 +200,7 @@ def pytest_namespace(): except ImportError: have_eigen = False pypy = platform.python_implementation() == "PyPy" + py3 = sys.version_info.major == 3 skipif = pytest.mark.skipif return { @@ -211,7 +212,8 @@ def pytest_namespace(): 'requires_eigen_and_scipy': skipif(not have_eigen or not scipy, reason="eigen and/or scipy are not installed"), 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"), - 'gc_collect': gc_collect + 'gc_collect': gc_collect, + 'requires_py3': skipif(not py3, reason='requires Python 3') } diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index 67341f4b08..459ee2b015 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -37,6 +37,25 @@ class ClassWithUnscopedEnum { } }; +enum Py3Enum { + A = -42, + B = 1, + C = 42, +}; + +enum class Py3EnumScoped : short { + X = 10, + Y = -1024, +}; + +enum class Py3EnumEmpty {}; + +enum class Py3EnumNonUnique { + X = 1 +}; + +class DummyScope {}; + std::string test_scoped_enum(ScopedEnum z) { return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three"); } @@ -59,6 +78,39 @@ test_initializer enums([](py::module &m) { .value("Execute", Flags::Execute) .export_values(); +#if PY_VERSION_HEX >= 0x03000000 + py::py3_enum(m, "Py3EnumEmpty"); + + auto e = py::py3_enum(m, "Py3Enum") + .value("A", Py3Enum::A) + .value("B", Py3Enum::B) + .value("C", Py3Enum::C) + .def("add", [](Py3Enum x, int y) { return static_cast(x) + y; }) + .def_property_readonly("is_b", [](Py3Enum e) { return e == Py3Enum::B; }) + .def_property_readonly_static("ultimate_answer", [](py::object) { return 42; }); + + auto scope = py::class_(m, "DummyScope"); + + py::py3_enum(scope, "Py3EnumScoped") + .value("X", Py3EnumScoped::X) + .export_values() + .value("Y", Py3EnumScoped::Y); + + m.def("make_py3_enum", [](bool x) { + return x ? Py3EnumScoped::X : Py3EnumScoped::Y; + }); + + m.def("take_py3_enum", [](Py3EnumScoped x) { + return x == Py3EnumScoped::X; + }); + + m.def("non_unique_py3_enum", [=]() { + py::py3_enum(m, "Py3EnumNonUnique") + .value("X", Py3EnumNonUnique::X) + .value("Y", Py3EnumNonUnique::X); + }); +#endif + py::class_ exenum_class(m, "ClassWithUnscopedEnum"); exenum_class.def_static("test_function", &ClassWithUnscopedEnum::test_function); py::enum_(exenum_class, "EMode") diff --git a/tests/test_enum.py b/tests/test_enum.py index 6cc4887c5c..c0a6ee9327 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -127,3 +127,47 @@ def test_enum_to_int(): test_enum_to_uint(ClassWithUnscopedEnum.EMode.EFirstMode) test_enum_to_long_long(Flags.Read) test_enum_to_long_long(ClassWithUnscopedEnum.EMode.EFirstMode) + + +@pytest.requires_py3 +def test_py3_enum(): + from pybind11_tests import ( + Py3Enum, DummyScope, Py3EnumEmpty, + make_py3_enum, take_py3_enum, non_unique_py3_enum + ) + + Py3EnumScoped = DummyScope.Py3EnumScoped # noqa + + from enum import IntEnum + + expected = { + Py3Enum: [('A', -42), ('B', 1), ('C', 42)], + Py3EnumEmpty: [], + Py3EnumScoped: [('X', 10), ('Y', -1024)] + } + + for tp, entries in expected.items(): + assert issubclass(tp, IntEnum) + assert sorted(tp.__members__.items()) == entries + assert tp.__module__ == 'pybind11_tests' + + assert DummyScope.X is Py3EnumScoped.X + assert DummyScope.Y is Py3EnumScoped.Y + + assert Py3Enum.__qualname__ == 'Py3Enum' + assert Py3EnumEmpty.__qualname__ == 'Py3EnumEmpty' + assert Py3EnumScoped.__qualname__ == 'DummyScope.Py3EnumScoped' + + assert make_py3_enum(True) is Py3EnumScoped.X + assert make_py3_enum(False) is Py3EnumScoped.Y + + assert take_py3_enum(Py3EnumScoped.X) + assert not take_py3_enum(Py3EnumScoped.Y) + + with pytest.raises(ValueError) as excinfo: + non_unique_py3_enum() + assert 'duplicate values found' in str(excinfo.value) + + assert Py3Enum.ultimate_answer == 42 + assert not Py3Enum.A.is_b and Py3Enum.B.is_b and not Py3Enum.C.is_b + assert Py3Enum.A.add(10) == -32 and Py3Enum.C.add(-1) == 41