Apply re-formatting to C++ in src/ tree.
[alexxy/gromacs.git] / python_packaging / src / gmxapi / export_exceptions.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2019,2020, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \file
36  * \brief Exception translation.
37  *
38  * Note that C++ exception objects are not actually bound to Python objects
39  * because C++ cannot give ownership of a thrown exception to the interpreter.
40  * Instead, we catch C++ exceptions and translate them into Python exceptions.
41  *
42  * As of pybind11 2.2, pybind does not provide a way to automatically express
43  * C++ exception class inheritance to Python, and only supports simple Python
44  * exceptions initialized with strings at the time of translation. (Automatic
45  * translation uses `std::exception::what()`)
46  *
47  * Currently, we restrict ourselves to this simple translation. Future versions
48  * may use the Python C API directly to support richer exception classes.
49  *
50  * \todo Determine inheritance relationship or equality between
51  * gmxapi._gmxapi.Exception and gmxapi.exceptions.Error, if any.
52  *
53  * \author M. Eric Irrgang <ericirrgang@gmail.com>
54  *
55  * \ingroup module_python
56  */
57 #include "gmxapi/exceptions.h"
58
59 #include "module.h"
60
61
62 namespace gmxpy
63 {
64
65 namespace detail
66 {
67
68 namespace py = pybind11;
69
70
71 void export_exceptions(pybind11::module& m)
72 {
73     // These two lines could cause exceptions, but they are already handled,
74     // causing an ImportError for the _gmxapi submodule raised from the
75     // ImportError or AttributeError that caused the failure.
76     // If we find that this is too cryptic or that there are too many places
77     // that import could fail, we can use the Python C API directly
78     // (in the module.cpp code block preceded by the PYBIND11 macro) to set a
79     // new PyExc_ImportError manually and return a nullptr immediately.
80     const auto packageModule           = py::module::import("gmxapi");
81     const auto gmxapi_exceptions_error = packageModule.attr("exceptions").attr("Error");
82
83     static py::exception<gmxapi::Exception> baseException(m, "Exception", gmxapi_exceptions_error.ptr());
84
85     // Developer note: as of pybind11 2.2.4, the py::exception template argument
86     // is unused internally, but required.
87     struct UnknownExceptionPlaceHolder
88     {
89     };
90     static py::exception<UnknownExceptionPlaceHolder> unknownException(
91             m, "UnknownException", baseException.ptr());
92     unknownException.doc() =
93             "GROMACS library produced an exception that is "
94             "not mapped in gmxapi or which should have been "
95             "caught at a lower level. I.e. a bug. (Please report.)";
96
97     // Catch unexpected/unbound exceptions from libgromacs or libgmxapi.
98     py::register_exception_translator([](std::exception_ptr p) {
99         try
100         {
101             if (p)
102             {
103                 std::rethrow_exception(p);
104             }
105         }
106         catch (const gmxapi::Exception& e)
107         {
108             // Nothing should be throwing the base exception and all gmxapi
109             // exceptions should be mapped in this module. Differences
110             // between GROMACS version and Python package version could leave
111             // some exceptions unmapped, but we should add an alert in case
112             // an exception gets overlooked.
113             std::string message = "Generic gmxapi exception caught: ";
114             message += e.what();
115             baseException(message.c_str());
116         }
117         catch (const std::exception& e)
118         {
119             std::string message = "Please report GROMACS bug. Unhandled C++ exception: ";
120             message += e.what();
121             unknownException(message.c_str());
122         }
123     });
124
125     // Map gmxapi exceptions from gmxapi/exceptions.h to Python package exceptions.
126     // Note: C++ exception translation occurs in revers order of registration,
127     // So derived exceptions must be registered after their base exceptions.
128     // TODO: We could have more informative exception class docstrings
129     //   by linking to online docs or if we had a way to reuse doxygen docs.
130
131     {
132         auto exception =
133                 py::register_exception<gmxapi::ProtocolError>(m, "ProtocolError", baseException.ptr());
134         exception.doc() = "Behavioral protocol violated.";
135     }
136
137     {
138         auto exception = py::register_exception<gmxapi::NotImplementedError>(
139                 m, "NotImplementedError", baseException.ptr());
140         exception.doc() = "Expected feature is not implemented.";
141     }
142
143     {
144         auto exception =
145                 py::register_exception<gmxapi::UsageError>(m, "UsageError", baseException.ptr());
146         exception.doc() = "Unacceptable API usage.";
147     }
148 }
149
150
151 } // namespace detail
152
153 } // end namespace gmxpy