File: Wrap.h

package info (click to toggle)
rdkit 202503.1-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 220,160 kB
  • sloc: cpp: 399,240; python: 77,453; ansic: 25,517; java: 8,173; javascript: 4,005; sql: 2,389; yacc: 1,565; lex: 1,263; cs: 1,081; makefile: 580; xml: 229; fortran: 183; sh: 105
file content (374 lines) | stat: -rw-r--r-- 12,884 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
//
// Copyright (c) 2003-2008 greg Landrum and Rational Discovery LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#include <RDGeneral/export.h>
#ifndef _RD_WRAP_H_
#define _RD_WRAP_H_

#include <RDGeneral/Invariant.h>

#include <RDGeneral/BoostStartInclude.h>
#include <Numerics/Vector.h>
//
// Generic Wrapper utility functionality
//
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/dynamic_bitset.hpp>
#include <memory>

#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <cstdint>
#include "list_indexing_suite.hpp"
#include <RDGeneral/BoostEndInclude.h>

#include <vector>
#include <list>
#include <iostream>
#include <RDGeneral/Exceptions.h>

namespace python = boost::python;

RDKIT_RDBOOST_EXPORT void throw_index_error(
    int key);  //!< construct and throw an \c IndexError
RDKIT_RDBOOST_EXPORT void throw_value_error(
    const std::string err);  //!< construct and throw a \c ValueError
RDKIT_RDBOOST_EXPORT void throw_key_error(
    const std::string key);  //!< construct and throw a \c KeyError
RDKIT_RDBOOST_EXPORT void translate_index_error(IndexErrorException const &e);
RDKIT_RDBOOST_EXPORT void translate_value_error(ValueErrorException const &e);
RDKIT_RDBOOST_EXPORT void translate_key_error(KeyErrorException const &e);

#ifdef INVARIANT_EXCEPTION_METHOD
RDKIT_RDBOOST_EXPORT void throw_runtime_error(
    const std::string err);  //!< construct and throw a \c ValueError
RDKIT_RDBOOST_EXPORT void translate_invariant_error(Invar::Invariant const &e);
#endif

//! \brief Checks whether there is already a registered Python converter for a
//!        particular type.
//! In order to avoid warning about duplicated converters, this should be used
//! before attempting to register a converter for any general type that might
//! be used in more than one Python wrapper.
template <typename T>
bool is_python_converter_registered() {
  // logic from https://stackoverflow.com/a/13017303
  auto info = python::type_id<T>();
  const auto *reg = python::converter::registry::query(info);
  return reg != nullptr && reg->m_to_python != nullptr;
}

//! \brief Registers a templated converter for returning \c vectors of a
//!        particular type.
//! This should be used instead of calling \c vector_to_python<T>()
//!    directly because this will catch the appropriate exception if
//!    the specified converter has already been registered.
template <typename T>
void RegisterVectorConverter(const char *name, bool noproxy = false) {
  if (is_python_converter_registered<std::vector<T>>()) {
    return;
  }
  if (noproxy) {
    python::class_<std::vector<T>>(name).def(
        python::vector_indexing_suite<std::vector<T>, 1>());
  } else {
    python::class_<std::vector<T>>(name).def(
        python::vector_indexing_suite<std::vector<T>>());
  }
}

template <typename T>
void RegisterVectorConverter(bool noproxy = false) {
  std::string name = "_vect";
  name += typeid(T).name();
  RegisterVectorConverter<T>(name.c_str(), noproxy);
}

//! \brief Registers a templated converter for returning \c lists of a
//!        particular type.
//! This should be used instead of calling \c list_to_python<T>()
//!    directly because this will catch the appropriate exception if
//!    the specified converter has already been registered.
template <typename T>
void RegisterListConverter(bool noproxy = false) {
  if (is_python_converter_registered<std::list<T>>()) {
    return;
  }
  std::string name = "_list";
  name += typeid(T).name();

  if (noproxy) {
    python::class_<std::list<T>>(name.c_str())
        .def(python::list_indexing_suite<std::list<T>, 1>());
  } else {
    python::class_<std::list<T>>(name.c_str())
        .def(python::list_indexing_suite<std::list<T>>());
  }
}

