wrap boost::optional using boost::python
Question: Is there a way to wrap `boost::optional<T>` type object to expose it via
`boost::python::class_` (used from `BOOST_PYTHON_MODULE`)

    
    
    struct Foo 
    {
        boost::optional<int> bar;
    };
    
    BOOST_PYTHON_MODULE(module_name)
    {
        class_<Foo>("Foo")
        .def_readwrite("bar", &Foo::bar);
    }
    

What I expect in Python is AttributeError in this case

    
    
    import module_name
    f = module_name.Foo()
    print f.bar
    

as the value of `bar` hasn't been set yet. And `TypeError` when

    
    
    import module_name
    f = module_name.Foo()
    f.bar = "string"
    

`bar` is of `int` type.

The other related problem is to export, in the same fashion, classes' objects
of `boost::python::indexing_suite` container types.

Is the problem solvable using `boost::python` api?


Answer: You need an [exception
translator](http://www.boost.org/doc/libs/1_48_0/libs/python/doc/v2/exception_translator.html)
and [python converters](https://misspent.wordpress.com/2009/09/27/how-to-
write-boost-python-converters/).

**Exception translator**

    
    
    namespace bp = boost::python;
    
    // Custom exceptions
    struct AttributeError: std::exception
    {
      const char* what() const throw() { return "AttributeError exception"; }
    };
    
    struct TypeError: std::exception
    {
      const char* what() const throw() { return "TypeError exception"; }
    };
    
    // Set python exceptions
    void translate(const std::exception& e)
    {
      if(dynamic_cast<const AttributeError*>(&e)) 
         PyErr_SetString(PyExc_AttributeError, e.what());
      if(dynamic_cast<const TypeError*>(&e)) 
         PyErr_SetString(PyExc_TypeError, e.what());
    }
    
    BOOST_PYTHON_MODULE(module_name)
    {
      // Exception translator
      bp::register_exception_translator<AttributeError>(&translate);
      bp::register_exception_translator<TypeError>(&translate);
      ...
    }
    

**To-python converter**

    
    
    template <typename T>
    struct to_python_optional
    {
      static PyObject* convert(const boost::optional<T>& obj)
      {
        if(obj) return bp::incref(bp::object(*obj).ptr());
        // raise AttributeError if any value hasn't been set yet
        else throw AttributeError();
      }
    };
    
    BOOST_PYTHON_MODULE(module_name)
    {
      ... 
      bp::to_python_converter<boost::optional<int>,
                              to_python_optional<int> >();
      ...
    }
    

**From-python converter**

    
    
    template<typename T>
    struct from_python_optional
    { 
      static void* convertible(PyObject *obj_ptr)
        {
          try { return typename bp::extract<T>::extract(obj_ptr) ? obj_ptr : 0 ; }
          // Without try catch it still raises a TypeError exception
          // But this enables to custom your error message
          catch(...) { throw TypeError(); }
        }
    
      static void construct(
        PyObject *obj_ptr,
        boost::python::converter::rvalue_from_python_stage1_data* data)
        {
          const T value = typename bp::extract<T>::extract(obj_ptr);
    
          assert(value);
    
          void* storage = (
            (bp::converter::rvalue_from_python_storage<boost::optional<T> >*)
            data)->storage.bytes;
    
          new (storage) boost::optional<T>(value);
    
          data->convertible = storage;
        }
    
      from_python_optional()
      {
        bp::converter::registry::push_back(
          &convertible,
          &construct,
          bp::type_id<boost::optional<T> >());
      }
    };
    
    BOOST_PYTHON_MODULE(module_name)
    {
      ... 
      from_python_optional<int>();
      ...
    }
    

Moreover you can't use converters with `def_readwrite` [(see this
FAQ)](http://www.boost.org/doc/libs/1_43_0/libs/python/doc/v2/faq.html#topythonconversionfailed),
you have to use `add_property`.

    
    
    BOOST_PYTHON_MODULE(module_name)
    {
      ...
      bp::class_<Foo>("Foo")
        .add_property("bar", bp::make_getter(&Foo::bar,
                             bp::return_value_policy<bp::return_by_value>()),
                             bp::make_setter(&Foo::bar,
                             bp::return_value_policy<bp::return_by_value>()));
    }
    

Thus you'll get those outputs in your Python interpreter :

    
    
    >>> import module_name
    >>> f = module_name.Foo()
    >>> print f.bar
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: AttributeError exception
    >>> f.bar="string"
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: TypeError exception
    

