pysxm is a simple and extensible xml python marshaller. It comes with two simple and basic types:
- SimpleType
- ComplexType
It supports py2 and py3 and uses lxml.objectify under the hood.
pip install pysxm
In [1]: from pysxm import ComplexType
In [2]: class Person(ComplexType):
...: attrib = {'description': 'a random person'}
...: def __init__(self, fname, lname):
...: self.fname = fname
...: self.lname = lname
...:
In [3]: person = Person('token', 'black')
In [4]: print(person)
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" description="a random person">
<lname>black</lname>
<fname>token</fname>
</person>
Let's say, we want a different tag for our object. An attribute tagname or _tagname can be set to define the xml tag name of the object.
In [5]: class Person(ComplexType):
...: attrib = {'description': 'a random person'}
...: tagname = 'student'
...: def __init__(self, fname, lname):
...: self.fname = fname
...: self.lname = lname
...:
In [6]: person = Person('token', 'black')
In [7]: print(person)
<student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" description="a random person">
<lname>black</lname>
<fname>token</fname>
</student>
A sequence or _sequence (tuple or list) attribute can be set to decide of the order or the presence of an subelement in the xml.
In [8]: class Person(ComplexType):
...: attrib = {'description': 'a random person'}
...: tagname = 'student'
...: _sequence = ('city', 'fname')
...:
...: def __init__(self, fname, lname, city):
...: self.fname = fname
...: self.lname = lname
...: self.city = city
...:
In [9]: person = Person('token', 'black', 'south park')
In [10]: print(person)
<student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" description="a random person">
<city>south park</city>
<fname>token</fname>
</student>
Let's add a namespace to our object.
In [11]: class Person(ComplexType):
...: attrib = {'description': 'a random south park character'}
...: nsmap = {'sp': 'http://southpark/xml/'}
...:
...: def __init__(self, fname, lname, city):
...: self.fname = fname
...: self.lname = lname
...: self.city = city
...:
In [12]: person = Person('token', 'black', 'south park')
In [13]: print(person)
<sp:person xmlns:sp="http://southpark/xml/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" description="a random south park character">
<sp:lname>black</sp:lname>
<sp:city>south park</sp:city>
<sp:fname>token</sp:fname>
</sp:person>
Let's make sure that a person's group is either coon or goth. To do so, we can inherit from SimpleType object and define a restriction by overriding check_restriction(self, value) method.
In [7]: from pysxm import ComplexType, SimpleType
In [8]: class Group(SimpleType):
...: allowed_groups = ('coon', 'goth')
...: def check_restriction(self, value):
...: if value not in self.allowed_groups:
...: raise ValueError('<%s> value %s not in %s' % (self.tagname, value, self.allowed_groups))
...:
In [9]: class Person(ComplexType):
...: def __init__(self, fname, lname, group):
...: self.fname = fname
...: self.lname = lname
...: self.group = Group(group)
...:
In [10]: Person('token', 'black', 'boys')
...
<ipython-input-8-116b49042116> in check_restriction(self, value)
3 def check_restriction(self, value):
4 if value not in self.allowed_groups:
----> 5 raise ValueError('<%s> value %s not in %s' % (self.tagname, value, self.allowed_groups))
6
ValueError: <group> value boys not in ('coon', 'goth')
In [11]: print(Person('token', 'black', 'goth'))
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<lname>black</lname>
<group>goth</group>
<fname>token</fname>
</person>
Note: ComplexType can have ComplexType and SimpleType as attribute
from pysxm import ComplexType, SimpleType
class AdultAge(SimpleType):
tagname = 'age'
attrib = {'minvalue': '18', 'maxvalue': '100'}
def check_restriction(self, value):
if int(value) < 18:
raise ValueError("<%s> '%d' < 18" % (self.tagname, value))
class Credentials(ComplexType):
def __init__(self, login, password):
self.login = login
self.password = password
class Person(ComplexType):
def __init__(self, fname, lname, credentials, age):
self.fname = fname
self.lname = lname
self.credentials = Credentials(credentials['login'], credentials['password'])
self.age = AdultAge(age)
In [3]: data = {
...: 'fname': 'token', 'lname': 'black',
...: 'credentials': {'login': 't0ken', 'password': 'l33tolite'},
...: 'age': '30'}
In [4]: person = Person(**data)
In [5]: print(person)
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<lname>black</lname>
<credentials>
<login>t0ken</login>
<password>l33tolite</password>
</credentials>
<age maxvalue="100" minvalue="18">30</age>
<fname>token</fname>
</person>
In [6]: person.save('token.xml')
The save method (object.save(<filename>)) allows you to save the xml result into a file.
In [7]: cat token.xml
<person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<lname>black</lname>
<credentials>
<login>t0ken</login>
<password>l33tolite</password>
</credentials>
<age maxvalue="100" minvalue="18">30</age>
<fname>token</fname>
</person>
Pysxm comes with a couple of extended types. Those types are defined in pysxm.ext module.
This is a simple DataClass of ComplexType. Here is how you can set one up:
from pysxm.ext import DataComplexType, XSimpleType
class Game(DataComplexType):
platform = XSimpleType('platform', ['xboxone', 'xboxx'], lambda v, av: v in av)
>>> game = Game(name='state of decay 2', editor='undead labs', platform='xboxone')
>>> print(game)
<game>
<name>state of decay 2</name>
<platform>xboxone</platform>
<editor>undead labs</editor>
</game>
It gets tiresome to subclass a SimpleType everytime you want to check a value. To overcome that, pysxm provides a descriptor called XSimpleType:
class XSimpleType(object):
def __init__(name=None, restriction=None, checker=None, error_msg=None, **kwargs):
'''name: it's the name of the attribute.
restriction: self explanatory
checker: the fucntion that checks the input value
error_msg: message returned when checking fails
kwargs: as tagname, attrib or nsmap
'''
Here is an example:
class XboxGamer(ComplexType):
platform = XSimpleType('platform', ('xone', 'xbox360', 'xbox'), lambda v, av: v in av)
score = XSimpleType('score', (4000, 1000000), lambda v, av: int(av[0]) <= int(v) < int(av[1]))
lastlogin = XDateTimeType('lastlogin')
def __init__(self, gamertag, platform, score, lastlogin):
self.gamertag = gamertag
self.platform = platform
self.score = score
self.lastlogin = lastlogin
In [1]: print(gamer_data)
{'gamertag': 'LokingHD', 'platform': 'ps4', 'score': '22526', 'lastlogin': '2018-03-21'}
In [2]: XboxGamer(**gamer_data)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-2-61f95466da46> in <module>()
----> 1 XboxGamer(**gamer_data)
/home/josue/workspace/dev/pysxdb/pysxm/ext.pyc in check(self, instance, value)
77 if not self.checker(value, self.restriction_values):
78 raise ValueError('tagname <%s> value %s is invalid: expected (%s)'
---> 79 % (instance.tagname, value, self.restriction_values))
80
81 def check_restriction(self, instance, value):
ValueError: tagname <xboxgamer> value ps4 is invalid: expected (('xone', 'xbox360', 'xbox'))
In [3]: gamer_data['platform'] = 'xone'
In [4]: gamer = XboxGamer(**gamer_data)
In [5]: print(gamer)
<xboxgamer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<gamertag>LokingHD</gamertag>
<platform>xone</platform>
<score>22526</score>
<lastlogin>2018-03-21T00:00:00</lastlogin>
</xboxgamer>
Most of the types defined in pysxm.ext are descriptors and they're subclassable.
Voila 😉