Library of Assembled Shared Sources
enum_definition.cpp
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2022-2025 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#include "enum_definition.h"
44#include "pyobject_ptr.h"
45#include "py_tuple.h"
47
48namespace lass
49{
50 namespace python
51 {
52 namespace impl
53 {
54 TPyObjPtr makeEnumType(const char* name, TPyObjPtr&& enumerators, TPyObjPtr&& kwargs)
55 {
56 TPyObjPtr enumMod(PyImport_ImportModule("enum"));
57 if (!enumMod)
58 {
59 return TPyObjPtr();
60 }
61 TPyObjPtr intEnumType(PyObject_GetAttrString(enumMod.get(), "Enum"));
62 if (!intEnumType)
63 {
64 return TPyObjPtr();
65 }
66
67 TPyObjPtr args = makeTuple(name, std::move(enumerators));
68 if (!args)
69 {
70 return TPyObjPtr();
71 }
72 TPyObjPtr type{ PyObject_Call(intEnumType.get(), args.get(), kwargs.get()) };
73 return type;
74 }
75
76 TPyObjPtr makeIntEnumTypeImpl(const char* enumType, const char* name, TPyObjPtr&& enumerators, TPyObjPtr&& kwargs)
77 {
78 TPyObjPtr enumMod(PyImport_ImportModule("enum"));
79 if (!enumMod)
80 {
81 return TPyObjPtr();
82 }
83 TPyObjPtr intEnumType(PyObject_GetAttrString(enumMod.get(), enumType));
84 if (!intEnumType)
85 {
86 return TPyObjPtr();
87 }
88
89 TPyObjPtr args = makeTuple(name, std::move(enumerators));
90 if (!args)
91 {
92 return TPyObjPtr();
93 }
94 TPyObjPtr type{ PyObject_Call(intEnumType.get(), args.get(), kwargs.get()) };
95
96#if PY_VERSION_HEX < 0x030b0000 // < 3.11
97 if (!type)
98 {
99 return TPyObjPtr();
100 }
101
102 // set it's __str__ method to int.__repr__, to mimic 3.11 IntEnum behavior.
103 // Not int.__str__ because that is object.__str__, which resolves back to enum.__repr__
104 PyObject* intType = reinterpret_cast<PyObject*>(&PyLong_Type);
105 TPyObjPtr intRepr{ PyObject_GetAttrString(intType, "__repr__") };
106 if (!intRepr || PyObject_SetAttrString(type.get(), "__str__", intRepr.get()) != 0)
107 {
108 return TPyObjPtr();
109 }
110#endif
111
112 return type;
113 }
114
115 TPyObjPtr makeIntEnumType(const char* name, TPyObjPtr&& enumerators, TPyObjPtr&& kwargs)
116 {
117 return makeIntEnumTypeImpl("IntEnum", name, std::move(enumerators), std::move(kwargs));
118 }
119
120 TPyObjPtr makeIntFlagType(const char* name, TPyObjPtr&& enumerators, TPyObjPtr&& kwargs, FlagBoundary boundary)
121 {
122#if PY_VERSION_HEX < 0x030b0000 // < 3.11
123 if (boundary != FlagBoundary::Keep)
124 {
125 PyErr_SetString(PyExc_ValueError, "FlagBoundary other than KEEP is only supported on Python 3.11 and later");
126 return TPyObjPtr();
127 }
128#else
129 using TInt = std::underlying_type<FlagBoundary>::type;
130 constexpr TInt numBoundaries = 3;
131 const char* boundaries[numBoundaries] = { "KEEP", "STRICT", "CONFORM" };
132 LASS_ENFORCE(static_cast<TInt>(boundary) >= 0 && static_cast<TInt>(boundary) < numBoundaries);
133 const char* boundariesStr = boundaries[static_cast<TInt>(boundary)];
134 TPyObjPtr enumMod(PY_ENFORCE_POINTER(PyImport_ImportModule("enum")));
135 if (!enumMod)
136 {
137 return TPyObjPtr();
138 }
139 TPyObjPtr boundaryObj(PY_ENFORCE_POINTER(PyObject_GetAttrString(enumMod.get(), boundariesStr)));
140 if (!boundaryObj)
141 {
142 return TPyObjPtr();
143 }
144 if (PyDict_SetItemString(kwargs.get(), "boundary", boundaryObj.get()) != 0)
145 {
146 return TPyObjPtr();
147 }
148#endif
149 return makeIntEnumTypeImpl("IntFlag", name, std::move(enumerators), std::move(kwargs));
150 }
151
152 TPyObjPtr makeStrEnumType(const char* name, TPyObjPtr&& enumerators, TPyObjPtr&& kwargs)
153 {
154#if PY_VERSION_HEX < 0x030b0000 // < 3.11
155 // mix in str type, so that they also behave like strings ...
156 PyObject* strType = reinterpret_cast<PyObject*>(&PyUnicode_Type);
157 if (PyDict_SetItemString(kwargs.get(), "type", strType) != 0)
158 {
159 return TPyObjPtr();
160 }
161
162 // build it as a normal Enum
163 TPyObjPtr type = makeEnumType(name, std::move(enumerators), std::move(kwargs));
164 if (!type)
165 {
166 return TPyObjPtr();
167 }
168
169 // set it's __str__ method to str.__str__, to mimic 3.11 StrEnum behavior.
170 TPyObjPtr strStr{ PyObject_GetAttrString(strType, "__str__") };
171 if (!strStr || PyObject_SetAttrString(type.get(), "__str__", strStr.get()) != 0)
172 {
173 return TPyObjPtr();
174 }
175
176 return type;
177#else
178 TPyObjPtr enumMod(PY_ENFORCE_POINTER(PyImport_ImportModule("enum")));
179 if (!enumMod)
180 {
181 return TPyObjPtr();
182 }
183 TPyObjPtr strEnumType(PY_ENFORCE_POINTER(PyObject_GetAttrString(enumMod.get(), "StrEnum")));
184 if (!strEnumType)
185 {
186 return TPyObjPtr();
187 }
188
189 TPyObjPtr args = makeTuple(name, std::move(enumerators));
190 if (!args)
191 {
192 return TPyObjPtr();
193 }
194 TPyObjPtr type{ PyObject_Call(strEnumType.get(), args.get(), kwargs.get()) };
195 return type;
196#endif
197 }
198 }
199
201 name_(name),
202 doc_(nullptr)
203 {
204 }
206 name_(name),
207 doc_(doc)
208 {
209 }
210
211 EnumDefinitionBase::~EnumDefinitionBase()
212 {
213 }
214
215 const char* EnumDefinitionBase::name() const
216 {
217 return name_;
218 }
219
220 const char* EnumDefinitionBase::doc() const
221 {
222 return doc_;
223 }
225 PyObject* EnumDefinitionBase::type() const
227 return type_.get();
228 }
229
231 {
232 return doValueObject(obj);
233 }
235 TPyObjPtr EnumDefinitionBase::doValueObject(PyObject* obj) const
236 {
237 PyObject* type = this->type();
238 if (PyObject_TypeCheck(obj, (PyTypeObject*) type))
239 {
240 return TPyObjPtr(PyObject_GetAttrString(obj, "value"));
241 }
242 // in the generic case, we're strict: only the enum type itself is accepted
243 PyErr_Format(PyExc_TypeError, "Expected %S, got %S", type, obj);
244 return TPyObjPtr();
245 }
246
247 PyObject* EnumDefinitionBase::freezeDefinition(const char* moduleName, const char* scopeName)
248 {
249 if (!type_)
250 {
251 TPyObjPtr kwargs(PyDict_New());
252 if (!kwargs)
254 return nullptr;
255 }
256 if (moduleName)
257 {
258 TPyObjPtr moduleNameObj(pyBuildSimpleObject(moduleName));
259 if (!moduleNameObj || PyDict_SetItemString(kwargs.get(), "module", moduleNameObj.get()) != 0)
260 {
261 return nullptr;
262 }
263 }
264 if (scopeName)
265 {
266 std::string qualName = stde::safe_format("%s.%s", scopeName, name_);
267 TPyObjPtr qualNameObj(pyBuildSimpleObject(std::move(qualName)));
268 if (!qualNameObj || PyDict_SetItemString(kwargs.get(), "qualname", qualNameObj.get()) != 0)
269 {
270 return nullptr;
271 }
272 }
273 type_ = doFreezeDefinition(std::move(kwargs));
274 if (!type_)
275 {
276 return nullptr;
277 }
278 }
279 if (doc_)
280 {
281 // set the docstring
282 TPyObjPtr docStr(pyBuildSimpleObject(doc_));
283 if (!docStr || PyObject_SetAttrString(type_.get(), "__doc__", docStr.get()) != 0)
284 {
285 return nullptr;
286 }
287 }
288 return type_.get();
289 }
290 }
291}
EnumDefinitionBase(const char *name)
Construct enum definition with name only.
PyObject * type() const
The Python enum type object, after freezeDefinition() has been called.
const char * doc() const
Optional docstring for the Python enum type.
const char * name() const
The name of the Python enum type.
TPyObjPtr valueObject(PyObject *obj) const
Returns the value of an enum instances as a Python object.
PyObject * freezeDefinition(const char *moduleName, const char *scopeName)
Freeze the enum definition and create the Python enum type.
TPyObjPtr makeEnumType(const char *name, TPyObjPtr &&enumerators, TPyObjPtr &&kwargs)
Creates a basic enum.Enum type.
TPyObjPtr makeIntEnumType(const char *name, TPyObjPtr &&enumerators, TPyObjPtr &&kwargs)
Creates an enum.IntEnum type.
TPyObjPtr makeIntFlagType(const char *name, TPyObjPtr &&enumerators, TPyObjPtr &&kwargs, FlagBoundary boundary)
Creates an enum.IntFlag type.
FlagBoundary
Defines the boundary behavior for enum.IntFlag-derived enums.
TPyObjPtr makeStrEnumType(const char *name, TPyObjPtr &&enumerators, TPyObjPtr &&kwargs)
Creates an enum.StrEnum type (or equivalent for Python < 3.11).
@ Keep
Allow any integer value (most permissive)
#define PY_ENFORCE_POINTER(pointer)
Enforce that a Python pointer is not null.
PyObjectPtr< PyObject >::Type TPyObjPtr
PyObjectPtr to a PyObject.
Comprehensive C++ to Python binding library.
Library for Assembled Shared Sources.
Definition config.h:53