//! NOTE: this returns a nullptr if obj is None or empty
template <typename T>
std::unique_ptr<std::vector<T>> pythonObjectToVect(const python::object &obj,
                                                   T maxV) {
  std::unique_ptr<std::vector<T>> res;
  if (obj) {
    res.reset(new std::vector<T>);

    auto check_max = [&maxV](const T &v) {
      if (v >= maxV) {
        throw_value_error("list element larger than allowed value");
      }
      return true;
    };
    std::copy_if(python::stl_input_iterator<T>(obj),
                 python::stl_input_iterator<T>(), std::back_inserter(*res),
                 check_max);
  }
  return res;
}
//! NOTE: this returns a nullptr if obj is None or empty
template <typename T>
std::unique_ptr<std::vector<T>> pythonObjectToVect(const python::object &obj) {
  std::unique_ptr<std::vector<T>> res;
  if (obj) {
    res.reset(new std::vector<T>(python::stl_input_iterator<T>(obj),
                                 python::stl_input_iterator<T>()));
  }
  return res;
}
//! NOTE: \c res will be cleared if obj is None or empty
template <typename T>
void pythonObjectToVect(const python::object &obj, std::vector<T> &res) {
  if (obj) {
    res.assign(python::stl_input_iterator<T>(obj),
               python::stl_input_iterator<T>());
  } else {
    res.clear();
  }
}

RDKIT_RDBOOST_EXPORT boost::dynamic_bitset<> pythonObjectToDynBitset(
    const python::object &obj, boost::dynamic_bitset<>::size_type maxV);

RDKIT_RDBOOST_EXPORT std::vector<std::pair<int, int>> *translateAtomMap(
    const python::object &atomMap);
RDKIT_RDBOOST_EXPORT std::vector<std::vector<std::pair<int, int>>>
translateAtomMapSeq(const python::object &atomMapSeq);
RDKIT_RDBOOST_EXPORT RDNumeric::DoubleVector *translateDoubleSeq(
    const python::object &doubleSeq);
RDKIT_RDBOOST_EXPORT std::vector<unsigned int> *translateIntSeq(
    const python::object &intSeq);

// Quiet warnings on GCC
#if defined(__GNUC__) || defined(__GNUG__)
#define RDUNUSED __attribute__((__unused__))
#else
#define RDUNUSED
#endif

class PyGILStateHolder {
 public:
  PyGILStateHolder() : d_gstate(PyGILState_Ensure()) {}
  ~PyGILStateHolder() { PyGILState_Release(d_gstate); }

 private:
  PyGILState_STATE d_gstate;
};

#ifdef RDK_BUILD_THREADSAFE_SSS
// Release the Global Interpreter lock at certain places
//  on construction - release the lock
//  on destruction - grab the lock
//  no entry into the python interpreter can be performed
//   between releasing and grabbing the lock
class RDKIT_RDBOOST_EXPORT RDUNUSED NOGIL {
 public:
  inline NOGIL() { m_thread_state = PyEval_SaveThread(); }

  inline ~NOGIL() {
    PyEval_RestoreThread(m_thread_state);
    m_thread_state = nullptr;
  }

 private:
  PyThreadState *m_thread_state;
};
#else
// Never release the lock when not compiling thread-safe
struct RDUNUSED NOGIL {};
#endif

// -------------------
// This block was adapted from this mailing list post by Matthew Scouten:
// https://mail.python.org/pipermail/cplusplus-sig/2009-May/014505.html
// Matthew credits Hans Meine in his post.
template <class T>
inline PyObject *managingPyObject(T *p) {
  return typename python::manage_new_object::apply<T *>::type()(p);
}

template <class Copyable>
python::object generic__copy__(python::object copyable) {
  Copyable *newCopyable(
      new Copyable(python::extract<const Copyable &>(copyable)));
  python::object result(
      python::detail::new_reference(managingPyObject(newCopyable)));

  python::extract<python::dict>(result.attr("__dict__"))().update(
      copyable.attr("__dict__"));

  return result;
}

template <class Copyable>
python::object generic__deepcopy__(python::object copyable, python::dict memo) {
  python::object copyMod = python::import("copy");
  python::object deepcopy = copyMod.attr("deepcopy");

  Copyable *newCopyable(
      new Copyable(python::extract<const Copyable &>(copyable)));
  python::object result(
      python::detail::new_reference(managingPyObject(newCopyable)));

  // HACK: copyableId shall be the same as the result of id(copyable) in Python
  // -
  // please tell me that there is a better way! (and which ;-p)
  size_t copyableId = (size_t)(copyable.ptr());
  memo[copyableId] = result;

  python::extract<python::dict>(result.attr("__dict__"))().update(deepcopy(
      python::extract<python::dict>(copyable.attr("__dict__"))(), memo));
  return result;
}
// -------------------

