Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
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)
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
    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)
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
    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
Exemplo n.º 12
0
    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)
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
    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)
Exemplo n.º 15
0
    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)