Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added sequences support on pyopendds [#18] #20

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions pyopendds/DataWriter.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
class DataWriter:
pass
from __future__ import annotations

from .Topic import Topic
from .constants import StatusKind
from .util import TimeDurationType, normalize_time_duration

from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .Publisher import Publisher


class DataWriter(object):
iguessthislldo marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None):
self.topic = topic
self.qos = qos
self.listener = listener
self.publisher = publisher
publisher.writers.append(self)

from _pyopendds import create_datawriter
create_datawriter(self, publisher, topic)

def wait_for(self, status: StatusKind, timeout: TimeDurationType):
from _pyopendds import datareader_wait_for
return datareader_wait_for(self, status, *normalize_time_duration(timeout))

def write(self, sample):
return self.topic._ts_package.write(self, sample)
2 changes: 1 addition & 1 deletion pyopendds/DomainParticipant.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .Publisher import Publisher


class DomainParticipant:
class DomainParticipant(object):

def __init__(self, domain: int, qos=None, listener=None):
self.domain = int(domain)
Expand Down
7 changes: 4 additions & 3 deletions pyopendds/Publisher.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from __future__ import annotations

from .DataWriter import DataWriter
from .Topic import Topic

from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .DomainParticipant import DomainParticipant


class Publisher:
class Publisher(object):

def __init__(self, participant: DomainParticipant, qos=None, listener=None):
participant.publishers.append(self)
Expand All @@ -18,5 +19,5 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None):
from _pyopendds import create_publisher
create_publisher(self, participant)

