pyEnum.h
Go to the documentation of this file.
1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_BASE_TF_PY_ENUM_H
25 #define PXR_BASE_TF_PY_ENUM_H
26 
29 
30 #include "pxr/pxr.h"
31 
32 #include "pxr/base/tf/api.h"
33 #include "pxr/base/tf/pyObjWrapper.h"
34 #include "pxr/base/tf/pyUtils.h"
35 #include "pxr/base/tf/type.h"
36 
37 #include "pxr/base/arch/demangle.h"
38 #include "pxr/base/tf/enum.h"
39 #include "pxr/base/tf/hash.h"
40 #include "pxr/base/tf/hashmap.h"
41 #include "pxr/base/tf/iterator.h"
42 #include "pxr/base/tf/singleton.h"
44 
45 #include <boost/python/class.hpp>
46 #include <boost/python/converter/from_python.hpp>
47 #include <boost/python/converter/registered.hpp>
48 #include <boost/python/converter/rvalue_from_python_data.hpp>
49 #include <boost/python/list.hpp>
50 #include <boost/python/object.hpp>
51 #include <boost/python/operators.hpp>
52 #include <boost/python/refcount.hpp>
53 #include <boost/python/scope.hpp>
54 #include <boost/python/to_python_converter.hpp>
55 #include <boost/python/tuple.hpp>
56 
57 #include <string>
58 
59 PXR_NAMESPACE_OPEN_SCOPE
60 
64 class Tf_PyEnum { };
65 
70 class Tf_PyEnumRegistry {
71 
72  public:
73  typedef Tf_PyEnumRegistry This;
74 
75  private:
76  Tf_PyEnumRegistry();
77  virtual ~Tf_PyEnumRegistry();
78  friend class TfSingleton<This>;
79 
80  public:
81 
82  TF_API static This &GetInstance() {
84  }
85 
86  TF_API
87  void RegisterValue(TfEnum const &e, boost::python::object const &obj);
88 
89  template <typename T>
90  void RegisterEnumConversions() {
91  // Register conversions to and from python.
92  boost::python::to_python_converter<T, _EnumToPython<T> >();
93  _EnumFromPython<T>();
94  }
95 
96  private:
97 
98  TF_API
99  PyObject *_ConvertEnumToPython(TfEnum const &e);
100 
101  template <typename T>
102  struct _EnumFromPython {
103  _EnumFromPython() {
104  boost::python::converter::registry::insert
105  (&convertible, &construct, boost::python::type_id<T>());
106  }
107  static void *convertible(PyObject *obj) {
108  TfHashMap<PyObject *, TfEnum, _ObjectHash> const &o2e =
109  Tf_PyEnumRegistry::GetInstance()._objectsToEnums;
110  TfHashMap<PyObject *, TfEnum, _ObjectHash>::const_iterator
111  i = o2e.find(obj);
112  // In the case of producing a TfEnum or an integer, any
113  // registered enum type is fine. In all other cases, the
114  // enum types must match.
115  if (boost::is_same<T, TfEnum>::value ||
116  (boost::is_integral<T>::value && !boost::is_enum<T>::value))
117  return i != o2e.end() ? obj : 0;
118  else
119  return (i != o2e.end() && i->second.IsA<T>()) ? obj : 0;
120  }
121  static void construct(PyObject *src, boost::python::converter::
122  rvalue_from_python_stage1_data *data) {
123  void *storage =
124  ((boost::python::converter::
125  rvalue_from_python_storage<T> *)data)->storage.bytes;
126  new (storage) T(_GetEnumValue(src, (T *)0));
127  data->convertible = storage;
128  }
129  private:
130  // Overloads to explicitly allow conversion of the TfEnum integer
131  // value to other enum/integral types.
132  template <typename U>
133  static U _GetEnumValue(PyObject *src, U *) {
134  return U(Tf_PyEnumRegistry::GetInstance()._objectsToEnums[src].
135  GetValueAsInt());
136  }
137  static TfEnum _GetEnumValue(PyObject *src, TfEnum *) {
138  return Tf_PyEnumRegistry::GetInstance()._objectsToEnums[src];
139  }
140  };
141 
142  template <class T>
143  struct _EnumToPython {
144  static PyObject *convert(T t) {
145  return Tf_PyEnumRegistry
146  ::GetInstance()._ConvertEnumToPython(TfEnum(t));
147  }
148  };
149 
150  // Since our enum objects live as long as the registry does, we can use the
151  // pointer values for a hash.
152  struct _ObjectHash {
153  size_t operator()(PyObject *o) const {
154  return reinterpret_cast<size_t>(o);
155  }
156  };
157 
158  TfHashMap<TfEnum, PyObject *, TfHash> _enumsToObjects;
159  TfHashMap<PyObject *, TfEnum, _ObjectHash> _objectsToEnums;
160 
161 };
162 
163 TF_API_TEMPLATE_CLASS(TfSingleton<Tf_PyEnumRegistry>);
164 
165 // Private function used for __repr__ of wrapped enum types.
166 TF_API
167 std::string Tf_PyEnumRepr(boost::python::object const &self);
168 
169 // Private base class for types which are instantiated and exposed to python
170 // for each registered enum type.
171 struct Tf_PyEnumWrapper
172  : public Tf_PyEnum, boost::totally_ordered<Tf_PyEnumWrapper>
173 {
174  typedef Tf_PyEnumWrapper This;
175 
176  Tf_PyEnumWrapper(std::string const &n, TfEnum const &val) :
177  name(n), value(val) {}
178  long GetValue() const {
179  return value.GetValueAsInt();
180  }
181  std::string GetName() const{
182  return name;
183  }
184  std::string GetDisplayName() const {
185  return TfEnum::GetDisplayName(value);
186  }
187  std::string GetFullName() const {
188  return TfEnum::GetFullName(value);
189  }
190  friend bool operator ==(Tf_PyEnumWrapper const &self,
191  long other) {
192  return self.value.GetValueAsInt() == other;
193  }
194 
195  friend bool operator ==(Tf_PyEnumWrapper const &lhs,
196  Tf_PyEnumWrapper const &rhs) {
197  return lhs.value == rhs.value;
198  }
199 
200  friend bool operator <(Tf_PyEnumWrapper const &lhs,
201  Tf_PyEnumWrapper const &rhs)
202  {
203  // If same, not less.
204  if (lhs == rhs)
205  return false;
206  // If types don't match, string compare names.
207  if (!lhs.value.IsA(rhs.value.GetType()))
208  return TfEnum::GetFullName(lhs.value) <
209  TfEnum::GetFullName(rhs.value);
210  // If types do match, numerically compare values.
211  return lhs.GetValue() < rhs.GetValue();
212  }
213 
214  //
215  // XXX Bitwise operators for Enums are a temporary measure to support the
216  // use of Enums as Bitmasks in libSd. It should be noted that Enums are
217  // NOT closed under these operators. The proper place for such operators
218  // is in a yet-nonexistent Bitmask type.
219  //
220 
221  friend TfEnum operator |(Tf_PyEnumWrapper const &lhs,
222  Tf_PyEnumWrapper const &rhs) {
223  if (lhs.value.IsA(rhs.value.GetType())) {
224  return TfEnum(lhs.value.GetType(),
225  lhs.value.GetValueAsInt() |
226  rhs.value.GetValueAsInt());
227  }
228  TfPyThrowTypeError("Enum type mismatch");
229  return TfEnum();
230  }
231  friend TfEnum operator |(Tf_PyEnumWrapper const &lhs, long rhs) {
232  return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() | rhs);
233  }
234  friend TfEnum operator |(long lhs, Tf_PyEnumWrapper const &rhs) {
235  return TfEnum(rhs.value.GetType(), lhs | rhs.value.GetValueAsInt());
236  }
237 
238  friend TfEnum operator &(Tf_PyEnumWrapper const &lhs,
239  Tf_PyEnumWrapper const &rhs) {
240  if (lhs.value.IsA(rhs.value.GetType())) {
241  return TfEnum(lhs.value.GetType(),
242  lhs.value.GetValueAsInt() &
243  rhs.value.GetValueAsInt());
244  }
245  TfPyThrowTypeError("Enum type mismatch");
246  return TfEnum();
247  }
248  friend TfEnum operator &(Tf_PyEnumWrapper const &lhs, long rhs) {
249  return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() & rhs);
250  }
251  friend TfEnum operator &(long lhs, Tf_PyEnumWrapper const &rhs) {
252  return TfEnum(rhs.value.GetType(), lhs & rhs.value.GetValueAsInt());
253  }
254 
255  friend TfEnum operator ^(Tf_PyEnumWrapper const &lhs,
256  Tf_PyEnumWrapper const &rhs) {
257  if (lhs.value.IsA(rhs.value.GetType())) {
258  return TfEnum(lhs.value.GetType(),
259  lhs.value.GetValueAsInt() ^
260  rhs.value.GetValueAsInt());
261  }
262  TfPyThrowTypeError("Enum type mismatch");
263  return TfEnum();
264  }
265  friend TfEnum operator ^(Tf_PyEnumWrapper const &lhs, long rhs) {
266  return TfEnum(lhs.value.GetType(), lhs.value.GetValueAsInt() ^ rhs);
267  }
268  friend TfEnum operator ^(long lhs, Tf_PyEnumWrapper const &rhs) {
269  return TfEnum(rhs.value.GetType(), lhs ^ rhs.value.GetValueAsInt());
270  }
271 
272  friend TfEnum operator ~(Tf_PyEnumWrapper const &rhs) {
273  return TfEnum(rhs.value.GetType(), ~rhs.value.GetValueAsInt());
274  }
275  std::string name;
276  TfEnum value;
277 };
278 
279 // Private template class which is instantiated and exposed to python for each
280 // registered enum type.
281 template <typename T>
282 struct Tf_TypedPyEnumWrapper : Tf_PyEnumWrapper
283 {
284  Tf_TypedPyEnumWrapper(std::string const &n, TfEnum const &val) :
285  Tf_PyEnumWrapper(n, val) {}
286 
287  static boost::python::object GetValueFromName(const std::string& name) {
288  bool found = false;
289  const TfEnum value = TfEnum::GetValueFromName<T>(name, &found);
290  return found
291  ? boost::python::object(value)
292  : boost::python::object();
293  }
294 };
295 
296 // Sanitizes the given \p name for use as a Python identifier. This includes
297 // replacing spaces with '_' and appending '_' to names matching Python
298 // keywords.
299 //
300 // If \p stripPackageName is true and \p name begins with the package name,
301 // it will be stripped off.
302 TF_API
303 std::string Tf_PyCleanEnumName(std::string name,
304  bool stripPackageName = false);
305 
306 // Adds attribute of given name with given value to given scope.
307 // Issues a coding error if attribute by that name already existed.
308 TF_API
309 void Tf_PyEnumAddAttribute(boost::python::scope &s,
310  const std::string &name,
311  const boost::python::object &value);
312 
354 
355 // Detect scoped enums by using that the C++ standard does not allow them to
356 // be converted to int implicitly.
357 template <typename T, bool IsScopedEnum = !std::is_convertible<T, int>::value>
358 struct TfPyWrapEnum {
359 
360 private:
361  typedef boost::python::class_<
362  Tf_TypedPyEnumWrapper<T>, boost::python::bases<Tf_PyEnumWrapper> >
363  _EnumPyClassType;
364 
365 public:
366 
371  explicit TfPyWrapEnum( std::string const &name = std::string())
372  {
373  using namespace boost::python;
374 
375  const bool explicitName = !name.empty();
376 
377  // First, take either the given name, or the demangled type name.
378  std::string enumName = explicitName ? name :
379  TfStringReplace(ArchGetDemangled(typeid(T)), "::", ".");
380 
381  // If the name is dotted, take everything before the dot as the base
382  // name. This is used in repr.
383  std::string baseName = TfStringGetBeforeSuffix(enumName);
384  if (baseName == enumName)
385  baseName = std::string();
386 
387  // If the name is dotted, take the last element as the enum name.
388  if (!TfStringGetSuffix(enumName).empty())
389  enumName = TfStringGetSuffix(enumName);
390 
391  // If the name was not explicitly given, then clean it up by removing
392  // the package name prefix if it exists.
393  if (!explicitName) {
394  if (!baseName.empty()) {
395  baseName = Tf_PyCleanEnumName(
396  baseName, /* stripPackageName = */ true);
397  }
398  else {
399  enumName = Tf_PyCleanEnumName(
400  enumName, /* stripPackageName = */ true);
401  }
402  }
403 
404  if (IsScopedEnum) {
405  // Make the enumName appear in python representation
406  // for scoped enums.
407  if (!baseName.empty()) {
408  baseName += ".";
409  }
410  baseName += enumName;
411  }
412 
413  // Make a python type for T.
414  _EnumPyClassType enumClass(enumName.c_str(), no_init);
415  enumClass.def("GetValueFromName", &Tf_TypedPyEnumWrapper<T>::GetValueFromName, arg("name"));
416  enumClass.staticmethod("GetValueFromName");
417  enumClass.setattr("_baseName", baseName);
418 
419  // Register conversions for it.
420  Tf_PyEnumRegistry::GetInstance().RegisterEnumConversions<T>();
421 
422  // Export values.
423  //
424  // Only strip the package name from top-level enum values.
425  // For example, if an enum named "Foo" is declared at top-level
426  // scope in Tf with values "TfBar" and "TfBaz", we want to strip
427  // off Tf so that the values in Python will be Tf.Bar and Tf.Baz.
428  const bool stripPackageName = baseName.empty();
429  _ExportValues(stripPackageName, enumClass);
430 
431  // Register with Tf so that python clients of a TfType
432  // that represents an enum are able to get to the equivalent
433  // python class with .pythonclass
434  const TfType &type = TfType::Find<T>();
435  if (!type.IsUnknown())
436  type.DefinePythonClass(enumClass);
437  }
438 
439  private:
440 
444  void _ExportValues(bool stripPackageName, _EnumPyClassType &enumClass) {
445  boost::python::list valueList;
446 
447  for (const std::string& name : TfEnum::GetAllNames<T>()) {
448  bool success = false;
449  TfEnum enumValue = TfEnum::GetValueFromName<T>(name, &success);
450  if (!success) {
451  continue;
452  }
453 
454  const std::string cleanedName =
455  Tf_PyCleanEnumName(name, stripPackageName);
456 
457  // convert value to python.
458  Tf_TypedPyEnumWrapper<T> wrappedValue(cleanedName, enumValue);
459  boost::python::object pyValue(wrappedValue);
460 
461  // register it as the python object for this value.
462  Tf_PyEnumRegistry::GetInstance().RegisterValue(enumValue, pyValue);
463 
464  // Take all the values and export them into the current scope.
465  std::string valueName = wrappedValue.GetName();
466  if (IsScopedEnum) {
467  // If scoped enum, enum values appear on the enumClass ...
468  boost::python::scope s(enumClass);
469  Tf_PyEnumAddAttribute(s, valueName, pyValue);
470  } else {
471  // ... otherwise, enum values appear on the enclosing scope.
472  boost::python::scope s;
473  Tf_PyEnumAddAttribute(s, valueName, pyValue);
474  }
475 
476  valueList.append(pyValue);
477  }
478 
479  // Add a tuple of all the values to the enum class.
480  enumClass.setattr("allValues", boost::python::tuple(valueList));
481  }
482 
483 };
484 
485 PXR_NAMESPACE_CLOSE_SCOPE
486 
487 #endif // PXR_BASE_TF_PY_ENUM_H
Manage a single instance of an object.
Manage a single instance of an object (see.
Definition: singleton.h:119
A simple iterator adapter for STL containers.
TF_API std::string TfStringGetBeforeSuffix(const std::string &name, char delimiter='.')
Returns everything up to the suffix of a string.
TF_API void DefinePythonClass(const TfPyObjWrapper &classObj) const
Define the Python class object corresponding to this TfType.
ARCH_API std::string ArchGetDemangled(const std::string &typeName)
Return demangled RTTI-generated type name.
Definitions of basic string utilities in tf.
An enum class that records both enum type and enum value.
Definition: enum.h:139
Miscellaneous Utilities for dealing with script.
Demangle C++ typenames generated by the typeid() facility.
static TF_API std::string GetDisplayName(TfEnum val)
Returns the display name for an enumerated value.
TF_API void TfPyThrowTypeError(const char *msg)
Raises a Python TypeError with the given error msg and throws a boost::python::error_already_set exce...
static T & GetInstance()
Return a reference to an object of type T, creating it if necessary.
Definition: singleton.h:134
static TF_API std::string GetFullName(TfEnum val)
Returns the fully-qualified name for an enumerated value.
TF_API std::string TfStringGetSuffix(const std::string &name, char delimiter='.')
Returns the suffix of a string.
TfPyWrapEnum(std::string const &name=std::string())
Construct an enum wrapper object.
Definition: pyEnum.h:371
TfType represents a dynamic runtime type.
Definition: type.h:64
bool IsUnknown() const
Return true if this is the unknown type, representing a type unknown to the TfType system.
Definition: type.h:388
VT_API bool operator==(VtDictionary const &, VtDictionary const &)
Equality comparison.
Used to wrap enum types for script.
Definition: pyEnum.h:358
TF_API std::string TfStringReplace(const std::string &source, const std::string &from, const std::string &to)
Replaces all occurrences of string from with to in source.