Skip to content

Instantly share code, notes, and snippets.

@sephii
Last active August 29, 2015 14:11
Show Gist options
  • Save sephii/646307fe735afea53e64 to your computer and use it in GitHub Desktop.
Save sephii/646307fe735afea53e64 to your computer and use it in GitHub Desktop.
xmodel
from urllib2 import urlopen
from lxml import etree
import six
class Options(object):
"""
_meta class for FeedModel.
"""
def __init__(self):
self.local_fields = []
def add_field(self, field):
self.local_fields.append(field)
class BaseFeedModel(type):
"""
Metaclass for FeedModel. Register attributes to the class.
"""
def __new__(cls, name, bases, attrs):
new_class = super(BaseFeedModel, cls).__new__(cls, name, bases, attrs)
# Create internal _meta object
setattr(new_class, '_meta', Options())
for attr_name, attr in attrs.items():
if hasattr(attr, 'contribute_to_class'):
attr.contribute_to_class(new_class, attr_name)
return new_class
class FeedModel(six.with_metaclass(BaseFeedModel)):
pass
class BaseField(object):
def __init__(self, xpath):
self.xpath = xpath
def contribute_to_class(self, cls, name):
self.model = cls
self.name = name
cls._meta.add_field(self)
def to_python(self, value):
return value
class TextField(BaseField):
pass
class IntegerField(BaseField):
def to_python(self, value):
return int(value)
class CollectionField(BaseField):
"""
Allow to access collections of items through the use of the all() method.
"""
def __init__(self, rel, xpath):
super(CollectionField, self).__init__(xpath)
self.rel = rel
def to_python(self, value):
objects = self.rel.objects
objects.xml = value
objects.selector = self.xpath
return objects
class BaseManager(object):
"""
Here's the main logic. The all() method goes through the elements of the
given selector and then goes through the different fields defined for this
element and sets its attributes by running xpath expressions.
"""
def contribute_to_class(self, cls, name):
self.model = cls
def all(self):
results = []
for element in self.get_elements():
relative_element = etree.ElementTree(element)
instance = self.model()
for field in self.model._meta.local_fields:
field_value = relative_element.xpath(field.xpath)
# CollectionField is a special case, create a CollectionManager
# that will operate on a subset of the XML tree
if isinstance(field, CollectionField):
new_manager = CollectionManager(field_value)
new_manager.contribute_to_class(field.rel, None)
setattr(instance, field.name, new_manager)
else:
if field_value and isinstance(field_value, list):
field_value = field_value[0]
field_value = field.to_python(field_value)
setattr(instance, field.name, field_value)
results.append(instance)
return results
class CollectionManager(BaseManager):
def __init__(self, collection):
super(CollectionManager, self).__init__()
self.collection = collection
def get_elements(self):
return self.collection
class FeedManager(BaseManager):
def __init__(self, url, selector):
super(FeedManager, self).__init__()
self.url = url
self.selector = selector
self.model = None
def get_xml(self):
return etree.parse(urlopen(self.url))
def get_elements(self):
return self.get_xml().xpath(self.selector)
##########################
# Test models definition #
##########################
class Season(FeedModel):
id = IntegerField('@id')
name = TextField('@name')
year_start = IntegerField('@start')
year_end = IntegerField('@end')
class Competition(FeedModel):
id = IntegerField('@id')
name = TextField('@name')
seasons = CollectionField(Season, '/competition/seasons/season')
objects = FeedManager(
url='https://gist.githubusercontent.com/sephii/46d6cf59873bac9eb553/raw/4c9ee0ef8ddafa68784f6c11b60fa65aeb84f0e4/gistfile1.xml',
selector='/competitions/competition'
)
# Access to objects like Competition.objects.all() and then to properties like Competition.objects.all()[0].name
# You can access to related collections like so: Competition.objects.all()[0].seasons.all()
import pdb; pdb.set_trace()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment