Skip to content

Commit 4198e7d

Browse files
committed
Phase one of csp.Enum retirement: add support for IntEnum as native csp types
Signed-off-by: Rob Ambalu <robert.ambalu@point72.com>
1 parent fb5ff41 commit 4198e7d

9 files changed

Lines changed: 98 additions & 25 deletions

File tree

cpp/csp/python/Conversions.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,16 @@ inline PyObject * toPython( const CspEnum & e, const CspType & type )
446446
auto & enumType = static_cast<const CspEnumType&>( type );
447447
const auto * emeta = static_cast<const DialectCspEnumMeta*>( enumType.meta().get() );
448448

449+
if( emeta -> isPyIntEnum() )
450+
{
451+
//TODO - precache IntEnums on DialectCspEnumMeta instead of PyCspEnumMeta for faster IntEnum conversion
452+
PyObjectPtr val = PyObjectPtr::own( toPython( e.value() ) );
453+
PyObject * obj = PyObject_CallOneArg( ( PyObject * ) emeta -> pyType().get(), val.get() );
454+
if( !obj )
455+
CSP_THROW( PythonPassthrough, "" );
456+
return obj;
457+
}
458+
449459
PyObject * obj = emeta -> pyMeta() -> toPyEnum( e );
450460
if( !obj ) [[unlikely]]
451461
CSP_THROW( ValueError, e.value() << " is not a valid value on csp.enum type " << emeta -> name() );
@@ -457,9 +467,14 @@ inline CspEnum fromPython( PyObject * o, const CspType & type )
457467
{
458468
assert( type.type() == CspType::Type::ENUM );
459469

460-
if( !PyType_IsSubtype( Py_TYPE( o ), &PyCspEnum::PyType ) ||
461-
static_cast<PyCspEnum *>( o ) -> meta() != static_cast<const CspEnumType &>( type ).meta().get() )
462-
CSP_THROW( TypeError, "Invalid enum type, expected enum type " << static_cast<const CspEnumType &>( type ).meta() -> name() << " got " << Py_TYPE( o ) -> tp_name );
470+
auto & enumType = static_cast<const CspEnumType&>( type );
471+
const auto * emeta = static_cast<const DialectCspEnumMeta*>( enumType.meta().get() );
472+
473+
if( !PyObject_IsInstance( o, ( PyObject * ) emeta -> pyType().get() ) )
474+
CSP_THROW( TypeError, "Invalid enum type, expected enum type " << emeta -> pyType() -> tp_name << " got " << Py_TYPE( o ) -> tp_name );
475+
476+
if( emeta -> isPyIntEnum() )
477+
return static_cast<const CspEnumType &>( type ).meta() -> create( PyLong_AsLong( o ) );
463478

464479
return static_cast<PyCspEnum *>( o ) -> enum_;
465480
}

cpp/csp/python/CspTypeFactory.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
namespace csp::python
88
{
99

10+
CspTypeFactory::CspTypeFactory()
11+
{
12+
PyObject *enum_mod = PyImport_ImportModule( "enum" );
13+
m_intEnumPyType = PyTypeObjectPtr::own( ( PyTypeObject * ) PyObject_GetAttrString( enum_mod, "IntEnum" ) );
14+
}
15+
1016
CspTypeFactory & CspTypeFactory::instance()
1117
{
1218
static CspTypeFactory s_instance;
@@ -64,6 +70,11 @@ CspTypePtr & CspTypeFactory::typeFromPyType( PyObject * pyTypeObj )
6470
auto meta = ( ( PyCspEnumMeta * ) pyType ) -> enumMeta;
6571
rv.first -> second = std::make_shared<csp::CspEnumType>( meta );
6672
}
73+
else if( PyType_IsSubtype( pyType, m_intEnumPyType.get() ) )
74+
{
75+
auto meta = createCspEnumMetaFromIntEnum( PyTypeObjectPtr::incref( pyType ) );
76+
rv.first -> second = std::make_shared<csp::CspEnumType>( meta );
77+
}
6778
else if( pyType == PyDateTimeAPI -> DateTimeType )
6879
rv.first -> second = csp::CspType::DATETIME();
6980
else if( pyType == PyDateTimeAPI -> DeltaType )
@@ -88,4 +99,30 @@ void CspTypeFactory::removeCachedType( PyTypeObject * pyType )
8899
m_cache.erase( pyType );
89100
}
90101

