core.py
Go to the documentation of this file.
2 import sys
3 import copy
4 
5 # @todo Get rid of "import *"
6 # @todo Make this work with decorators
7 
8 # Is this reflection or serialization? I think it's serialization...
9 # Rename?
10 
11 # Do parent operations after, to allow child to 'override' parameters?
12  # Need to make sure that duplicate entires do not get into the 'unset*' lists
13 
14 def reflect(cls, *args, **kwargs):
15  """ Simple wrapper to add XML reflection to an xml_reflection.Object class """
16  cls.XML_REFL = Reflection(*args, **kwargs)
17 
18 # Rename 'write_xml' to 'write_xml' to have paired 'load/dump', and make 'pre_dump' and 'post_load'?
19 # When dumping to yaml, include tag name?
20 
21 # How to incorporate line number and all that jazz?
22 def on_error(message):
23  """ What to do on an error. This can be changed to raise an exception. """
24  print(message, file=sys.stderr)
25 
26 skip_default = True
27 #defaultIfMatching = True # Not implemeneted yet
28 
29 # Registering Types
30 value_types = {}
31 value_type_prefix = ''
32 
33 def start_namespace(namespace):
34  """
35  Basic mechanism to prevent conflicts for string types for URDF and SDF
36  @note Does not handle nesting!
37  """
38  global value_type_prefix
39  value_type_prefix = namespace + '.'
40 
42  global value_type_prefix
43  value_type_prefix = ''
44 
45 def add_type(key, value):
46  if isinstance(key, str):
47  key = value_type_prefix + key
48  assert key not in value_types
49  value_types[key] = value
50 
51 def get_type(cur_type):
52  """ Can wrap value types if needed """
53  if value_type_prefix and isinstance(cur_type, str):
54  # See if it exists in current 'namespace'
55  curKey = value_type_prefix + cur_type
56  value_type = value_types.get(curKey)
57  else:
58  value_type = None
59  if value_type is None:
60  # Try again, in 'global' scope
61  value_type = value_types.get(cur_type)
62  if value_type is None:
63  value_type = make_type(cur_type)
64  add_type(cur_type, value_type)
65  return value_type
66 
67 def make_type(cur_type):
68  if isinstance(cur_type, ValueType):
69  return cur_type
70  elif isinstance(cur_type, str):
71  if cur_type.startswith('vector'):
72  extra = cur_type[6:]
73  if extra:
74  count = float(extra)
75  else:
76  count = None
77  return VectorType(count)
78  else:
79  raise Exception("Invalid value type: {}".format(cur_type))
80  elif cur_type == list:
81  return ListType()
82  elif issubclass(cur_type, Object):
83  return ObjectType(cur_type)
84  elif cur_type in [str, float]:
85  return BasicType(cur_type)
86  else:
87  raise Exception("Invalid type: {}".format(cur_type))
88 
89 class ValueType(object):
90  """ Primitive value type """
91  def from_xml(self, node):
92  return self.from_string(node.text)
93 
94  def write_xml(self, node, value):
95  """ If type has 'write_xml', this function should expect to have it's own XML already created
96  i.e., In Axis.to_sdf(self, node), 'node' would be the 'axis' element.
97  @todo Add function that makes an XML node completely independently?"""
98  node.text = self.to_string(value)
99 
100  def equals(self, a, b):
101  return a == b
102 
103 class BasicType(ValueType):
104  def __init__(self, cur_type):
105  self.type = cur_type
106  def to_string(self, value):
107  return str(value)
108  def from_string(self, value):
109  return self.type(value)
110 
111 class ListType(ValueType):
112  def to_string(self, values):
113  return ' '.join(values)
114  def from_string(self, text):
115  return text.split()
116  def equals(self, aValues, bValues):
117  return len(aValues) == len(bValues) and all(a == b for (a, b) in zip(aValues, bValues))
118 
119 class VectorType(ListType):
120  def __init__(self, count = None):
121  self.count = count
122 
123  def check(self, values):
124  if self.count is not None:
125  assert len(values) == self.count, "Invalid vector length"
126 
127  def to_string(self, values):
128  self.check(values)
129  raw = list(map(str, values))
130  return ListType.to_string(self, raw)
131 
132  def from_string(self, text):
133  raw = ListType.from_string(self, text)
134  self.check(raw)
135  return list(map(float, raw))
136 
137 class RawType(ValueType):
138  """ Simple, raw XML value. Need to bugfix putting this back into a document """
139  def from_xml(self, node):
140  return node
141 
142  def write_xml(self, node, value):
143  #!!! HACK Trying to insert an element at root level seems to screw up pretty printing
144  children = xml_children(value)
145  list(map(node.append, children))
146 
148  def __init__(self, attribute, value_type):
149  self.attribute = attribute
150  self.value_type = get_type(value_type)
151  def from_xml(self, node):
152  text = node.get(self.attribute)
153  return self.value_type.from_string(text)
154  def write_xml(self, node, value):
155  text = self.value_type.to_string(value)
156  node.set(self.attribute, text)
157 
158 class ObjectType(ValueType):
159  def __init__(self, cur_type):
160  self.type = cur_type
161 
162  def from_xml(self, node):
163  obj = self.type()
164  obj.read_xml(node)
165  return obj
166 
167  def write_xml(self, node, obj):
168  obj.write_xml(node)
169 
170 class FactoryType(ValueType):
171  def __init__(self, name, typeMap):
172  self.name = name
173  self.typeMap = typeMap
174  self.nameMap = {}
175  for (key, value) in typeMap.items():
176  # Reverse lookup
177  self.nameMap[value] = key
178 
179  def from_xml(self, node):
180  cur_type = self.typeMap.get(node.tag)
181  if cur_type is None:
182  raise Exception("Invalid {} tag: {}".format(self.name, node.tag))
183  value_type = get_type(cur_type)
184  return value_type.from_xml(node)
185 
186  def get_name(self, obj):
187  cur_type = type(obj)
188  name = self.nameMap.get(cur_type)
189  if name is None:
190  raise Exception("Invalid {} type: {}".format(self.name, cur_type))
191  return name
192 
193  def write_xml(self, node, obj):
194  obj.write_xml(node)
195 
196 
198  def __init__(self, name, typeOrder):
199  self.name = name
200  assert len(typeOrder) > 0
201  self.type_order = typeOrder
202 
203  def from_xml(self, node):
204  error_set = []
205  for value_type in self.type_order:
206  try:
207  return value_type.from_xml(node)
208  except Exception as e:
209  error_set.append((value_type, e))
210  # Should have returned, we encountered errors
211  out = "Could not perform duck-typed parsing."
212  for (value_type, e) in error_set:
213  out += "\nValue Type: {}\nException: {}\n".format(value_type, e)
214  raise Exception(out)
215 
216  def write_xml(self, node, obj):
217  obj.write_xml(node)
218 
219 class Param(object):
220  """ Mirroring Gazebo's SDF api
221 
222  @param xml_var: Xml name
223  @todo If the value_type is an object with a tag defined in it's reflection, allow it to act as the default tag name?
224  @param var: Python class variable name. By default it's the same as the XML name
225  """
226  def __init__(self, xml_var, value_type, required = True, default = None, var = None):
227  self.xml_var = xml_var
228  if var is None:
229  self.var = xml_var
230  else:
231  self.var = var
232  self.type = None
233  self.value_type = get_type(value_type)
234  self.default = default
235  if required:
236  assert default is None, "Default does not make sense for a required field"
237  self.required = required
238  self.is_aggregate = False
239 
240  def set_default(self):
241  if self.required:
242  raise Exception("Required {} not set in XML: {}".format(self.type, self.xml_var))
243  elif not skip_default:
244  setattr(obj, self.var, self.default)
245 
246 class Attribute(Param):
247  def __init__(self, xml_var, value_type, required = True, default = None, var = None):
248  Param.__init__(self, xml_var, value_type, required, default, var)
249  self.type = 'attribute'
250 
251  def set_from_string(self, obj, value):
252  """ Node is the parent node in this case """
253  # Duplicate attributes cannot occur at this point
254  setattr(obj, self.var, self.value_type.from_string(value))
255 
256  def add_to_xml(self, obj, node):
257  value = getattr(obj, self.var)
258  # Do not set with default value if value is None
259  if value is None:
260  if self.required:
261  raise Exception("Required attribute not set in object: {}".format(self.var))
262  elif not skip_default:
263  value = self.default
264  # Allow value type to handle None?
265  if value is not None:
266  node.set(self.xml_var, self.value_type.to_string(value))
267 
268 # Add option if this requires a header? Like <joints> <joint/> .... </joints> ??? Not really... This would be a specific list type, not really aggregate
269 
270 class Element(Param):
271  def __init__(self, xml_var, value_type, required = True, default = None, var = None, is_raw = False):
272  Param.__init__(self, xml_var, value_type, required, default, var)
273  self.type = 'element'
274  self.is_raw = is_raw
275 
276  def set_from_xml(self, obj, node):
277  value = self.value_type.from_xml(node)
278  setattr(obj, self.var, value)
279 
280  def add_to_xml(self, obj, parent):
281  value = getattr(obj, self.xml_var)
282  if value is None:
283  if self.required:
284  raise Exception("Required element not defined in object: {}".format(self.var))
285  elif not skip_default:
286  value = self.default
287  if value is not None:
288  self.add_scalar_to_xml(parent, value)
289 
290  def add_scalar_to_xml(self, parent, value):
291  if self.is_raw:
292  node = parent
293  else:
294  node = node_add(parent, self.xml_var)
295  self.value_type.write_xml(node, value)
296 
297 
299  def __init__(self, xml_var, value_type, var = None, is_raw = False):
300  if var is None:
301  var = xml_var + 's'
302  Element.__init__(self, xml_var, value_type, required = False, var = var, is_raw = is_raw)
303  self.is_aggregate = True
304 
305  def add_from_xml(self, obj, node):
306  value = self.value_type.from_xml(node)
307  obj.add_aggregate(self.xml_var, value)
308 
309  def set_default(self):
310  pass
311 
312 
313 class Info:
314  """ Small container for keeping track of what's been consumed """
315  def __init__(self, node):
316  self.attributes = list(node.attrib.keys())
317  self.children = xml_children(node)
318 
319 class Reflection(object):
320  def __init__(self, params = [], parent_cls = None, tag = None):
321  """ Construct a XML reflection thing
322  @param parent_cls: Parent class, to use it's reflection as well.
323  @param tag: Only necessary if you intend to use Object.write_xml_doc()
324  This does not override the name supplied in the reflection definition thing.
325  """
326  if parent_cls is not None:
327  self.parent = parent_cls.XML_REFL
328  else:
329  self.parent = None
330  self.tag = tag
331 
332  # Laziness for now
333  attributes = []
334  elements = []
335  for param in params:
336  if isinstance(param, Element):
337  elements.append(param)
338  else:
339  attributes.append(param)
340 
341  self.vars = []
342  self.paramMap = {}
343 
344  self.attributes = attributes
345  self.attribute_map = {}
346  self.required_attribute_names = []
347  for attribute in attributes:
348  self.attribute_map[attribute.xml_var] = attribute
349  self.paramMap[attribute.xml_var] = attribute
350  self.vars.append(attribute.var)
351  if attribute.required:
352  self.required_attribute_names.append(attribute.xml_var)
353 
354  self.elements = []
355  self.element_map = {}
356  self.required_element_names = []
357  self.aggregates = []
358  self.scalars = []
359  self.scalarNames = []
360  for element in elements:
361  self.element_map[element.xml_var] = element
362  self.paramMap[element.xml_var] = element
363  self.vars.append(element.var)
364  if element.required:
365  self.required_element_names.append(element.xml_var)
366  if element.is_aggregate:
367  self.aggregates.append(element)
368  else:
369  self.scalars.append(element)
370  self.scalarNames.append(element.xml_var)
371 
372  def set_from_xml(self, obj, node, info = None):
373  is_final = False
374  if info is None:
375  is_final = True
376  info = Info(node)
377 
378  if self.parent:
379  self.parent.set_from_xml(obj, node, info)
380 
381  # Make this a map instead? Faster access? {name: isSet} ?
382  unset_attributes = list(self.attribute_map.keys())
383  unset_scalars = copy.copy(self.scalarNames)
384 
385  # Better method? Queues?
386  for xml_var in copy.copy(info.attributes):
387  attribute = self.attribute_map.get(xml_var)
388  if attribute is not None:
389  value = node.attrib[xml_var]
390  attribute.set_from_string(obj, value)
391  unset_attributes.remove(xml_var)
392  info.attributes.remove(xml_var)
393 
394  # Parse unconsumed nodes
395  for child in copy.copy(info.children):
396  tag = child.tag
397  element = self.element_map.get(tag)
398  if element is not None:
399  if element.is_aggregate:
400  element.add_from_xml(obj, child)
401  else:
402  if tag in unset_scalars:
403  element.set_from_xml(obj, child)
404  unset_scalars.remove(tag)
405  else:
406  on_error("Scalar element defined multiple times: {}".format(tag))
407  info.children.remove(child)
408 
409  for attribute in map(self.attribute_map.get, unset_attributes):
410  attribute.set_default()
411 
412  for element in map(self.element_map.get, unset_scalars):
413  element.set_default()
414 
415  if is_final:
416  for xml_var in info.attributes:
417  on_error('Unknown attribute: {}'.format(xml_var))
418  for node in info.children:
419  on_error('Unknown tag: {}'.format(node.tag))
420 
421  def add_to_xml(self, obj, node):
422  if self.parent:
423  self.parent.add_to_xml(obj, node)
424  for attribute in self.attributes:
425  attribute.add_to_xml(obj, node)
426  for element in self.scalars:
427  element.add_to_xml(obj, node)
428  # Now add in aggregates
429  if self.aggregates:
430  obj.add_aggregates_to_xml(node)
431 
432 class Object(YamlReflection):
433  """ Raw python object for yaml / xml representation """
434  XML_REFL = None
435 
436  def get_refl_vars(self):
437  return self.XML_REFL.vars
438 
439  def check_valid(self):
440  pass
441 
442  def pre_write_xml(self):
443  """ If anything needs to be converted prior to dumping to xml
444  i.e., getting the names of objects and such """
445  pass
446 
447  def write_xml(self, node):
448  """ Adds contents directly to XML node """
449  self.check_valid()
450  self.pre_write_xml()
451  self.XML_REFL.add_to_xml(self, node)
452 
453  def to_xml(self):
454  """ Creates an overarching tag and adds its contents to the node """
455  tag = self.XML_REFL.tag
456  assert tag is not None, "Must define 'tag' in reflection to use this function"
457  doc = etree.Element(tag)
458  self.write_xml(doc)
459  return doc
460 
461  def to_xml_string(self):
462  return xml_string(self.to_xml())
463 
464  def post_read_xml(self):
465  pass
466 
467  def read_xml(self, node):
468  self.XML_REFL.set_from_xml(self, node)
469  self.post_read_xml()
470  self.check_valid()
471 
472  @classmethod
473  def from_xml(cls, node):
474  cur_type = get_type(cls)
475  return cur_type.from_xml(node)
476 
477  @classmethod
478  def from_xml_string(cls, xml_string):
479  node = etree.fromstring(xml_string)
480  return cls.from_xml(node)
481 
482  @classmethod
483  def from_xml_file(cls, file_path):
484  xml_string= open(file_path, 'r').read()
485  return cls.from_xml_string(xml_string)
486 
487  # Confusing distinction between loading code in object and reflection registry thing...
488 
489  def get_aggregate_list(self, xml_var):
490  var = self.XML_REFL.paramMap[xml_var].var
491  values = getattr(self, var)
492  assert isinstance(values, list)
493  return values
494 
495  def aggregate_init(self):
496  """ Must be called in constructor! """
497  self.aggregate_order = []
498  # Store this info in the loaded object??? Nah
499  self.aggregate_type = {}
500 
501  def add_aggregate(self, xml_var, obj):
502  """ NOTE: One must keep careful track of aggregate types for this system.
503  Can use 'lump_aggregates()' before writing if you don't care. """
504  self.get_aggregate_list(xml_var).append(obj)
505  self.aggregate_order.append(obj)
506  self.aggregate_type[obj] = xml_var
507 
508  def add_aggregates_to_xml(self, node):
509  for value in self.aggregate_order:
510  typeName = self.aggregate_type[value]
511  element = self.XML_REFL.element_map[typeName]
512  element.add_scalar_to_xml(node, value)
513 
514  def remove_aggregate(self, obj):
515  self.aggregate_order.remove(obj)
516  xml_var = self.aggregate_type[obj]
517  del self.aggregate_type[obj]
518  self.get_aggregate_list(xml_var).remove(obj)
519 
520  def lump_aggregates(self):
521  """ Put all aggregate types together, just because """
522  self.aggregate_init()
523  for param in self.XML_REFL.aggregates:
524  for obj in self.get_aggregate_list(param.xml_var):
525  self.add_aggregate(param.var, obj)
526 
527  """ Compatibility """
528  def parse(self, xml_string):
529  node = etree.fromstring(xml_string)
530  self.read_xml(node)
531  return self
532 
533 # Really common types
534 add_type('element_name', SimpleElementType('name', str))
535 add_type('element_value', SimpleElementType('value', float))
536 
537 # Add in common vector types so they aren't absorbed into the namespaces
538 get_type('vector3')
539 get_type('vector4')
540 get_type('vector6')
def reflect(cls, args, kwargs)


pr2_calibration_launch
Author(s): Vijay Pradeep
autogenerated on Tue Jun 1 2021 02:50:59