#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2016 <>
#
# Distributed under terms of the MIT license.
"""
Useful functions
"""
# Python modules
import os
import re
from codecs import open
from collections import OrderedDict
# Third-party modules
from bs4 import BeautifulSoup
from lxml import etree
import xmltodict
import requests
# Project modules
from config import *
# CLASSES
class Query(object):
"""A simple class to create queries"""
def __init__(self, name, tags={}, **kwargs):
self._dict = OrderedDict()
self.name = name
self.attrib = kwargs
for k, v in kwargs.items():
if not k.startswith('@'):
self._dict['@'+k] = v
for k, v in tags.items():
self._dict[k] = v
def _create_new_dict(self):
new_dict = OrderedDict()
for k, v in self._dict.items():
if k.startswith('@'):
new_dict[k] = v
return new_dict
def between(self, min, max):
new_dict = self._create_new_dict()
new_dict['min'] = {'#text': min}
new_dict['max'] = {'#text': max}
return Query(self.name, tags=new_dict)
def _operator(self, op, value):
op_to_mode = {'eq': 'Equals',
'ge': 'GreaterThanOrEquals', 'gt': 'GreaterThan',
'le': 'LessThanOrEquals', 'lt': 'LessThan'}
new_dict = self._create_new_dict()
new_dict['@mode'] = op_to_mode[op]
new_dict['@value'] = value
return Query(self.name, tags=new_dict)
def eq(self, value):
return self._operator('eq', value)
def ge(self, value):
return self._operator('ge', value)
def gt(self, value):
return self._operator('gt', value)
def le(self, value):
return self._operator('le', value)
def lt(self, value):
return self._operator('lt', value)
@property
def dict(self):
"""Return the OrderedDict"""
return self._dict
@property
def xml(self):
"""Return the XML"""
return dict2xml({self.name: self.dict})
def __str__(self):
return "<{0}: {1}>".format(self.__class__.__name__, self.name)
__repr__ = __str__
class HttpMessage(object):
def __init__(self, text):
self.dict = xml2dict(text)
# Raw XML
self.xml = text.encode('utf-8')
# etree._Element
self.element = etree.fromstring(self.xml)
self.tag = re.sub(pattern='{[^}]+}', repl='', string=self.element.tag, flags=0)
def __repr__(self):
return '<{0}: {1}>'.format(self.__class__.__name__, self.tag)
def __str__(self):
return self.xml
[docs]class Request(HttpMessage):
"""A handy class to handle the request"""
def __init__(self, text):
super(Request, self).__init__(text)
[docs]class Response(HttpMessage):
"""A handy class to handle the response"""
def __init__(self, text):
super(Response, self).__init__(text)
# TODO Implement a check for the attributes
[docs]class Message(object):
ACTIONS = (
'mark_as_read', 'mark_as_unread', 'archive',
'mark_as_read_and_archive', 'unarchive', 'reply', 'create'
)
TO = (
'CALLCENTER', 'CLIENT', 'ALL'
)
SUBJECTS = (
'product_information', 'shipping_information', 'order_information',
'offer_problem', 'offer_not_received', 'other_question'
)
TYPES = ('ORDER', 'OFFER')
def __init__(self, action, id, to='ALL', description='', subject='', type='ORDER'):
self._action = action
self._id = id
self._to = to
self._description = description
self._subject = subject
self._type = type
@property
def action(self):
return self._action
@action.setter
def action(self, new_value):
if new_value not in Message.ACTIONS:
msg = 'Invalid action. Choose between {}.'.format(Message.ACTIONS)
raise ValueError(msg)
self._action = new_value
@property
def id(self):
return self._id
@id.setter
def id(self, new_value):
self._id = new_value
@property
def to(self):
return self._to
@to.setter
def to(self, new_value):
if new_value not in Message.TO:
msg = 'Invalid recipient. Choose between {}.'.format(Message.TO)
raise ValueError(msg)
self._to = new_value
@property
def description(self):
return self._description
@description.setter
def description(self, new_value):
self._description = new_value
@property
def subject(self):
return self._subject
@subject.setter
def subject(self, new_value):
if new_value not in Message.SUBJECTS:
msg = 'Invalid subject. Choose between {}'.format(Message.SUBJECTS)
raise ValueError(msg)
self._subject = new_value
@property
def type(self):
return self._type
@type.setter
def type(self, new_value):
if new_value not in Message.TYPES:
msg = 'Invalid type. Choose between {}'.format(Message.TYPES)
raise ValueError(msg)
self._type = new_value
def __repr__(self):
r = '<Message: action={self.action}, id={self.id}, to={self.to}, '
r += 'description={self.description}, subject={self.subject}, '
r += 'type={self.type}>'
return r.format(self=self)
def __str__(self):
return """Message
action : {self.action}
id : {self.id}
to : {self.to}
description: {self.description}
subject : {self.subject}
type : {self.type}
""".format(self=self)
[docs] def to_dict(self):
"""Return the a dictionary in the xmltodict format"""
message = {'message': {
'@action': self.action,
'@id': self.id,
'message_to': {'#text': self.to},
'message_subject': {'#text': self.subject},
'message_description': {'#text': self.description},
'message_type': {'#text': self.type},
}}
return message
# FUNCTIONS
def dict2xml(_dict):
"""Returns a XML string from the input dictionary"""
xml = xmltodict.unparse(_dict, pretty=True)
xmlepured = remove_namespace(xml)
return xmlepured
def remove_namespace(xml):
"""Remove the namespace from the XML string"""
xmlepured = re.sub(pattern=' xmlns="[^"]+"', repl='', string=xml, flags=0)
xmlepured = xmlepured.encode('utf-8')
return xmlepured
# TODO Use process_namespaces
def xml2dict(xml):
"""Returns a dictionary from the input XML
:type xml: unicode
:param xml: The XML
:rtype: dict
:returns: the dictionary correspoding to the input XML
"""
xmlepured = remove_namespace(xml)
return xmltodict.parse(xmlepured)
# TODO Parse the token with lxml instead of BeautifulSoup
def parse_xml(response, tag_name):
"""Get the text contained in the tag of the response
:param response: the Response
:param tag_name: the name of the tag
:returns: the text enclosed in the tag
"""
return BeautifulSoup(response.content, 'lxml').find(tag_name).text
# TODO Reimplement create_offer_element with kwargs
def create_offer_element(product_reference, offer_reference, price, product_state, quantity, description=None):
"""Create an offer element
An offer needs 5 mandatory parameters:
:param product_reference: a product reference (such as EAN)
:param offer_reference: a seller offer reference (such as SKU)
:param product_state: a product state
:param price: a price
:param quantity: a quantity
You may add an optional parameter:
:param description: a description of the product
:returns: offer (etree.Element)
"""
offer = etree.Element('offer')
etree.SubElement(offer, "product_reference" ,type="Ean").text = str(product_reference)
etree.SubElement(offer, "offer_reference", type="SellerSku").text = etree.CDATA(offer_reference)
etree.SubElement(offer, "price").text = str(price)
etree.SubElement(offer, "product_state").text = str(product_state)
etree.SubElement(offer, "quantity").text = str(quantity)
if description:
etree.SubElement(offer, "description").text = etree.CDATA(description)
return offer
def create_xml_element(connection, token, name):
"""A helper function creating an etree.Element with the necessary
attributes
:param name: The name of the element
:returns: etree.Element
"""
return etree.Element(name, nsmap={None: XHTML_NAMESPACE},
shop_id=connection.shop_id, partner_id=connection.partner_id, token=token)
def get_order_ids(orders_query_response):
"""Returns the order_ids in orders_query_response"""
orders = orders_query_response.dict['orders_query_response'].get('order', None)
order_ids = []
if orders:
if isinstance(orders, (list, tuple)):
for order in orders:
order_ids.append(order.get('order_id', ''))
elif isinstance(orders, dict):
order_ids.append(orders.get('order_id', ''))
return order_ids
def get_token():
partner_id = os.getenv('FNAC_PARTNER_ID')
shop_id = os.getenv('FNAC_SHOP_ID')
key = os.getenv('FNAC_KEY')
xml = """<?xml version="1.0" encoding="utf-8"?>
<auth xmlns='http://www.fnac.com/schemas/mp-dialog.xsd'>
<partner_id>{partner_id}</partner_id>
<shop_id>{shop_id}</shop_id>
<key>{key}</key>
</auth>
""".format(partner_id=partner_id, shop_id=shop_id, key=key)
response = post('auth', xml)
return parse_xml(response, 'token')
def set_credentials(xml):
"""Set the credentials in the given raw XML """
credentials = {'shop_id': os.getenv('FNAC_SHOP_ID'),
'partner_id': os.getenv('FNAC_PARTNER_ID'),
'token': get_token()}
for credential, value in credentials.items():
xml = re.sub(pattern='{0}="[^"]+"'.format(credential),
repl='{0}="{1}"'.format(credential, value), string=xml, flags=0)
return xml
def post(service, request):
return requests.post(URL + service, request, headers=HEADERS)
def save_xml_response(response, action):
"""Save the response in a file """
output_dir = os.path.dirname(os.path.abspath(__file__))
filename = action + '_response.xml'
with open(os.path.join(output_dir, '../tests/assets', filename), 'w') as f:
f.write(response.encode('utf-8'))
print('Saved the response in {}'.format(filename))
def load_xml_request(action):
input_dir = os.path.dirname(os.path.abspath(__file__))
filename = action + '_request.xml'
with open(os.path.join(input_dir, '../tests/assets', filename), 'r', 'utf-8') as f:
request = f.read()
return request