102+
std::shared_ptr<CspEnumMeta> CspTypeFactory::createCspEnumMetaFromIntEnum( PyTypeObjectPtr pyIntEnumType )
103+
{
104+
CspEnumMeta::ValueDef metadef;
105+
106+
PyObjectPtr iter = PyObjectPtr::check( PyObject_GetIter( ( PyObject * ) pyIntEnumType.get() ) );
107+
PyObject * member;
108+
while( ( member = PyIter_Next( iter.get() ) ) != NULL )
109+
{
110+
PyObjectPtr name = PyObjectPtr::check( PyObject_GetAttrString( member, "name" ) );
111+
112+
const char * namestr = PyUnicode_AsUTF8( name.get() );
113+
if( !namestr )
114+
CSP_THROW( PythonPassthrough, "" );
115+
116+
if( !PyLong_Check( member ) )
117+
CSP_THROW( TypeError, "enum key " << namestr << " expected an integer got " << PyObjectPtr::incref( member ) );
118+
119+
int64_t value = fromPython<int64_t>( member );
120+
metadef[ namestr ] = value;
121+
122+
Py_DECREF( member );
123+
}
124+
125+
return std::make_shared<DialectCspEnumMeta>( pyIntEnumType, pyIntEnumType -> tp_name, metadef );
126+
}
127+
91128
}

cpp/csp/python/CspTypeFactory.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <csp/core/Platform.h>
55
#include <csp/engine/CspType.h>
6+
#include <csp/python/PyObjectPtr.h>
67
#include <unordered_map>
78
#include <Python.h>
89

@@ -17,9 +18,17 @@ class CSPTYPESIMPL_EXPORT CspTypeFactory
1718
CspTypePtr & typeFromPyType( PyObject * );
1819
void removeCachedType( PyTypeObject * );
1920

21+
PyTypeObject * intEnumPyType() { return m_intEnumPyType.get(); }
22+
2023
private:
2124
using Cache = std::unordered_map<PyTypeObject *, CspTypePtr>;
25+
26+
std::shared_ptr<CspEnumMeta> createCspEnumMetaFromIntEnum( PyTypeObjectPtr pyIntEnumType );
27+
28+
CspTypeFactory();
2229
Cache m_cache;
30+
31+
PyTypeObjectPtr m_intEnumPyType;
2332
};
2433

2534
}

