You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bazarr/libs/plex/interfaces/core/base.py

217 lines
5.3 KiB

6 years ago
from plex.lib.six import string_types, StringIO
from plex.lib.six.moves.urllib_parse import urlparse
from functools import wraps
import logging
# Import available parser
PARSER = None
try:
from lxml import etree
PARSER = 'etree.HTMLParser'
except ImportError:
from xml.etree import ElementTree as etree
PARSER = 'etree.XMLParser'
log = logging.getLogger(__name__)
class Helpers(object):
@staticmethod
def get(node, attr):
if PARSER == 'etree.HTMLParser':
return node.get(attr.lower())
return node.get(attr)
@staticmethod
def find(node, tag):
if PARSER == 'etree.HTMLParser':
return node.find(tag.lower())
return node.find(tag)
@staticmethod
def findall(node, tag):
if PARSER == 'etree.HTMLParser':
return node.findall(tag.lower())
return node.findall(tag)
class Interface(object):
helpers = Helpers
path = None
object_map = {}
def __init__(self, client):
self.client = client
def __getitem__(self, name):
if hasattr(self, name):
return getattr(self, name)
raise ValueError('Unknown action "%s" on %s', name, self)
@property
def http(self):
if not self.client:
return None
return self.client.http.configure(self.path)
def parse(self, response, schema):
if response.status_code < 200 or response.status_code >= 300:
return None
try:
root = self.__parse_xml(response.content)
except SyntaxError as ex:
log.error('Unable to parse XML response: %s', ex, exc_info=True, extra={
'data': {
'snippet': self.__error_snippet(response, ex)
}
})
return None
except Exception as ex:
log.error('Unable to parse XML response: %s', ex, exc_info=True)
return None
url = urlparse(response.url)
path = url.path
return self.__construct(self.client, path, root, schema)
@staticmethod
def __parse_xml(content):
if PARSER == 'etree.HTMLParser':
html = etree.fromstring(content, parser=etree.HTMLParser())
assert html.tag == 'html'
bodies = [e for e in html if e.tag == 'body']
assert len(bodies) == 1
body = bodies[0]
assert len(body) == 1
return body[0]
return etree.fromstring(content)
@staticmethod
def __error_snippet(response, ex):
# Retrieve the error line
position = getattr(ex, 'position', None)
if not position or len(position) != 2:
return None
n_line, n_column = position
snippet = None
# Create StringIO stream
stream = StringIO(response.text)
# Iterate over `content` to find `n_line`
for x, l in enumerate(stream):
if x < n_line - 1:
continue
# Line found
snippet = l
break
# Close the stream
stream.close()
if not snippet:
# Couldn't find the line
return None
# Find an attribute value containing `n_column`
start = snippet.find('"', n_column)
end = snippet.find('"', start + 1)
# Trim `snippet` (if attribute value was found)
if start >= 0 and end >= 0:
return snippet[start:end + 1]
return snippet
@classmethod
def __construct(cls, client, path, node, schema):
if not schema:
return None
# Try retrieve schema for `tag`
item = schema.get(node.tag)
if item is None:
raise ValueError('Unknown node with tag "%s"' % node.tag)
if type(item) is dict:
value = cls.helpers.get(node, item.get('_', 'type'))
if value is None:
return None
item = item.get(value)
if item is None:
raise ValueError('Unknown node type "%s"' % value)
descriptor = None
child_schema = None
if type(item) is tuple and len(item) == 2:
descriptor, child_schema = item
else:
descriptor = item
if isinstance(descriptor, string_types):
if descriptor not in cls.object_map:
raise Exception('Unable to find descriptor by name "%s"' % descriptor)
descriptor = cls.object_map.get(descriptor)
if descriptor is None:
raise Exception('Unable to find descriptor')
keys_used, obj = descriptor.construct(client, node, path=path)
# Lazy-construct children
def iter_children():
for child_node in node:
item = cls.__construct(client, path, child_node, child_schema)
if item:
yield item
obj._children = iter_children()
return obj
class InterfaceProxy(object):
def __init__(self, interface, args):
self.interface = interface
self.args = list(args)
def __getattr__(self, name):
value = getattr(self.interface, name)
if not hasattr(value, '__call__'):
return value
@wraps(value)
def wrap(*args, **kwargs):
args = self.args + list(args)
return value(*args, **kwargs)
return wrap