Apache Mesos
common.hpp
Go to the documentation of this file.
1 // Licensed to the Apache Software Foundation (ASF) under one
2 // or more contributor license agreements. See the NOTICE file
3 // distributed with this work for additional information
4 // regarding copyright ownership. The ASF licenses this file
5 // to you under the Apache License, Version 2.0 (the
6 // "License"); you may not use this file except in compliance
7 // with the License. You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 
17 #ifndef MESOS_NATIVE_COMMON_HPP
18 #define MESOS_NATIVE_COMMON_HPP
19 
20 // Python.h must be included before standard headers.
21 // See: http://docs.python.org/2/c-api/intro.html#include-files
22 #include <Python.h>
23 
24 #include <iostream>
25 #include <memory>
26 #include <string>
27 #include <vector>
28 
29 #include <google/protobuf/io/zero_copy_stream_impl.h>
30 #include <google/protobuf/message.h>
31 
32 
33 namespace mesos { namespace python {
34 
39 extern PyObject* mesos_pb2;
40 
41 
46  PyGILState_STATE state;
47 
48 public:
50  state = PyGILState_Ensure();
51  }
52 
54  PyGILState_Release(state);
55  }
56 };
57 
58 
64 template <typename T>
65 bool readPythonProtobuf(PyObject* obj, T* t)
66 {
67  if (obj == Py_None) {
68  std::cerr << "None object given where protobuf expected" << std::endl;
69  return false;
70  }
71  PyObject* res = PyObject_CallMethod(obj,
72  (char*) "SerializeToString",
73  (char*) nullptr);
74  if (res == nullptr) {
75  std::cerr << "Failed to call Python object's SerializeToString "
76  << "(perhaps it is not a protobuf?)" << std::endl;
77  PyErr_Print();
78  return false;
79  }
80  char* chars;
81  Py_ssize_t len;
82  if (PyString_AsStringAndSize(res, &chars, &len) < 0) {
83  std::cerr << "SerializeToString did not return a string" << std::endl;
84  PyErr_Print();
85  Py_DECREF(res);
86  return false;
87  }
88  google::protobuf::io::ArrayInputStream stream(chars, len);
89  bool success = t->ParseFromZeroCopyStream(&stream);
90  if (!success) {
91  std::cerr << "Could not deserialize protobuf as expected type" << std::endl;
92  }
93  Py_DECREF(res);
94  return success;
95 }
96 
97 
104 template <typename T>
105 PyObject* createPythonProtobuf(const T& t, const char* typeName)
106 {
107  PyObject* dict = PyModule_GetDict(mesos_pb2);
108  if (dict == nullptr) {
109  PyErr_Format(PyExc_Exception, "PyModule_GetDict failed");
110  return nullptr;
111  }
112 
113  PyObject* type = PyDict_GetItemString(dict, typeName);
114  if (type == nullptr) {
115  PyErr_Format(PyExc_Exception, "Could not resolve mesos_pb2.%s", typeName);
116  return nullptr;
117  }
118  if (!PyType_Check(type)) {
119  PyErr_Format(PyExc_Exception, "mesos_pb2.%s is not a type", typeName);
120  return nullptr;
121  }
122 
123  std::string str;
124  if (!t.SerializeToString(&str)) {
125  PyErr_Format(PyExc_Exception, "C++ %s SerializeToString failed", typeName);
126  return nullptr;
127  }
128 
129  // Propagates any exception that might happen in FromString.
130  return PyObject_CallMethod(type,
131  (char*) "FromString",
132  (char*) "s#",
133  str.data(),
134  str.size());
135 }
136 
137 
138 // construct<T>() should take PyObject* as an argument, try to convert that
139 // PyObject into an object of type T and return an object of type equivalent
140 // to std::unique_ptr<T> that should hold
141 // - the conversion result on success
142 // - nullptr on failure
143 // Also, the Python exception should be set on conversion failure.
144 //
145 // TODO(asekretenko): use std::optional or stout Try instead of
146 // std::unique_ptr when they become available in this code.
147 //
148 // Declaration of 'construct<T>()' for protobufs.
149 template <typename T>
150 typename std::enable_if<
151  std::is_base_of<google::protobuf::Message, T>::value,
152  std::unique_ptr<T>>::type
153 construct(PyObject* obj)
154 {
155  std::unique_ptr<T> result(new T());
156  if (!readPythonProtobuf(obj, result.get())) {
157  PyErr_Format(
158  PyExc_TypeError,
159  "Failed to construct %s from a Python object",
160  result->GetDescriptor()->full_name().c_str());
161 
162  return nullptr;
163  }
164 
165  return result;
166 }
167 
168 
169 // Declaration of 'construct<T>()' for non-protobufs.
170 template <typename T>
171 typename std::enable_if<
172  !std::is_base_of<google::protobuf::Message, T>::value,
173  std::unique_ptr<T>>::type
174 construct(PyObject* obj);
175 
176 
177 // TODO(asekretenko): move this specialization into .cpp file. That file will
178 // likely have to be put into a library (there is no simple way to use one
179 // source file in two python extensions that can be built concurrently).
180 template <>
181 inline std::unique_ptr<std::string> construct<std::string>(PyObject* obj)
182 {
183  char* chars;
184  Py_ssize_t len;
185  if (PyString_AsStringAndSize(obj, &chars, &len) < 0) {
186  PyErr_Print();
187  PyErr_Format(
188  PyExc_TypeError,
189  "Cannot construct std::string from a non-string object");
190 
191  return nullptr;
192  }
193 
194  return std::unique_ptr<std::string>(new std::string(chars, len));
195 }
196 
197 
198 template <typename T>
199 std::unique_ptr<std::vector<T>> constructFromIterable(PyObject* iterable)
200 {
201  PyObject* pyIterator = PyObject_GetIter(iterable);
202  if (pyIterator == nullptr) {
203  PyErr_Format(
204  PyExc_TypeError,
205  "Cannot construct std::vector from a non-iterable object");
206  return nullptr;
207  }
208 
209  std::unique_ptr<std::vector<T>> result(new std::vector<T>());
210 
211  PyObject* pyItem;
212 
213  while ((pyItem = PyIter_Next(pyIterator)) != nullptr) {
214  std::unique_ptr<T> item = construct<T>(pyItem);
215  if (!item) {
216  // An exception has already been set by construct<>().
217  Py_DECREF(pyItem);
218  Py_DECREF(pyIterator);
219  return nullptr;
220  }
221 
222  result->emplace_back(std::move(*item));
223  Py_DECREF(pyItem);
224  }
225 
226  Py_DECREF(pyIterator);
227 
228  if (PyErr_Occurred() != nullptr) {
229  return nullptr;
230  }
231 
232  return result;
233 }
234 
235 
236 
237 } // namespace python {
238 } // namespace mesos {
239 
240 #endif /* MESOS_NATIVE_COMMON_HPP */
PyObject * createPythonProtobuf(const T &t, const char *typeName)
Convert a C++ protocol buffer object into a Python one by serializing it to a string and deserializin...
Definition: common.hpp:105
bool readPythonProtobuf(PyObject *obj, T *t)
Convert a Python protocol buffer object into a C++ one by serializing it to a string and deserializin...
Definition: common.hpp:65
Try< std::string > typeName(uint32_t fsType)
std::unique_ptr< std::vector< T > > constructFromIterable(PyObject *iterable)
Definition: common.hpp:199
PyObject * mesos_pb2
The Python module object for mesos_pb2 (which contains the protobuf classes generated for Python)...
RAII utility class for acquiring the Python global interpreter lock.
Definition: common.hpp:45
~InterpreterLock()
Definition: common.hpp:53
Definition: agent.hpp:25
std::enable_if< std::is_base_of< google::protobuf::Message, T >::value, std::unique_ptr< T > >::type construct(PyObject *obj)
Definition: common.hpp:153
InterpreterLock()
Definition: common.hpp:49
Try< uint32_t > type(const std::string &path)