cpp/csp/python/PyCspEnum.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ DialectCspEnumMeta::DialectCspEnumMeta( PyTypeObjectPtr pyType, const std::strin
1212
CspEnumMeta( name, def ),
1313
m_pyType( pyType )
1414
{
15+
m_isPyIntEnum = PyType_IsSubtype( pyType.get(), CspTypeFactory::instance().intEnumPyType() );
1516
}
1617

1718
/*

cpp/csp/python/PyCspEnum.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ class CSPTYPESIMPL_EXPORT DialectCspEnumMeta : public CspEnumMeta
4040

4141
const PyTypeObjectPtr & pyType() const { return m_pyType; }
4242

43-
const PyCspEnumMeta * pyMeta() const { return ( const PyCspEnumMeta * ) m_pyType.get(); }
43+
const PyCspEnumMeta * pyMeta() const { assert( !m_isPyIntEnum ); return ( const PyCspEnumMeta * ) m_pyType.get(); }
4444

45+
bool isPyIntEnum() const { return m_isPyIntEnum; }
4546
private:
4647

4748
PyTypeObjectPtr m_pyType;
49+
bool m_isPyIntEnum;
4850
};
4951

5052
struct CSPTYPESIMPL_EXPORT PyCspEnum : public PyObject

cpp/csp/python/PyInputProxy.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,8 @@ PyObject *PyInputProxy::valuesAt( ValueType valueType, PyObject *startIndexArg,
239239
PyObject *endIndexPolicyArg ) const
240240
{
241241
int32_t startIndex, endIndex;
242-
auto startPolicy = static_cast<autogen::TimeIndexPolicy>( static_cast<PyCspEnum *>( startIndexPolicyArg ) -> enum_ );
243-
auto endPolicy = static_cast<autogen::TimeIndexPolicy>( static_cast<PyCspEnum *>( endIndexPolicyArg ) -> enum_ );
242+
auto startPolicy = csp::autogen::TimeIndexPolicy::create( startIndexPolicyArg );
243+
auto endPolicy = csp::autogen::TimeIndexPolicy::create( endIndexPolicyArg );
244244

245245
if( startIndexArg == Py_None )
246246
startIndex = 1 - ts() -> numTicks();
@@ -513,9 +513,9 @@ static inline PyObject * PyInputProxy_values_at_impl( ValueType valueType, PyInp
513513
PyObject * endIndexArg;
514514
PyObject * startExclusiveArg;
515515
PyObject * endExclusiveArg;
516-
if( !PyArg_ParseTuple( args, "OOO!O!", &startIndexArg, &endIndexArg,
517-
&PyCspEnum::PyType, &startExclusiveArg,
518-
&PyCspEnum::PyType, &endExclusiveArg ) )
516+
if( !PyArg_ParseTuple( args, "OOOO", &startIndexArg, &endIndexArg,
517+
&startExclusiveArg,
518+
&endExclusiveArg ) )
519519
CSP_THROW( RuntimeException, "Invalid arguments passed to values_at" );
520520

521521
return proxy -> valuesAt( valueType, startIndexArg, endIndexArg, startExclusiveArg, endExclusiveArg );

csp/build/csp_autogen.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os.path
66
import sys
77
import types
8+
from enum import IntEnum
89

910
# We need to patch mock modules into sys.modules to avoid pulling in csp/__init__.py and all of its baggage, which include imports of
1011
# _cspimpl, which would be a circular dep
@@ -85,7 +86,7 @@ def __init__(self, module_name: str, output_filename: str, namespace: str, gener
8586

8687
if issubclass(v, Struct) and v is not Struct:
8788
self._struct_types.append(v)
88-
elif issubclass(v, Enum) and v is not Enum:
89+
elif issubclass(v, Enum) and v is not Enum or issubclass(v, IntEnum) and v is not IntEnum:
8990
self._enum_types.append(v)
9091

9192
def _get_dependent_headers(self):
@@ -119,11 +120,9 @@ def cpp_filename(self):
119120
return self._cpp_filename
120121

121122
def generate_header_code(self):
122-
include_guard = "_IN_CSP_AUTOGEN_" + self._module_name.replace(".", "_").upper()
123-
out = f"""
124-
#ifndef {include_guard}
125-
#define {include_guard}
126-
123+
out = f"""//// Generated from {self._module_name}
124+
#pragma once
125+
127126
"""
128127
out += self._generate_headers()
129128

@@ -139,11 +138,11 @@ def generate_header_code(self):
139138
for struct_type in self._struct_types:
140139
out += self._generate_struct_class(struct_type)
141140

142-
out += "\n}\n#endif"
141+
out += "\n}"
143142
return out
144143

145144
def _generate_headers(self):
146-
common_headers = ["csp/core/Exception.h", "csp/engine/Struct.h", "cstddef"]
145+
common_headers = ["csp/core/Exception.h", "csp/engine/Struct.h", "csp/python/Conversions.h", "cstddef"]
147146

148147
common_headers.extend(self._get_dependent_headers())
149148
return "\n".join(f"#include <{h}>" for h in common_headers)
@@ -173,7 +172,11 @@ class {enum_name} : public csp::CspEnum
173172
static {enum_name} create( enum_ v ) {{ return s_meta -> create( ( int64_t ) v ); }}
174173
static {enum_name} create( const char * name) {{ return s_meta -> fromString( name ); }}
175174
static {enum_name} create( const std::string & s ) {{ return create( s.c_str() ); }}
176-
175+
static {enum_name} create( PyObject * e )
176+
{{
177+
return {enum_name}( csp::python::fromPython<CspEnum>( e, *s_cspEnumType ) );
178+
}}
179+
177180
enum_ enum_value() const {{ return ( enum_ ) value(); }}
178181
179182
static constexpr uint32_t num_types() {{ return {len([x for x in enum_type])}; }}
@@ -183,7 +186,7 @@ class {enum_name} : public csp::CspEnum
183186
{enum_name}( const csp::CspEnum & v ) : csp::CspEnum( v ) {{ CSP_TRUE_OR_THROW( v.meta() == s_meta.get(), AssertionError, "Mismatched enum meta" ); }}
184187
185188
private:
186-
189+
static std::shared_ptr<const csp::CspEnumType> s_cspEnumType;
187190
static std::shared_ptr<csp::CspEnumMeta> s_meta;
188191
}};
189192
"""
@@ -439,14 +442,18 @@ def generate_cpp_code(self):
439442
assert_or_die( enumType != nullptr, "failed to find num type {enum_name} in module {self._module_name}" );
440443
441444
// should add some assertion here..
442-
csp::python::PyCspEnumMeta * pymeta = ( csp::python::PyCspEnumMeta * ) enumType;
443-
s_meta = pymeta -> enumMeta;
445+
//csp::python::PyCspEnumMeta * pymeta = ( csp::python::PyCspEnumMeta * ) enumType;
446+
//s_meta = pymeta -> enumMeta;
447+
auto type = csp::python::CspTypeFactory::instance().typeFromPyType( enumType );
448+
s_cspEnumType = std::static_pointer_cast<const CspEnumType>( type );
449+
s_meta = s_cspEnumType -> meta();
444450
}}
445451
446452
return true;
447453
}}
448454
449455
bool static_init_{enum_name} = {enum_name}::static_init();
456+
std::shared_ptr<const csp::CspEnumType> {enum_name}::s_cspEnumType;
450457
std::shared_ptr<csp::CspEnumMeta> {enum_name}::s_meta;
451458
{static_decls}
452459
"""
@@ -460,6 +467,7 @@ def generate_cpp_code(self):
460467
#include <csp/python/Common.h>
461468
#include <csp/python/PyStruct.h>
462469
#include <csp/python/PyCspEnum.h>
470+
#include <csp/python/CspTypeFactory.h>
463471
#include <iostream>
464472
#include <stdlib.h>
465473
#include <Python.h>

csp/impl/types/autogen_types.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Set of structs / enums that are autogenerated / used in the C++ engine
2-
from csp.impl.enum import Enum
2+
from enum import IntEnum
3+
34
from csp.impl.struct import Struct
45

56
CSP_AUTOGEN_HINTS = {"cpp_header": "csp/engine/csp_autogen/autogen_types.h"}
@@ -23,7 +24,7 @@ def removed(self):
2324
return [event.key for event in self.events if not event.added]
2425

2526

26-
class TimeIndexPolicy(Enum):
27+
class TimeIndexPolicy(IntEnum):
2728
"""An enum that specifies the policy for handling the start and end values in functions like values_at."""
2829

2930
INCLUSIVE = 1

csp/tests/test_history.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def collect_index(
161161
with self.assertRaises(TypeError):
162162
csp.values_at(x, 0, 0, csp.TimeIndexPolicy.EXCLUSIVE, csp.TimeIndexPolicy.EXCLUSIVE)
163163

164-
with self.assertRaises(RuntimeError):
164+
with self.assertRaises(TypeError):
165165
csp.items_at(x, -10, 0, False, False)
166166

167167
@csp.node
@@ -200,7 +200,7 @@ def collect_timedelta(
200200

201201
csp.output(values_default, csp.values_at(x))
202202

203-
with self.assertRaises(RuntimeError):
203+
with self.assertRaises(TypeError):
204204
csp.times_at(x, startIndex, endIndex, "abc", False)
205205

206206
@csp.node

0 commit comments

Comments
 (0)