def create_datawriter(self, topic: Topic, qos=None, listener=None):
pass
def create_datawriter(self, topic: Topic, qos=None, listener=None) -> DataWriter:
return DataWriter(self, topic, qos, listener)
61 changes: 47 additions & 14 deletions pyopendds/dev/include/pyopendds/user.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class IntegerType {

static PyObject* get_python_class()
{
return PyLong_Type;
return PyLong_FromLong(0);
Comment on lines 31 to +33
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was meant to return the class itself, not an instance.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get a compilation error without this modification.

}

static void cpp_to_python(const T& cpp, PyObject*& py)
Expand All @@ -45,24 +45,23 @@ class IntegerType {

static void python_to_cpp(PyObject* py, T& cpp)
{
LongType value;
long value;
iguessthislldo marked this conversation as resolved.
Show resolved Hide resolved
if (limits::is_signed) {
value = PyLong_AsLong(py);
value = PyLong_AsLong(py);
iguessthislldo marked this conversation as resolved.
Show resolved Hide resolved
} else {
value = PyLong_AsUnsignedLong(py);
value = PyLong_AsUnsignedLong(py);
iguessthislldo marked this conversation as resolved.
Show resolved Hide resolved
}
if (value < limits::min() || value > limits::max()) {
throw Exception(
"Integer Value is Out of Range for IDL Type", PyExc_ValueError);
}
if (value == -1 && PyErr_Occurred()) throw Exception();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kinda important because otherwise values will be silently truncated.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

cpp = value;
cpp = T(value);
}

};

typedef ::CORBA::Long i32;
template<> class Type<i32>: public IntegerType<i32> {};

typedef ::CORBA::Short i16;
template<> class Type<i16>: public IntegerType<i16> {};

// TODO: Put Other Integer Types Here

const char* string_data(const std::string& cpp)
Expand Down Expand Up @@ -90,7 +89,7 @@ class StringType {
public:
static PyObject* get_python_class()
{
return PyUnicode_Type;
return PyUnicode_FromString("");
}

static void cpp_to_python(const T& cpp, PyObject*& py, const char* encoding)
Expand All @@ -101,9 +100,14 @@ class StringType {
py = o;
}

static void python_to_cpp(PyObject* py, T& cpp)
static void python_to_cpp(PyObject* py, T& cpp, const char* encoding)
{
// TODO: Encode or Throw Unicode Error
PyObject* repr = PyObject_Repr(py);
iguessthislldo marked this conversation as resolved.
Show resolved Hide resolved
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

itl2py could set an encoding other than UTF-8. Also this also needs to throw if it failed.

Suggested change
PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~");
PyObject* str = PyUnicode_AsEncodedString(repr, encoding, "~E~");
if (!str) throw Exception();

Also what's "~E~" do? I don't see it in the documentation for this function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, "E" is the default string in case of error. replaced by NULL

const char *bytes = PyBytes_AS_STRING(str);
cpp = T(bytes);
Py_XDECREF(repr);
Py_XDECREF(str);
}
};

Expand All @@ -127,6 +131,7 @@ class TopicTypeBase {
virtual const char* type_name() = 0;
virtual void register_type(PyObject* pyparticipant) = 0;
virtual PyObject* take_next_sample(PyObject* pyreader) = 0;
virtual PyObject* write(PyObject* pywriter, PyObject* pysample) = 0;

typedef std::shared_ptr<TopicTypeBase> Ptr;
typedef std::map<PyObject*, Ptr> TopicTypes;
Expand Down Expand Up @@ -215,7 +220,7 @@ class TopicType : public TopicTypeBase {
DDS::WaitSet_var ws = new DDS::WaitSet;
ws->attach_condition(read_condition);
DDS::ConditionSeq active;
const DDS::Duration_t max_wait_time = {10, 0};
const DDS::Duration_t max_wait_time = {60, 0};
if (Errors::check_rc(ws->wait(active, max_wait_time))) {
throw Exception();
}
Expand All @@ -234,6 +239,34 @@ class TopicType : public TopicTypeBase {
return rv;
}

PyObject* write(PyObject* pywriter, PyObject* pysample)
{
DDS::DataWriter* writer = get_capsule<DDS::DataWriter>(pywriter);
if (!writer) PyErr_SetString(PyExc_Exception, "writer is a NULL pointer");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!writer) PyErr_SetString(PyExc_Exception, "writer is a NULL pointer");
if (!writer) throw Exception();

So PyErr_SetString doesn't stop this function from running. All it does is set up an exception that the interrupter will throw when it has the chance. Also get_capsule is already setting PyErr_SetString, all we would have to do here is to stop running this function, which is the purpose of throwing Exception() (with no arguments).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


DataWriter* writer_impl = DataWriter::_narrow(writer);
if (!writer_impl) {
throw Exception(
"Could not narrow writer implementation", Errors::PyOpenDDS_Error());
}

IdlType rv;
Type<IdlType>::python_to_cpp(pysample, rv);

DDS::ReturnCode_t rc = writer_impl->write(rv, DDS::HANDLE_NIL);
if (Errors::check_rc(rc)) {
throw Exception();
}
// Wait for samples to be acknowledged
DDS::Duration_t timeout = { 30, 0 };
if (writer_impl->wait_for_acknowledgments(timeout) != DDS::RETCODE_OK) {
throw Exception(
"wait_for_acknowledgments error : ", Errors::PyOpenDDS_Error());
}
Comment on lines +267 to +272
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a separate operation like it is in OpenDDS, because this could be a lengthy process depending on how much stuff is being sent and to how many peers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, it's needed as a separate function on our application too.


return pysample;
}

PyObject* get_python_class()
{
return Type<IdlType>::get_python_class();
Expand Down
79 changes: 65 additions & 14 deletions pyopendds/dev/itl2py/CppOutput.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from jinja2 import Environment

from .ast import PrimitiveType, StructType, EnumType
from .ast import PrimitiveType, StructType, EnumType, SequenceType
from .Output import Output


Expand All @@ -13,6 +13,8 @@ def cpp_type_name(type_node):
return type_node.kind.name
elif isinstance(type_node, (StructType, EnumType)):
return cpp_name(type_node.name.parts)
elif isinstance(type_node, (SequenceType)):
return cpp_name(type_node.name.parts);
else:
raise NotImplementedError

Expand Down Expand Up @@ -44,33 +46,85 @@ def visit_struct(self, struct_type):
struct_to_lines = [
'Ref field_value;',
]
struct_from_lines = []
struct_from_lines = [
'Ref field_value;',
]
for field_name, field_node in struct_type.fields.items():
to_lines = []
from_lines = []
pyopendds_type = ''
is_string = isinstance(field_node.type_node, PrimitiveType) and \
field_node.type_node.is_string()
is_sequence = isinstance(field_node.type_node, SequenceType)

if is_sequence:
to_lines = [
'Ref field_elem;',
'field_value = PyList_New(0);',
'for (int i = 0; i < cpp.{field_name}.length(); i++) {{',
' {pyopendds_type} elem = cpp.{field_name}[i];',
' field_elem = nullptr;',
' Type<{pyopendds_type}>::cpp_to_python(elem',
' #ifdef CPP11_IDL',
' ()',
' #endif',
' , *field_elem' + (', "{default_encoding}"' if is_string else '') + ');',
' PyList_Append(*field_value, *field_elem);',
'}}'
]
else:
to_lines = [
'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}',
'#ifdef CPP11_IDL',
' ()',
'#endif',
' , *field_value'
+ (', "{default_encoding}"' if is_string else '') + ');',
]

to_lines = [
'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}',
'#ifdef CPP11_IDL',
' ()',
'#endif',
' , *field_value'
+ (', "{default_encoding}"' if is_string else '') + ');',
from_lines = [
'if (PyObject_HasAttrString(py, "{field_name}")) {{',
' *field_value = PyObject_GetAttrString(py, "{field_name}");',
'}}',
'if (!field_value) {{',
' throw Exception();',
'}}'
]

pyopendds_type = cpp_type_name(field_node.type_node)

if to_lines:
to_lines.extend([
'if (!field_value || PyObject_SetAttrString('
'if (!field_value || PyObject_SetAttrString(',
'py, "{field_name}", *field_value)) {{',
' throw Exception();',
'}}'
])

if from_lines:
if is_sequence:
from_lines.extend([
'cpp.{field_name}.length(PyList_Size(*field_value));',
'for (int i = 0; i < PyList_Size(*field_value); i++) {{',
' ::ContTrajSegment elem = cpp.{field_name}[i];',
' Type<{pyopendds_type}>::python_to_cpp(PyList_GetItem(*field_value, i), elem',
'#ifdef CPP11_IDL',
' ()',
'#endif',
' ' + (', "{default_encoding}"' if is_string else '') + ');',
' cpp.{field_name}[i] = elem;',
'}}'
])
else:
from_lines.extend([
'Type<{pyopendds_type}>::python_to_cpp(*field_value, cpp.{field_name}',
'#ifdef CPP11_IDL',
' ()',
'#endif',
' '
+ (', "{default_encoding}"' if is_string else '') + ');'
])

def line_process(lines):
return [''] + [
s.format(
Expand Down Expand Up @@ -107,9 +161,6 @@ def visit_enum(self, enum_type):
'args = PyTuple_Pack(1, PyLong_FromLong(static_cast<long>(cpp)));',
]),
'to_lines': '',
'from_lines': '\n'.join([
'',
'// left unimplemented'
]),
'from_lines': '',
'is_topic_type': False,
})
6 changes: 4 additions & 2 deletions pyopendds/dev/itl2py/PythonOutput.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .ast import PrimitiveType, StructType, EnumType
from .ast import PrimitiveType, StructType, EnumType, SequenceType
from .Output import Output


Expand Down Expand Up @@ -72,6 +72,8 @@ def get_python_default_value_string(self, field_type):
return type_name + '()'
elif isinstance(field_type, EnumType):
return type_name + '.' + field_type.default_member
elif isinstance(field_type, SequenceType):
return 'field(default_factory=list)'
else:
raise NotImplementedError(repr(field_type) + " is not supported")

Expand All @@ -98,4 +100,4 @@ def visit_enum(self, enum_type):
dict(name=name, value=value) for name, value in enum_type.members.items()
],
),
))
))
3 changes: 3 additions & 0 deletions pyopendds/dev/itl2py/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ def __repr__(self):
return self.repr_template(repr(self.base_type)
+ ("max " + str(self.max_count) if self.max_count else "no max"))