/// Awesome StackOverflow response:
/// http://stackoverflow.com/questions/15842126/feeding-a-python-list-into-a-function-taking-in-a-vector-with-boost-python
/// I know a lot more about how boost works.
/// @brief Type that allows for registration of conversions from
///        python iterable types.
struct iterable_converter {
  /// @note Registers converter from a python iterable type to the
  ///       provided type.
  template <typename Container>
  iterable_converter &from_python() {
    python::converter::registry::push_back(
        &iterable_converter::convertible,
        &iterable_converter::construct<Container>,
        python::type_id<Container>());

    // Support chaining.
    return *this;
  }

  /// @brief Check if PyObject is iterable.
  static void *convertible(PyObject *object) {
    return PyObject_GetIter(object) ? object : nullptr;
  }

  /// @brief Convert iterable PyObject to C++ container type.
  ///
  /// Container Concept requirements:
  ///
  ///   * Container::value_type is CopyConstructable.
  ///   * Container can be constructed and populated with two iterators.
  ///     I.e. Container(begin, end)
  template <typename Container>
  static void construct(
      PyObject *object,
      python::converter::rvalue_from_python_stage1_data *data) {
    namespace python = boost::python;
    // Object is a borrowed reference, so create a handle indicting it is
    // borrowed for proper reference counting.
    python::handle<> handle(python::borrowed(object));

    // Obtain a handle to the memory block that the converter has allocated
    // for the C++ type.
    typedef python::converter::rvalue_from_python_storage<Container>
        storage_type;
    void *storage = reinterpret_cast<storage_type *>(data)->storage.bytes;

    typedef python::stl_input_iterator<typename Container::value_type> iterator;

    // Allocate the C++ type into the converter's memory block, and assign
    // its handle to the converter's convertible variable.  The C++
    // container is populated by passing the begin and end iterators of
    // the python object to the container's constructor.
    new (storage) Container(iterator(python::object(handle)),  // begin
                            iterator());                       // end
    data->convertible = storage;
  }
};

/// Simple Boost.Python custom converter from pathlib.Path to std::string
template <typename T = std::string>
struct path_converter {
  path_converter() {
    python::converter::registry::push_back(&path_converter::convertible,
                                           &path_converter::construct,
                                           boost::python::type_id<T>());
  }

  /// Check PyObject is a pathlib.Path
  static void *convertible(PyObject *object) {
    // paranoia
    if (object == nullptr) {
      return nullptr;
    }
    python::object boost_object(python::handle<>(python::borrowed(object)));

    std::string object_classname = boost::python::extract<std::string>(
        boost_object.attr("__class__").attr("__name__"));
    // pathlib.Path is always specialized to the below derived classes
    if (object_classname == "WindowsPath" || object_classname == "PosixPath") {
      return object;
    }

    return nullptr;
  }

  /// Construct a std::string from pathlib.Path using its own __str__ attribute
  static void construct(
      PyObject *object,
      boost::python::converter::rvalue_from_python_stage1_data *data) {
    void *storage =
        ((boost::python::converter::rvalue_from_python_storage<T> *)data)
            ->storage.bytes;
    python::object boost_object{python::handle<>{python::borrowed(object)}};
    new (storage)
        T{boost::python::extract<std::string>{boost_object.attr("__str__")()}};
    data->convertible = storage;
  }
};

//
// allows rdkit objects to be pickled.
struct rdkit_pickle_suite : python::pickle_suite {
  static python::tuple getstate(python::object w_obj) {
    return python::make_tuple(w_obj.attr("__dict__"));
  }

  static void setstate(python::object w_obj, python::tuple state) {
    if (len(state) != 1) {
      PyErr_SetObject(
          PyExc_ValueError,
          ("expected 1-item tuple in call to __setstate__; got %s" % state)
              .ptr());
      python::throw_error_already_set();
    }

    // restore the object's __dict__
    python::dict d = python::extract<python::dict>(w_obj.attr("__dict__"))();
    d.update(state[0]);
  }

  static bool getstate_manages_dict() { return true; }
};

#endif