def update_client_order_comments(self, seller_comment, order_fnac_id): """Reply to client order comments Usage:: response = manager.update_client_order_comments(seller_comment, order_fnac_id) :type seller_comment: str :param seller_comment: The seller comment :type order_fnac_id: str :param order_fnac_id: Order unique identifier filter from FNAC :returns: :class:`Response <Response>` object """ client_order_comments_update = create_xml_element( self.connection, self.token, "client_order_comments_update") comment = etree.Element("comment", id=order_fnac_id) etree.SubElement(comment, "comment_reply").text = etree.CDATA(seller_comment) client_order_comments_update.append(comment) self.client_order_comments_update_request = Request( etree.tostring(client_order_comments_update, **XML_OPTIONS)) return self._get_response( client_order_comments_update, self.client_order_comments_update_request.xml)
def update_messages(self, messages): """Update message sent on your offers or orders : reply, set as read, ... Usage:: response = manager.update_messages(messages) :type messages: Message :param messages: the specified messages we want to update :returns: :class:`Response <Response>` object Example:: >>> m1 = Message(action='mark_as_read', id='12345') >>> m2 = Message(action='reply', id='12345') >>> m2.description = 'Your order has been shipped' >>> m2.subject = 'order_information' >>> m2.type = 'ORDER' >>> response = manager.update_messages([m1, m2]) """ messages_update = create_xml_element(self.connection, self.token, "messages_update") for m in messages: message = etree.XML(dict2xml(m.to_dict())) messages_update.append(message) self.messages_update_request = Request( etree.tostring(messages_update, **XML_OPTIONS)) return self._get_response(messages_update, self.messages_update_request.xml)
def delete_offers(self, offer_references): """Delete the offers with the given offer_references (sku) Usage:: response = manager.delete_offers(offer_references) :param offer_references: the list of SKUs corresponding to the offers you want to delete from your catalog :returns: :class:`Response <Response>` object """ offers_update = create_xml_element(self.connection, self.token, "offers_update") for offer_reference in offer_references: offer = etree.Element("offer") etree.SubElement( offer, "offer_reference", type="SellerSku").text = etree.CDATA(offer_reference) etree.SubElement(offer, "treatment").text = "delete" offers_update.append(offer) self.offers_update_request = Request( etree.tostring(offers_update, **XML_OPTIONS)) # the response contains the element batch_id response = self._get_response(offers_update, self.offers_update_request.xml) self.batch_id = response.dict["offers_update_response"]["batch_id"] return response
def _get_response(self, element, xml): """Send the request and return the response as a dictionary :type element: lxml.etree.Element :param element: the XML element :type xml: str :param xml: the XML string sent in the request :returns: :class:`Response <Response>` object :raises: FnapyResponseError when the response is incorrect """ service = element.tag response = requests.post(self.url + service, xml, headers=HEADERS) response = Response(handle_content_response(response)) if response.dict.get(service + "_response", {}).get("error"): # Reauthenticate and update the element element.attrib["token"] = self.authenticate() setattr( self, service + "_request", Request(etree.tostring(element, **XML_OPTIONS)), ) # Resend the updated request response = requests.post( self.url + service, getattr(self, service + "_request").xml, headers=HEADERS, ) response = Response(handle_content_response(response)) return response
def query_pricing(self, eans): """Retrieve the best prices applied to a given product within all Fnac marketplace sellers (Fnac included) Usage:: response = manager.query_pricing(eans) :type eans: list or tuple :param eans: a list of EANs :returns: response .. note: If no price is found for a product, a :class:`FnapyPricingError <FnapyPricingError>` is raised. """ pricing_query = create_xml_element(self.connection, self.token, "pricing_query") for ean in eans: product_reference = etree.Element("product_reference", type="Ean") product_reference.text = str(ean) pricing_query.append(product_reference) else: if len(eans) == 0: product_reference = etree.Element("product_reference", type="Ean") pricing_query.append(product_reference) self.pricing_query_request = Request( etree.tostring(pricing_query, **XML_OPTIONS)) response = self._get_response(pricing_query, self.pricing_query_request.xml) # Check if any error is returned errors = response.element.xpath("//ns:error", namespaces={"ns": XHTML_NAMESPACE}) if len(errors) > 0 and hasattr(errors[0], "text"): for error in errors: product_reference = error.getprevious() # code="ERR_120" # Service Pricing : Product not found. if product_reference is not None: logger.warning("EAN: {0}. {1}".format( product_reference.text, error.text)) # code="ERR_105" # The limit of requested elements is reached by the service. else: logger.warning("{0}".format(error.text)) return response
def test_request(): xml_request = """<?xml version='1.0' encoding='utf-8'?> <batch_status xmlns="http://www.fnac.com/schemas/mp-dialog.xsd" partner_id="X" shop_id="X" token="X"><batch_id>{}</batch_id></batch_status> """.format(BATCH_ID) xml_request = remove_namespace(xml_request) request = Request(xml_request) element = etree.Element('batch_status', partner_id='X', shop_id='X', token='X') etree.SubElement(element, 'batch_id').text = BATCH_ID assert request.dict == xml2dict(xml_request) assert request.xml == xml_request assert request.tag == 'batch_status' assert elements_are_equal(request.element, element)
def update_offers(self, offers_data): """Post the update offers and return the response Usage:: response = manager.update_offers(offers_data) :type offers_data: list :param offers_data: the list of data to create the offers where data is dictionary with the keys: * offer_reference : the SKU (mandatory) * product_reference: the EAN (optional) * price : the price of the offer (optional) * product_state : an integer representing the state of the product (documentation needed) (optional) * quantity : the quantity (optional) * description : a description of the offer (optional) The exception FnapyUpdateOfferError is raised if: - offer_reference and at least one of the optional parameters (except product_reference) are not provided - offers_data is empty :returns: :class:`Response <Response>` object """ offers_update = create_xml_element(self.connection, self.token, "offers_update") if len(offers_data) == 0: msg = "You must provide at least one offer_data." raise FnapyUpdateOfferError(msg) for offer_data in offers_data: check_offer_data(offer_data) offer = create_offer_element(offer_data) offers_update.append(offer) self.offers_update_request = Request( etree.tostring(offers_update, **XML_OPTIONS)) # the response contains the element batch_id response = self._get_response(offers_update, self.offers_update_request.xml) try: self.batch_id = response.dict["offers_update_response"]["batch_id"] except KeyError: self.batch_id = "" return response
def query_batch(self): """Return information about your currently processing import batches Usage:: response = manager.query_batch() :returns: :class:`Response <Response>` object """ batch_query = create_xml_element(self.connection, self.token, "batch_query") self.batch_query_request = Request( etree.tostring(batch_query, **XML_OPTIONS)) return self._get_response(batch_query, self.batch_query_request.xml)
def update_incidents(self, order_id, incident_update_action, reasons): """Handle incidents created on orders Usage:: response = manager.update_incidents(order_id, incident_update_action, reasons) :type order_id: str :param order_id: the unique FNAC identified for an order :type incident_update_action: str :param incident_update_action: the action to perform (`'refund'` is the only available action for the moment) :type reasons: list :param reasons: the reasons of the incident for this order Example:: reason = {"order_detail_id": 1, "refund_reason": 'no_stock'} response = manager.update_incidents('07LWQ6278YJUI', 'refund', [reason]) :returns: :class:`Response <Response>` object """ incidents_update = create_xml_element(self.connection, self.token, "incidents_update") order = etree.Element("order", order_id=order_id, action=incident_update_action) for reason in reasons: order_detail = etree.Element("order_detail") etree.SubElement(order_detail, "order_detail_id").text = str( reason["order_detail_id"]) etree.SubElement(order_detail, "refund_reason").text = str( reason["refund_reason"]) order.append(order_detail) incidents_update.append(order) self.incidents_update_request = Request( etree.tostring(incidents_update, **XML_OPTIONS)) return self._get_response(incidents_update, self.incidents_update_request.xml)
def query_carriers(self): """Return the available carriers managed on FNAC Marketplace platform Usage:: response = manager.query_carriers() :returns: :class:`Response <Response>` object """ carriers_query = create_xml_element(self.connection, self.token, "carriers_query") etree.SubElement(carriers_query, "query").text = etree.CDATA("all") self.carriers_query_request = Request( etree.tostring(carriers_query, **XML_OPTIONS)) return self._get_response(carriers_query, self.carriers_query_request.xml)
def authenticate(self): """Authenticate to the FNAC API and return a token Usage:: token = manager.authenticate() :returns: token :rtype: str """ auth = etree.Element("auth", nsmap={None: XHTML_NAMESPACE}) etree.SubElement(auth, "partner_id").text = self.connection.partner_id etree.SubElement(auth, "shop_id").text = self.connection.shop_id etree.SubElement(auth, "key").text = self.connection.key self.auth_request = Request(etree.tostring(auth, **XML_OPTIONS)) response = requests.post(self.url + "auth", self.auth_request.xml, headers=HEADERS) self.token = parse_xml(response, "token") return self.token
def get_batch_status(self, batch_id=None): """Return the status for the given batch id Usage:: response = manager.get_batch_status(batch_id=batch_id) ..note:: :class:`FnapyManager <FnapyManager>` stores the last `batch_id` but you can provide a new one if needed. :param batch_id: the batch id (optional) :returns: :class:`Response <Response>` object """ if batch_id is not None: self.batch_id = batch_id batch_status = create_xml_element(self.connection, self.token, "batch_status") etree.SubElement(batch_status, "batch_id").text = self.batch_id self.batch_status_request = Request( etree.tostring(batch_status, **XML_OPTIONS)) return self._get_response(batch_status, self.batch_status_request.xml)
def query_pricing(self, codes): """Retrieve the best prices applied to a given product within all Fnac marketplace sellers (Fnac included) Usage:: response = manager.query_pricing(codes) :type codes: list or tuple :param codes: a list of EANs or a list of dicts with the keys `value` and `type`. Example:: codes = [ean1, ean2, ...] codes = [{"value": code1, "type": "Ean"}, {"value": code2, "type": "Isbn"}, ...] The availble code types are: - FnacId: The code given is the Fnac product identifier - PartnerId: The code given is the id from a partner, if this value is set, a partner id will be mandatory - Ean: The code given is the global Ean of this product - Isbn: The code given is the global Isbn of this product - PartNumber: The code given is the global partNumber of this product - CnetId: The code given is the global Cnet reference of this product :returns: response .. note: If no price is found for a product, a :class:`FnapyPricingError <FnapyPricingError>` is raised. """ pricing_query = create_xml_element(self.connection, self.token, "pricing_query") for code in codes: if isinstance(code, str): code_type = "Ean" code_value = code elif isinstance(code, dict): code_value = code["value"] code_type = code["type"] product_reference = etree.Element("product_reference", type=code_type) product_reference.text = str(code_value) pricing_query.append(product_reference) else: if len(codes) == 0: product_reference = etree.Element("product_reference", type="Ean") pricing_query.append(product_reference) self.pricing_query_request = Request( etree.tostring(pricing_query, **XML_OPTIONS)) response = self._get_response(pricing_query, self.pricing_query_request.xml) # Check if any error is returned errors = response.element.xpath("//ns:error", namespaces={"ns": XHTML_NAMESPACE}) if len(errors) > 0 and hasattr(errors[0], "text"): for error in errors: product_reference = error.getprevious() # code="ERR_120" # Service Pricing : Product not found. if product_reference is not None: logger.warning("EAN: {0}. {1}".format( product_reference.text, error.text)) # code="ERR_105" # The limit of requested elements is reached by the service. else: logger.warning("{0}".format(error.text)) return response
def _query(self, query_type, results_count="", **elements): """Query your catalog and return the ${query_type} response Usage:: response = manager.query_${query_type}(results_count=results_count, **elements) The available XML elements are the following parameters: ${parameters} :returns: :class:`Response <Response>` object Examples: Find the 2 first items of the catalog:: response = manager.query_${query_type}(results_count=2, paging=1) Find the ${query_type} created between 2 dates:: >>> from fnapy.utils import Query >>> date = Query('date', type='Modified') .between(min="2016-08-23T17:00:00+00:00", max="2016-08-26T17:00:00+00:00") >>> response = manager.query_${query_type}(date=date) """ if query_type in FnapyManager.VALID_QUERY_TYPES: query_type += "_query" else: raise ValueError("The query_type must be in {}".format( FnapyManager.VALID_QUERY_TYPES)) # TODO Refactor: Use a dictionary to prevent code duplication # Check the queried elements self._check_elements(REQUEST_ELEMENTS[query_type], elements.keys()) # Make sure we have unicode # paging = str(paging).decode('utf-8') results_count = str(results_count) # .decode('utf-8') # Create the XML element query = create_xml_element(self.connection, self.token, query_type) if results_count: query.attrib["results_count"] = results_count # Create the XML from the queried elements if len(elements): for key, value in elements.items(): # Handle cases where Query is used if isinstance(value, Query): value = value.dict d = {key: value} queried_elements = etree.XML(dict2xml(d)) query.append(queried_elements) setattr(self, query_type + "_request", Request(etree.tostring(query, **XML_OPTIONS))) query_xml = getattr(self, query_type + "_request").xml return self._get_response(query, query_xml)
def update_orders(self, order_id, order_update_action, actions): """Update the selected order with an order_update_action Usage:: response = manager.update_orders(order_id, order_update_action, actions) :type order_id: str :param order_id: Order unique identifier from FNAC :type order_update_action: str :param order_update_action: Group action type for order detail action :type actions: list :param actions: a list of dictionaries with 2 keys: `'order_detail_id'` and `'action'` :returns: :class:`Response <Response>` object Available order_update_action: * accept_order : The action for the order is accepting orders by the seller * confirm_to_send : The action for the order is confirming sending orders by the seller * update : The action for the order is updating orders by the seller * accept_all_orders : The action for the order is accepting or refusing all order_details of the order by the seller * confirm_all_to_send: The action for the order is confirming sending all order_details by the seller * update_all : The action for the order is to update tracking information for all order_details Example: For this order (whose `order_id` is `'LDJEDEAS123'`), we have 2 items. We decide to accept the first item and refuse the second:: action1 = {"order_detail_id": 1, "action": "Accepted"} action2 = {"order_detail_id": 2, "action": "Refused"} response = manager.update_orders('LDJEDEAS123', 'accept_order', [action1, action2] ) """ order_id = str(order_id) orders_update = create_xml_element(self.connection, self.token, "orders_update") order = etree.Element("order", order_id=order_id, action=order_update_action) for action in actions: if "order_detail_id" not in action or "action" not in action: msg = "You must provide order_detail_id and action" raise FnapyUpdateOrderError(msg) order_detail = etree.Element("order_detail") for action_key, action_value in action.items(): etree.SubElement(order_detail, action_key).text = str(action_value) order.append(order_detail) orders_update.append(order) self.orders_update_request = Request( etree.tostring(orders_update, **XML_OPTIONS)) return self._get_response(orders_update, self.orders_update_request.xml)