def repr_name(self):
if self.name:
return '::' + self.name.join('::') + '::_tao_seq_' + self.base_type + '_'

class NodeVisitor:

Expand Down
17 changes: 13 additions & 4 deletions pyopendds/dev/itl2py/itl.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def parse_string(details):


def parse_sequence(types, details):
base_type = parse_type(types, details["type"])
base_type = parse_type(types, list(types)[0])
sequence_max_count = details.get("capacity", None)
array_dimensions = details.get("size", None)
if array_dimensions is not None:
Expand All @@ -86,9 +86,16 @@ def parse_sequence(types, details):
def parse_record(types, details):
struct_type = StructType()
for field_dict in details['fields']:
struct_type.add_field(
field_dict['name'], parse_type(types, field_dict['type']),
field_dict.get('optional', False))
if 'sequence' in field_dict['type']:
sequence = parse_sequence(types, {'type': field_dict['type'], 'capacity': 1, 'size': None})
sequence.set_name(itl_name=sequence.base_type.name.itl_name)
struct_type.add_field(
field_dict['name'], sequence,
field_dict.get('optional', False))
else:
struct_type.add_field(
field_dict['name'], parse_type(types, field_dict['type']),
field_dict.get('optional', False))
return struct_type


Expand Down Expand Up @@ -132,6 +139,8 @@ def parse_type(types, details):
if details_type is str:
if details in types:
return types[details]
elif 'sequence' in details :
return parse_sequence(types, {'type':types, 'capacity': 1, 'size': None})
else:
raise ValueError("Invalid Type: " + details)
elif details_type is dict:
Expand Down
Loading