Skip to main content

Simple and extensible xml python marshaller

Project description

https://travis-ci.org/josuebrunel/pysxm.svg?branch=master https://coveralls.io/repos/github/josuebrunel/pysxm/badge.svg?branch=master http://pepy.tech/badge/pysxm

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.

Installation

pip install pysxm

Quickstart

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>

The ext module

Pysxm comes with a couple of extended types. Those types are defined in pysxm.ext module.

DataComplexType

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>

XSimpleType

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 :wink:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pysxm-1.5.0.tar.gz (6.3 kB view hashes)

Uploaded Source

Built Distribution

pysxm-1.5.0-py2.py3-none-any.whl (8.4 kB view hashes)

Uploaded Python 2 Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page