pyloxi: create inheritance hierarchy
Virtual classes are generated but currently only serve as superclasses.
Somehow this works without topologically sorting the classes.
diff --git a/py_gen/codegen.py b/py_gen/codegen.py
index 4eafa48..f135f18 100644
--- a/py_gen/codegen.py
+++ b/py_gen/codegen.py
@@ -25,7 +25,7 @@
# EPL for the specific language governing permissions and limitations
# under the EPL.
-from collections import namedtuple
+from collections import defaultdict
import loxi_globals
import struct
import template_utils
@@ -34,74 +34,74 @@
import oftype
from loxi_ir import *
-ofclasses_by_version = {}
+modules_by_version = {}
-# Return the name for the generated Python class
-def generate_pyname(cls):
- if utils.class_is_action(cls):
- return cls[10:]
- elif utils.class_is_oxm(cls):
- return cls[7:]
- elif utils.class_is_meter_band(cls):
- return cls[14:]
- elif utils.class_is_instruction(cls):
- return cls[15:]
- else:
- return cls[3:]
+# Map from inheritance root to module name
+roots = {
+ 'of_header': 'message',
+ 'of_action': 'action',
+ 'of_oxm': 'oxm',
+ 'of_instruction': 'instruction',
+ 'of_meter_band': 'meter_band',
+}
+
+# Return the module and class names for the generated Python class
+def generate_pyname(ofclass):
+ for root, module_name in roots.items():
+ if ofclass.name == root:
+ return module_name, module_name
+ elif ofclass.is_instanceof(root):
+ if root == 'of_header':
+ # The input files don't prefix message names
+ return module_name, ofclass.name[3:]
+ else:
+ return module_name, ofclass.name[len(root)+1:]
+ return 'common', ofclass.name[3:]
# Create intermediate representation, extended from the LOXI IR
def build_ofclasses(version):
- ofclasses = []
+ modules = defaultdict(list)
for ofclass in loxi_globals.ir[version].classes:
- if ofclass.virtual:
- continue
-
- ofclass.pyname = generate_pyname(ofclass.name)
- ofclasses.append(ofclass)
-
- return ofclasses
+ module_name, ofclass.pyname = generate_pyname(ofclass)
+ modules[module_name].append(ofclass)
+ return modules
def generate_init(out, name, version):
util.render_template(out, 'init.py', version=version)
def generate_action(out, name, version):
- ofclasses = [x for x in ofclasses_by_version[version]
- if utils.class_is_action(x.name)]
- util.render_template(out, 'action.py', ofclasses=ofclasses, version=version)
+ util.render_template(out, 'action.py',
+ ofclasses=modules_by_version[version]['action'],
+ version=version)
def generate_oxm(out, name, version):
- ofclasses = [x for x in ofclasses_by_version[version]
- if utils.class_is_oxm(x.name)]
- util.render_template(out, 'oxm.py', ofclasses=ofclasses, version=version)
+ util.render_template(out, 'oxm.py',
+ ofclasses=modules_by_version[version]['oxm'],
+ version=version)
def generate_common(out, name, version):
- ofclasses = [x for x in ofclasses_by_version[version]
- if not utils.class_is_message(x.name)
- and not utils.class_is_action(x.name)
- and not utils.class_is_instruction(x.name)
- and not utils.class_is_meter_band(x.name)
- and not utils.class_is_oxm(x.name)
- and not utils.class_is_list(x.name)]
- util.render_template(out, 'common.py', ofclasses=ofclasses, version=version)
+ util.render_template(out, 'common.py',
+ ofclasses=modules_by_version[version]['common'],
+ version=version)
def generate_const(out, name, version):
util.render_template(out, 'const.py', version=version,
enums=loxi_globals.ir[version].enums)
def generate_instruction(out, name, version):
- ofclasses = [x for x in ofclasses_by_version[version]
- if utils.class_is_instruction(x.name)]
- util.render_template(out, 'instruction.py', ofclasses=ofclasses, version=version)
+ util.render_template(out, 'instruction.py',
+ ofclasses=modules_by_version[version]['instruction'],
+ version=version)
def generate_message(out, name, version):
- ofclasses = [x for x in ofclasses_by_version[version]
- if utils.class_is_message(x.name)]
- util.render_template(out, 'message.py', ofclasses=ofclasses, version=version)
+ util.render_template(out, 'message.py',
+ ofclasses=modules_by_version[version]['message'],
+ version=version)
def generate_meter_band(out, name, version):
- ofclasses = [x for x in ofclasses_by_version[version]
- if utils.class_is_meter_band(x.name)]
- util.render_template(out, 'meter_band.py', ofclasses=ofclasses, version=version)
+ util.render_template(out, 'meter_band.py',
+ ofclasses=modules_by_version[version]['meter_band'],
+ version=version)
def generate_pp(out, name, version):
util.render_template(out, 'pp.py')
@@ -111,4 +111,4 @@
def init():
for version in loxi_globals.OFVersions.target_versions:
- ofclasses_by_version[version] = build_ofclasses(version)
+ modules_by_version[version] = build_ofclasses(version)
diff --git a/py_gen/templates/_ofclass.py b/py_gen/templates/_ofclass.py
index 53d6d7a..610e5a8 100644
--- a/py_gen/templates/_ofclass.py
+++ b/py_gen/templates/_ofclass.py
@@ -1,8 +1,9 @@
+:: superclass_pyname = ofclass.superclass.pyname if ofclass.superclass else "loxi.OFObject"
:: from loxi_ir import *
:: import py_gen.oftype
:: type_members = [m for m in ofclass.members if type(m) == OFTypeMember]
:: normal_members = [m for m in ofclass.members if type(m) == OFDataMember]
-class ${ofclass.pyname}(${superclass}):
+class ${ofclass.pyname}(${superclass_pyname}):
:: for m in type_members:
${m.name} = ${m.value}
:: #endfor
@@ -39,12 +40,5 @@
:: #endfor
return True
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def show(self):
- import loxi.pp
- return loxi.pp.pp(self)
-
def pretty_print(self, q):
:: include('_pretty_print.py', ofclass=ofclass)
diff --git a/py_gen/templates/_virtual_ofclass.py b/py_gen/templates/_virtual_ofclass.py
new file mode 100644
index 0000000..29f01f7
--- /dev/null
+++ b/py_gen/templates/_virtual_ofclass.py
@@ -0,0 +1,3 @@
+:: superclass_pyname = ofclass.superclass.pyname if ofclass.superclass else "loxi.OFObject"
+class ${ofclass.pyname}(${superclass_pyname}):
+ pass
diff --git a/py_gen/templates/action.py b/py_gen/templates/action.py
index 77a1d9f..2f1d020 100644
--- a/py_gen/templates/action.py
+++ b/py_gen/templates/action.py
@@ -48,12 +48,12 @@
return parser(reader)
return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
-class Action(object):
- type = None # override in subclass
- pass
-
:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="Action")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
:: #endfor
@@ -72,8 +72,9 @@
raise loxi.ProtocolError("unexpected BSN experimenter subtype %#x" % subtype)
parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
:: sort_key = lambda x: x.member_by_name('type').value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: msgtype_groups = itertools.groupby(sorted(concrete_ofclasses, key=sort_key), sort_key)
:: for (k, v) in msgtype_groups:
:: k = util.constant_for_value(version, "ofp_action_type", k)
:: v = list(v)
@@ -85,7 +86,7 @@
:: #endfor
}
-:: experimenter_ofclasses = [x for x in ofclasses if x.member_by_name('type').value == 0xffff]
+:: experimenter_ofclasses = [x for x in concrete_ofclasses if x.member_by_name('type').value == 0xffff]
:: sort_key = lambda x: x.member_by_name('experimenter').value
:: experimenter_ofclasses.sort(key=sort_key)
:: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
diff --git a/py_gen/templates/common.py b/py_gen/templates/common.py
index 76ae631..ddffdf7 100644
--- a/py_gen/templates/common.py
+++ b/py_gen/templates/common.py
@@ -90,7 +90,11 @@
return loxi.generic_util.unpack_list(reader, wrapper)
:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="object")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
:: #endfor
diff --git a/py_gen/templates/instruction.py b/py_gen/templates/instruction.py
index 5dfafab..21ab84a 100644
--- a/py_gen/templates/instruction.py
+++ b/py_gen/templates/instruction.py
@@ -45,12 +45,12 @@
return parser(reader)
return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
-class Instruction(object):
- type = None # override in subclass
- pass
-
:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="Instruction")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
:: #endfor
@@ -67,8 +67,9 @@
raise loxi.ProtocolError("unexpected experimenter id %#x subtype %#x" % (experimenter, subtype))
parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
:: sort_key = lambda x: x.member_by_name('type').value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: msgtype_groups = itertools.groupby(sorted(concrete_ofclasses, key=sort_key), sort_key)
:: for (k, v) in msgtype_groups:
:: k = util.constant_for_value(version, "ofp_instruction_type", k)
:: v = list(v)
@@ -80,7 +81,7 @@
:: #endfor
}
-:: experimenter_ofclasses = [x for x in ofclasses if x.member_by_name('type').value == 0xffff]
+:: experimenter_ofclasses = [x for x in concrete_ofclasses if x.member_by_name('type').value == 0xffff]
:: sort_key = lambda x: x.member_by_name('experimenter').value
:: experimenter_ofclasses.sort(key=sort_key)
:: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
diff --git a/py_gen/templates/message.py b/py_gen/templates/message.py
index 2106ce7..9307676 100644
--- a/py_gen/templates/message.py
+++ b/py_gen/templates/message.py
@@ -48,13 +48,12 @@
import util
import loxi.generic_util
-class Message(object):
- version = const.OFP_VERSION
- type = None # override in subclass
- xid = None
-
:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="Message")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
:: #endfor
@@ -170,8 +169,9 @@
raise loxi.ProtocolError("unexpected experimenter %#x subtype %#x" % (experimenter, subtype))
parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
:: sort_key = lambda x: x.member_by_name('type').value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: msgtype_groups = itertools.groupby(sorted(concrete_ofclasses, key=sort_key), sort_key)
:: for (k, v) in msgtype_groups:
:: k = util.constant_for_value(version, "ofp_type", k)
:: v = list(v)
@@ -271,7 +271,7 @@
:: #endif
}
-:: experimenter_ofclasses = [x for x in ofclasses if x.member_by_name('type').value == 4]
+:: experimenter_ofclasses = [x for x in concrete_ofclasses if x.member_by_name('type').value == 4]
:: sort_key = lambda x: x.member_by_name('experimenter').value
:: experimenter_ofclasses.sort(key=sort_key)
:: grouped = itertools.groupby(experimenter_ofclasses, sort_key)
diff --git a/py_gen/templates/meter_band.py b/py_gen/templates/meter_band.py
index 3273314..ef73978 100644
--- a/py_gen/templates/meter_band.py
+++ b/py_gen/templates/meter_band.py
@@ -44,18 +44,19 @@
return parser(reader)
return loxi.generic_util.unpack_list_tlv16(reader, deserializer)
-class MeterBand(object):
- type = None # override in subclass
- pass
-
:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="MeterBand")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
:: #endfor
parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
:: sort_key = lambda x: x.member_by_name('type').value
-:: msgtype_groups = itertools.groupby(sorted(ofclasses, key=sort_key), sort_key)
+:: msgtype_groups = itertools.groupby(sorted(concrete_ofclasses, key=sort_key), sort_key)
:: for (k, v) in msgtype_groups:
:: k = util.constant_for_value(version, "ofp_meter_band_type", k)
:: v = list(v)
diff --git a/py_gen/templates/oxm.py b/py_gen/templates/oxm.py
index 176a74c..4adfab0 100644
--- a/py_gen/templates/oxm.py
+++ b/py_gen/templates/oxm.py
@@ -48,18 +48,19 @@
def unpack_list(reader):
return loxi.generic_util.unpack_list(reader, unpack)
-class OXM(object):
- type_len = None # override in subclass
- pass
-
:: for ofclass in ofclasses:
-:: include('_ofclass.py', ofclass=ofclass, superclass="OXM")
+:: if ofclass.virtual:
+:: include('_virtual_ofclass.py', ofclass=ofclass)
+:: else:
+:: include('_ofclass.py', ofclass=ofclass)
+:: #endif
:: #endfor
parsers = {
+:: concrete_ofclasses = [x for x in ofclasses if not x.virtual]
:: key = lambda x: x.member_by_name('type_len').value
-:: for ofclass in sorted(ofclasses, key=key):
+:: for ofclass in sorted(concrete_ofclasses, key=key):
${key(ofclass)} : ${ofclass.pyname}.unpack,
:: #endfor
}
diff --git a/py_gen/templates/toplevel_init.py b/py_gen/templates/toplevel_init.py
index b170bd8..e5493a5 100644
--- a/py_gen/templates/toplevel_init.py
+++ b/py_gen/templates/toplevel_init.py
@@ -68,3 +68,17 @@
def unimplemented(msg):
raise Unimplemented(msg)
+
+class OFObject(object):
+ """
+ Superclass of all OpenFlow classes
+ """
+ def __init__(self, *args):
+ raise NotImplementedError("cannot instantiate abstract class")
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def show(self):
+ import loxi.pp
+ return loxi.pp.pp(self)
diff --git a/py_gen/tests/of10.py b/py_gen/tests/of10.py
index c4725e3..818f123 100644
--- a/py_gen/tests/of10.py
+++ b/py_gen/tests/of10.py
@@ -30,6 +30,7 @@
from testutil import add_datafiles_tests
try:
+ import loxi
import loxi.of10 as ofp
from loxi.generic_util import OFReader
except ImportError:
@@ -195,11 +196,11 @@
msg = ofp.message.parse_message(buf)
assert(msg.xid == 0x12345678)
- # Get a list of all message classes
+ # Get a list of all concrete message classes
test_klasses = [x for x in ofp.message.__dict__.values()
if type(x) == type
- and issubclass(x, ofp.message.Message)
- and x != ofp.message.Message]
+ and issubclass(x, ofp.message.message)
+ and hasattr(x, 'pack')]
for klass in test_klasses:
self.assertIsInstance(ofp.message.parse_message(klass(xid=1).pack()), klass)
@@ -228,7 +229,9 @@
mods = [ofp.action,ofp.message,ofp.common]
self.klasses = [klass for mod in mods
for klass in mod.__dict__.values()
- if hasattr(klass, 'show')]
+ if isinstance(klass, type) and
+ issubclass(klass, loxi.OFObject) and
+ hasattr(klass, 'pack')]
self.klasses.sort(key=lambda x: str(x))
def test_serialization(self):
@@ -248,7 +251,7 @@
def test_parse_message(self):
expected_failures = []
for klass in self.klasses:
- if not issubclass(klass, ofp.message.Message):
+ if not issubclass(klass, ofp.message.message):
continue
def fn():
obj = klass(xid=42)
diff --git a/py_gen/tests/of11.py b/py_gen/tests/of11.py
index 07a5437..b30a40c 100644
--- a/py_gen/tests/of11.py
+++ b/py_gen/tests/of11.py
@@ -28,6 +28,7 @@
import unittest
try:
+ import loxi
import loxi.of11 as ofp
except ImportError:
exit("loxi package not found. Try setting PYTHONPATH.")
@@ -65,7 +66,9 @@
mods = [ofp.action,ofp.message,ofp.common]
self.klasses = [klass for mod in mods
for klass in mod.__dict__.values()
- if hasattr(klass, 'show')]
+ if isinstance(klass, type) and
+ issubclass(klass, loxi.OFObject) and
+ hasattr(klass, 'pack')]
self.klasses.sort(key=lambda x: str(x))
def test_serialization(self):
@@ -85,7 +88,7 @@
def test_parse_message(self):
expected_failures = []
for klass in self.klasses:
- if not issubclass(klass, ofp.message.Message):
+ if not issubclass(klass, ofp.message.message):
continue
def fn():
obj = klass(xid=42)
diff --git a/py_gen/tests/of12.py b/py_gen/tests/of12.py
index 98d999b..fcdb0cf 100644
--- a/py_gen/tests/of12.py
+++ b/py_gen/tests/of12.py
@@ -29,6 +29,7 @@
from testutil import add_datafiles_tests
try:
+ import loxi
import loxi.of12 as ofp
except ImportError:
exit("loxi package not found. Try setting PYTHONPATH.")
@@ -74,7 +75,9 @@
mods = [ofp.action,ofp.message,ofp.common,ofp.oxm]
self.klasses = [klass for mod in mods
for klass in mod.__dict__.values()
- if hasattr(klass, 'show')]
+ if isinstance(klass, type) and
+ issubclass(klass, loxi.OFObject) and
+ hasattr(klass, 'pack')]
self.klasses.sort(key=lambda x: str(x))
def test_serialization(self):
@@ -96,7 +99,7 @@
def test_parse_message(self):
expected_failures = []
for klass in self.klasses:
- if not issubclass(klass, ofp.message.Message):
+ if not issubclass(klass, ofp.message.message):
continue
def fn():
obj = klass(xid=42)
diff --git a/py_gen/tests/of13.py b/py_gen/tests/of13.py
index 8c18f41..07b0ef0 100644
--- a/py_gen/tests/of13.py
+++ b/py_gen/tests/of13.py
@@ -30,6 +30,7 @@
from testutil import add_datafiles_tests
try:
+ import loxi
import loxi.of13 as ofp
from loxi.generic_util import OFReader
except ImportError:
@@ -88,7 +89,9 @@
mods = [ofp.action,ofp.message,ofp.common,ofp.oxm]
self.klasses = [klass for mod in mods
for klass in mod.__dict__.values()
- if hasattr(klass, 'show')]
+ if isinstance(klass, type) and
+ issubclass(klass, loxi.OFObject) and
+ hasattr(klass, 'pack')]
self.klasses.sort(key=lambda x: str(x))
def test_serialization(self):
@@ -120,7 +123,7 @@
ofp.message.table_features_stats_request,
]
for klass in self.klasses:
- if not issubclass(klass, ofp.message.Message):
+ if not issubclass(klass, ofp.message.message):
continue
def fn():
obj = klass(xid=42)