First off this is NOT about XML serialization - its just a good use case to use as an example.
I want to be able to markup properties within a class then later have a serializer be able to read that meta data and make use of it.
I've looked at Class with a registry of methods based on decorators, but the approach here only seems to work with methods (not properties) and I also need to accept arguments into the markup decorators (OutputAsXmlAttribute
& OutputAsXmlElement
).
class MyXmlInvoice(object):
@OutputAsXmlAttribute('invoiceNo', 'http://my.company.com/invoice-schema')
@property
def id(self) -> 'str':
pass
@id.setter
def id(self, value: str):
pass
@OutputAsXmlElement('Price', 'http://my.company.com/invoice-schema')
@property
def price(self) -> 'int':
pass
@price.setter
def price(self, value: int):
pass
# This is the use case for the objects where MyXmlSerializer uses
# the meta data on price & id to influence the serialization
xmlInvoice = MyXmlInvoice()
xmlInvoice.price = 3
xmlInvoice.id = "0001"
xmlSerializer = MyXmlSerializer()
xmlData = xmlSerializer.Serialize(xmlInvoice)
A Python function is just an object, and can receive additional attributes. Unfortunately, properties cannot so you must unwrap them. That means that you could start with:
def OutputAsXmlElement(label, schema):
def deco(p):
f = p.fget if isinstance(p, property) else p
f._label = label
f._schema = schema
f._iselement = True
return p
return deco
def OutputAsXmlAttribute(label, schema):
def deco(p):
f = p.fget if isinstance(p, property) else p
f._label = label
f._schema = schema
f._iselement = False
return p
return deco
Then through the inspect
module, you can access those special attributes. For example here is a way to find all decorated members of your example class, with their label and schema:
for x, y in ((x,y) for x,y in inspect.getmembers(xmlInvoice.__class__)
if not x.startswith('__')):
if isinstance(y, property):
f = y.fget
else:
f = y
print(x, getattr(f, '_label', None), getattr(f, '_schema', None), getattr(f, '_element', None))