Module push_to_3yourmind.api.user_panel

Groups API functionality from the User Panel, such as creating/updating baskets, placing orders, making requests for quotes, ordering quotes etc.

Expand source code
"""
Groups API functionality from the User Panel, such as creating/updating baskets,
placing orders, making requests for quotes, ordering quotes etc.
"""

import datetime
import decimal
import typing as t
import time

from push_to_3yourmind import types, exceptions, utils
from push_to_3yourmind.logger import logger
from push_to_3yourmind.api.base import BaseAPI
from push_to_3yourmind.types import NoValue


__all__ = ["UserPanelAPI"]


class UserPanelAPI(BaseAPI):
    """
    Accessible via namespace `user_panel`, for example:
    >>> response = client.user_panel.get_baskets()

    Attributes:
        CHECK_FILE_STATUS_MAX_ATTEMPTS: How many times to check for the uploaded CAD file analysis status
        CHECK_FILE_STATUS_DELAY: Delay between status check requests, in seconds
    """

    CHECK_FILE_STATUS_MAX_ATTEMPTS = 60
    CHECK_FILE_STATUS_DELAY = 0.5  # seconds

    def get_baskets(
        self,
        *,
        page: types.OptionalInteger = NoValue,
        page_size: types.OptionalInteger = NoValue,
    ) -> t.List[types.ResponseDict]:
        """
        Get all baskets of the current user. Returns paginated list.

        Args:
            page: int, optional
            page_size: int, optional
        Returns:
            dictionary with the following keys:

            - count: total number of baskets, int
            - currentPage: page number, int
            - totalPages: total number of pages, int
            - pageSize: baskets per page, int
            - results: list of basket details
        """

        query = self._get_parameters(page=page, pageSize=page_size)
        return self._request("GET", "user-panel/baskets/", params=query)

    def get_basket(self, *, basket_id: int) -> types.ResponseDict:
        """
        Args:
            basket_id: int

        Returns:
            Basket details dict
        """
        return self._request("GET", f"user-panel/baskets/{basket_id}/")

    def get_basket_price(
        self,
        *,
        basket_id: int,
        currency: str,
        shipping_address_id: types.OptionalInteger = types.NoValue,
        billing_address_id: types.OptionalInteger = types.NoValue,
        shipping_method_id: types.OptionalInteger = types.NoValue,
        voucher_code: types.OptionalString = types.NoValue,
    ) -> types.ResponseDict:
        """
        Calculate basket's price, given additional optional shipping, billing information

        Args:
            basket_id: int
            currency: str
            shipping_address_id: int, optional
            billing_address_id: int, optional
            shipping_method_id: int, optional
            voucher_code: str, optional

        Returns:
            Dict containing basket prices
        """
        query = self._get_parameters(
            currency=currency,
            shippingAddressId=shipping_address_id,
            shippingMethodId=shipping_method_id,
            billingAddressId=billing_address_id,
            voucherCode=voucher_code,
        )
        return self._request(
            "GET", f"user-panel/baskets/{basket_id}/price/", params=query
        )

    def create_basket(self) -> types.ResponseDict:
        return self._request("POST", "user-panel/baskets/")

    def delete_basket(self, basket_id: int) -> str:
        return self._request("DELETE", f"user-panel/baskets/{basket_id}/")

    def update_basket(
        self, *, basket_id: int, title: t.Union[str, types.NoValueType] = NoValue
    ) -> types.ResponseDict:
        json = self._get_parameters(title=title)
        return self._request("PATCH", f"user-panel/baskets/{basket_id}/", json=json)

    def get_basket_lines(self, basket_id: int) -> t.List[types.ResponseDict]:
        return self._request("GET", f"user-panel/baskets/{basket_id}/lines/")

    def get_basket_line(self, *, basket_id: int, line_id) -> types.ResponseDict:
        return self._request("GET", f"user-panel/baskets/{basket_id}/lines/{line_id}/")

    def create_basket_line(self, basket_id: int) -> types.ResponseDict:
        return self._request("POST", f"user-panel/baskets/{basket_id}/lines/")

    def update_basket_line(
        self,
        *,
        basket_id: int,
        line_id: int,
        quantity: types.OptionalInteger = types.NoValue,
        product_id: types.OptionalInteger = types.NoValue,
        post_processings: t.Sequence[types.PostProcessingConfig] = (),
        preferred_due_date: types.OptionalDate = types.NoValue,
    ) -> types.ResponseDict:

        if post_processings:
            post_processings = [
                {"postProcessingId": pp.post_processing_id, "colorId": pp.color_id}
                for pp in post_processings
            ]
        if isinstance(preferred_due_date, datetime.date):
            preferred_due_date = preferred_due_date.strftime("%Y-%m-%d")
        json = self._get_parameters(
            quantity=quantity,
            offerId=product_id,
            postProcessings=post_processings,
            preferredDueDate=preferred_due_date,
        )
        return self._request(
            "PATCH", f"user-panel/baskets/{basket_id}/lines/{line_id}/", json=json
        )

    def add_part_requirements_to_basket_line(
            self,
            *,
            line_id: int,
            form_data: types.FormData,
    ):
        json = {
            "formId": form_data.form_id,
            "fields": [
                {"formFieldId": field.form_field_id, "value": field.value} 
                for field in form_data.fields
            ]
        }
        return self._request(
            "POST", f"user-panel/forms/basket-line/{line_id}/",
            json=json,
        )

    def get_materials(
        self, *, basket_id: int, line_id: int
    ) -> t.List[types.ResponseDict]:
        preferences = self._request("GET", "my-profile/preferences/")
        country = preferences["country"]
        query = {"country": country}

        return self._request(
            "GET",
            f"user-panel/baskets/{basket_id}/lines/{line_id}/materials/",
            params=query,
        )

    def get_products(
        self, *, basket_id: int, line_id: int, material_id: int
    ) -> t.List[types.ResponseDict]:
        preferences = self._request("GET", "my-profile/preferences/")
        country = preferences["country"]
        query = {"country": country}

        return self._request(
            "GET",
            f"user-panel/baskets/{basket_id}/lines/{line_id}/"
            f"materials/{material_id}/offers/",
            params=query,
        )

    def upload_cad_file(
        self,
        *,
        basket_id: int,
        unit: types.Unit,
        cad_file: types.CadFileSpecifier,
        line_id: int,
    ) -> types.ResponseDict:

        data = self._get_parameters(basket_id=basket_id, unit=unit, line_id=line_id)
        cad_file_contents = utils.extract_file_content(cad_file)

        return self._request(
            "POST", f"/upload/", data=data, files={"file": cad_file_contents}
        )

    def create_line_with_cad_file_and_product(
        self,
        *,
        basket_id: int,
        cad_file: types.CadFileSpecifier,
        product_id: int,
        quantity: int,
        post_processings: t.Sequence[types.PostProcessingConfig] = (),
        preferred_due_date: types.OptionalDate = types.NoValue,
    ) -> types.ResponseDict:
        preferences = self._request("GET", "my-profile/preferences/")
        unit = preferences["unit"]

        line_response = self.create_basket_line(basket_id=basket_id)
        line_id = line_response["id"]
        self.upload_cad_file(
            basket_id=basket_id, line_id=line_id, unit=unit, cad_file=cad_file
        )
        self.check_uploaded_file_status(basket_id=basket_id, line_id=line_id)

        return self.update_basket_line(
            basket_id=basket_id,
            line_id=line_id,
            quantity=quantity,
            product_id=product_id,
            post_processings=post_processings,
            preferred_due_date=preferred_due_date,
        )

    def check_uploaded_file_status(
        self,
        *,
        basket_id: int,
        line_id: int,
    ) -> None:
        max_attempts = self.CHECK_FILE_STATUS_MAX_ATTEMPTS
        for attempt_number in range(max_attempts):
            logger.debug(f"Checking file status {attempt_number}/{max_attempts}")
            response = self._request(
                "GET", f"user-panel/baskets/{basket_id}/lines/{line_id}/file-status/"
            )
            response_content = response.get("status")
            if response_content == "analysing":
                time.sleep(self.CHECK_FILE_STATUS_DELAY)
                continue
            elif response_content == "finished":
                logger.debug("File analysis done")
                return
            else:
                raise exceptions.FileAnalysisError()

    def create_request_for_quote(
        self, *, basket_id: int, supplier_id: int, message: str
    ) -> types.ResponseDict:
        json = {
            "basketId": basket_id,
            "partnerId": supplier_id,
            "message": message,
        }
        return self._request("POST", f"user-panel/requests-for-quote/", json=json)

    def get_quotes(self) -> types.ResponseDict:
        return self._request("GET", "user-panel/quotes/")

    def get_orders(self) -> types.ResponseDict:
        return self._request("GET", "user-panel/orders/")

    def get_order(self, *, order_id: int) -> types.ResponseDict:
        return self._request("GET", f"user-panel/orders/{order_id}/")

    def get_order_line(self, *, order_id: int, line_id: int) -> types.ResponseDict:
        return self._request("GET", f"user-panel/orders/{order_id}/{line_id}/")

    def get_quote(self, *, quote_id: int) -> types.ResponseDict:
        return self._request("GET", f"user-panel/quotes/{quote_id}/")

    def finalize_quote(
        self,
        *,
        quote_id: int,
        billing_address_id: t.Optional[int] = None,
        shipping_address_id: t.Optional[int] = None,
        shipping_method_id: t.Optional[int] = None,
        pickup_location_id: t.Optional[int] = None,
        delivery_instructions: t.Optional[str] = None,
    ) -> types.ResponseDict:
        json = self._get_parameters(
            billingAddressId=billing_address_id,
            shippingAddressId=shipping_address_id,
            shippingMethodId=shipping_method_id,
            pickupLocationId=pickup_location_id,
            deliveryInstructions=delivery_instructions,
        )
        return self._request(
            "PUT",
            f"user-panel/quotes/{quote_id}/finalize/",
            json=json,
        )

    def place_order_from_quote(
        self,
        *,
        quote_id: int,
        payment_method_id: int,
        authorized_amount: decimal.Decimal,
        reference: t.Optional[str] = None,
        currency: t.Optional[str] = None,
        voucher_code: str = "",
    ) -> types.ResponseDict:
        json = {
            "additionalInformation": {"reference": reference},
            "payment": {
                "currency": currency,
                "details": {},
                "methodId": payment_method_id,
                "authorizedAmount": authorized_amount,
            },
            "quoteId": quote_id,
            "voucherCode": voucher_code,
        }
        return self._request("POST", f"user-panel/orders/", json=json)

    def quick_order_quote(self, *, quote_id: int) -> types.ResponseDict:
        """
        get quote details
        get supplier id

        If quote is not finalized:
           get my addresses, pick one
           get supplier shipping methods, pick one
           finalize quote with one address as shipping and billing,
            one payment method and one shipping method

        get payment methods, pick one
        place order from quote with payment method
        """
        quote_details = self.get_quote(quote_id=quote_id)
        supplier_id = quote_details["partner"]["id"]
        quote_is_finalized = quote_details["status"] == "finalized"

        if not quote_is_finalized:
            addresses = self._request("GET", "my-profile/addresses/")
            address = addresses[0]
            address_id = address["id"]

            shipping_methods = self.get_shipping_methods(
                supplier_id=supplier_id,
                quote_id=quote_id,
                shipping_address_id=address_id,
            )
            shipping_method = shipping_methods[0]
            shipping_method_id = shipping_method["id"]

            self.finalize_quote(
                quote_id=quote_id,
                billing_address_id=address_id,
                shipping_address_id=address_id,
                shipping_method_id=shipping_method_id,
            )
        payment_methods = self.get_payment_methods(supplier_id=supplier_id)
        payment_method = payment_methods[0]
        payment_method_id = payment_method["id"]
        return self.place_order_from_quote(
            quote_id=quote_id,
            payment_method_id=payment_method_id,
            currency=quote_details["currency"],
            authorized_amount=quote_details["totalPrice"]["inclusiveTax"],
        )

    def get_payment_methods(
        self, *, supplier_id: int
    ) -> t.Sequence[types.ResponseDict]:
        return self._request(
            "GET", f"user-panel/services/{supplier_id}/payment-methods/"
        )

    def get_shipping_methods(
        self,
        *,
        supplier_id: int,
        quote_id: types.OptionalInteger = types.NoValue,
        shipping_address_id: types.OptionalInteger = types.NoValue,
    ) -> t.Sequence[types.ResponseDict]:
        query = self._get_parameters(
            quoteId=quote_id, shippingAddressId=shipping_address_id
        )
        return self._request(
            "GET",
            f"user-panel/services/{supplier_id}/shipping-methods/",
            params=query,
        )

    def create_catalog_item(self, basket_line_id: int) -> types.ResponseDict:
        return self._request(
            "POST",
            "user-panel/catalog/",
            json={"lineId": basket_line_id},
        )

    def upload_catalog_item_attachment(
            self,
            catalog_item_id: int,
            attachment_file: types.AttachmentFileSpecifier,
    ) -> types.ResponseDict:
        attachment_file_contents = utils.extract_file_content(attachment_file)
        return self._request(
            "POST",
            f"user-panel/catalog/{catalog_item_id}/attachments/",
            files={"file": attachment_file_contents},
        )

Classes

class UserPanelAPI (access_token: str, base_url: str)

Accessible via namespace user_panel, for example:

>>> response = client.user_panel.get_baskets()

Attributes

CHECK_FILE_STATUS_MAX_ATTEMPTS
How many times to check for the uploaded CAD file analysis status
CHECK_FILE_STATUS_DELAY
Delay between status check requests, in seconds

Args

access_token
to create a token, open /admin/auth/user/, and click "Create token" in the user list.
base_url
application URL, ex. https://app.3yourmind.com
Expand source code
class UserPanelAPI(BaseAPI):
    """
    Accessible via namespace `user_panel`, for example:
    >>> response = client.user_panel.get_baskets()

    Attributes:
        CHECK_FILE_STATUS_MAX_ATTEMPTS: How many times to check for the uploaded CAD file analysis status
        CHECK_FILE_STATUS_DELAY: Delay between status check requests, in seconds
    """

    CHECK_FILE_STATUS_MAX_ATTEMPTS = 60
    CHECK_FILE_STATUS_DELAY = 0.5  # seconds

    def get_baskets(
        self,
        *,
        page: types.OptionalInteger = NoValue,
        page_size: types.OptionalInteger = NoValue,
    ) -> t.List[types.ResponseDict]:
        """
        Get all baskets of the current user. Returns paginated list.

        Args:
            page: int, optional
            page_size: int, optional
        Returns:
            dictionary with the following keys:

            - count: total number of baskets, int
            - currentPage: page number, int
            - totalPages: total number of pages, int
            - pageSize: baskets per page, int
            - results: list of basket details
        """

        query = self._get_parameters(page=page, pageSize=page_size)
        return self._request("GET", "user-panel/baskets/", params=query)

    def get_basket(self, *, basket_id: int) -> types.ResponseDict:
        """
        Args:
            basket_id: int

        Returns:
            Basket details dict
        """
        return self._request("GET", f"user-panel/baskets/{basket_id}/")

    def get_basket_price(
        self,
        *,
        basket_id: int,
        currency: str,
        shipping_address_id: types.OptionalInteger = types.NoValue,
        billing_address_id: types.OptionalInteger = types.NoValue,
        shipping_method_id: types.OptionalInteger = types.NoValue,
        voucher_code: types.OptionalString = types.NoValue,
    ) -> types.ResponseDict:
        """
        Calculate basket's price, given additional optional shipping, billing information

        Args:
            basket_id: int
            currency: str
            shipping_address_id: int, optional
            billing_address_id: int, optional
            shipping_method_id: int, optional
            voucher_code: str, optional

        Returns:
            Dict containing basket prices
        """
        query = self._get_parameters(
            currency=currency,
            shippingAddressId=shipping_address_id,
            shippingMethodId=shipping_method_id,
            billingAddressId=billing_address_id,
            voucherCode=voucher_code,
        )
        return self._request(
            "GET", f"user-panel/baskets/{basket_id}/price/", params=query
        )

    def create_basket(self) -> types.ResponseDict:
        return self._request("POST", "user-panel/baskets/")

    def delete_basket(self, basket_id: int) -> str:
        return self._request("DELETE", f"user-panel/baskets/{basket_id}/")

    def update_basket(
        self, *, basket_id: int, title: t.Union[str, types.NoValueType] = NoValue
    ) -> types.ResponseDict:
        json = self._get_parameters(title=title)
        return self._request("PATCH", f"user-panel/baskets/{basket_id}/", json=json)

    def get_basket_lines(self, basket_id: int) -> t.List[types.ResponseDict]:
        return self._request("GET", f"user-panel/baskets/{basket_id}/lines/")

    def get_basket_line(self, *, basket_id: int, line_id) -> types.ResponseDict:
        return self._request("GET", f"user-panel/baskets/{basket_id}/lines/{line_id}/")

    def create_basket_line(self, basket_id: int) -> types.ResponseDict:
        return self._request("POST", f"user-panel/baskets/{basket_id}/lines/")

    def update_basket_line(
        self,
        *,
        basket_id: int,
        line_id: int,
        quantity: types.OptionalInteger = types.NoValue,
        product_id: types.OptionalInteger = types.NoValue,
        post_processings: t.Sequence[types.PostProcessingConfig] = (),
        preferred_due_date: types.OptionalDate = types.NoValue,
    ) -> types.ResponseDict:

        if post_processings:
            post_processings = [
                {"postProcessingId": pp.post_processing_id, "colorId": pp.color_id}
                for pp in post_processings
            ]
        if isinstance(preferred_due_date, datetime.date):
            preferred_due_date = preferred_due_date.strftime("%Y-%m-%d")
        json = self._get_parameters(
            quantity=quantity,
            offerId=product_id,
            postProcessings=post_processings,
            preferredDueDate=preferred_due_date,
        )
        return self._request(
            "PATCH", f"user-panel/baskets/{basket_id}/lines/{line_id}/", json=json
        )

    def add_part_requirements_to_basket_line(
            self,
            *,
            line_id: int,
            form_data: types.FormData,
    ):
        json = {
            "formId": form_data.form_id,
            "fields": [
                {"formFieldId": field.form_field_id, "value": field.value} 
                for field in form_data.fields
            ]
        }
        return self._request(
            "POST", f"user-panel/forms/basket-line/{line_id}/",
            json=json,
        )

    def get_materials(
        self, *, basket_id: int, line_id: int
    ) -> t.List[types.ResponseDict]:
        preferences = self._request("GET", "my-profile/preferences/")
        country = preferences["country"]
        query = {"country": country}

        return self._request(
            "GET",
            f"user-panel/baskets/{basket_id}/lines/{line_id}/materials/",
            params=query,
        )

    def get_products(
        self, *, basket_id: int, line_id: int, material_id: int
    ) -> t.List[types.ResponseDict]:
        preferences = self._request("GET", "my-profile/preferences/")
        country = preferences["country"]
        query = {"country": country}

        return self._request(
            "GET",
            f"user-panel/baskets/{basket_id}/lines/{line_id}/"
            f"materials/{material_id}/offers/",
            params=query,
        )

    def upload_cad_file(
        self,
        *,
        basket_id: int,
        unit: types.Unit,
        cad_file: types.CadFileSpecifier,
        line_id: int,
    ) -> types.ResponseDict:

        data = self._get_parameters(basket_id=basket_id, unit=unit, line_id=line_id)
        cad_file_contents = utils.extract_file_content(cad_file)

        return self._request(
            "POST", f"/upload/", data=data, files={"file": cad_file_contents}
        )

    def create_line_with_cad_file_and_product(
        self,
        *,
        basket_id: int,
        cad_file: types.CadFileSpecifier,
        product_id: int,
        quantity: int,
        post_processings: t.Sequence[types.PostProcessingConfig] = (),
        preferred_due_date: types.OptionalDate = types.NoValue,
    ) -> types.ResponseDict:
        preferences = self._request("GET", "my-profile/preferences/")
        unit = preferences["unit"]

        line_response = self.create_basket_line(basket_id=basket_id)
        line_id = line_response["id"]
        self.upload_cad_file(
            basket_id=basket_id, line_id=line_id, unit=unit, cad_file=cad_file
        )
        self.check_uploaded_file_status(basket_id=basket_id, line_id=line_id)

        return self.update_basket_line(
            basket_id=basket_id,
            line_id=line_id,
            quantity=quantity,
            product_id=product_id,
            post_processings=post_processings,
            preferred_due_date=preferred_due_date,
        )

    def check_uploaded_file_status(
        self,
        *,
        basket_id: int,
        line_id: int,
    ) -> None:
        max_attempts = self.CHECK_FILE_STATUS_MAX_ATTEMPTS
        for attempt_number in range(max_attempts):
            logger.debug(f"Checking file status {attempt_number}/{max_attempts}")
            response = self._request(
                "GET", f"user-panel/baskets/{basket_id}/lines/{line_id}/file-status/"
            )
            response_content = response.get("status")
            if response_content == "analysing":
                time.sleep(self.CHECK_FILE_STATUS_DELAY)
                continue
            elif response_content == "finished":
                logger.debug("File analysis done")
                return
            else:
                raise exceptions.FileAnalysisError()

    def create_request_for_quote(
        self, *, basket_id: int, supplier_id: int, message: str
    ) -> types.ResponseDict:
        json = {
            "basketId": basket_id,
            "partnerId": supplier_id,
            "message": message,
        }
        return self._request("POST", f"user-panel/requests-for-quote/", json=json)

    def get_quotes(self) -> types.ResponseDict:
        return self._request("GET", "user-panel/quotes/")

    def get_orders(self) -> types.ResponseDict:
        return self._request("GET", "user-panel/orders/")

    def get_order(self, *, order_id: int) -> types.ResponseDict:
        return self._request("GET", f"user-panel/orders/{order_id}/")

    def get_order_line(self, *, order_id: int, line_id: int) -> types.ResponseDict:
        return self._request("GET", f"user-panel/orders/{order_id}/{line_id}/")

    def get_quote(self, *, quote_id: int) -> types.ResponseDict:
        return self._request("GET", f"user-panel/quotes/{quote_id}/")

    def finalize_quote(
        self,
        *,
        quote_id: int,
        billing_address_id: t.Optional[int] = None,
        shipping_address_id: t.Optional[int] = None,
        shipping_method_id: t.Optional[int] = None,
        pickup_location_id: t.Optional[int] = None,
        delivery_instructions: t.Optional[str] = None,
    ) -> types.ResponseDict:
        json = self._get_parameters(
            billingAddressId=billing_address_id,
            shippingAddressId=shipping_address_id,
            shippingMethodId=shipping_method_id,
            pickupLocationId=pickup_location_id,
            deliveryInstructions=delivery_instructions,
        )
        return self._request(
            "PUT",
            f"user-panel/quotes/{quote_id}/finalize/",
            json=json,
        )

    def place_order_from_quote(
        self,
        *,
        quote_id: int,
        payment_method_id: int,
        authorized_amount: decimal.Decimal,
        reference: t.Optional[str] = None,
        currency: t.Optional[str] = None,
        voucher_code: str = "",
    ) -> types.ResponseDict:
        json = {
            "additionalInformation": {"reference": reference},
            "payment": {
                "currency": currency,
                "details": {},
                "methodId": payment_method_id,
                "authorizedAmount": authorized_amount,
            },
            "quoteId": quote_id,
            "voucherCode": voucher_code,
        }
        return self._request("POST", f"user-panel/orders/", json=json)

    def quick_order_quote(self, *, quote_id: int) -> types.ResponseDict:
        """
        get quote details
        get supplier id

        If quote is not finalized:
           get my addresses, pick one
           get supplier shipping methods, pick one
           finalize quote with one address as shipping and billing,
            one payment method and one shipping method

        get payment methods, pick one
        place order from quote with payment method
        """
        quote_details = self.get_quote(quote_id=quote_id)
        supplier_id = quote_details["partner"]["id"]
        quote_is_finalized = quote_details["status"] == "finalized"

        if not quote_is_finalized:
            addresses = self._request("GET", "my-profile/addresses/")
            address = addresses[0]
            address_id = address["id"]

            shipping_methods = self.get_shipping_methods(
                supplier_id=supplier_id,
                quote_id=quote_id,
                shipping_address_id=address_id,
            )
            shipping_method = shipping_methods[0]
            shipping_method_id = shipping_method["id"]

            self.finalize_quote(
                quote_id=quote_id,
                billing_address_id=address_id,
                shipping_address_id=address_id,
                shipping_method_id=shipping_method_id,
            )
        payment_methods = self.get_payment_methods(supplier_id=supplier_id)
        payment_method = payment_methods[0]
        payment_method_id = payment_method["id"]
        return self.place_order_from_quote(
            quote_id=quote_id,
            payment_method_id=payment_method_id,
            currency=quote_details["currency"],
            authorized_amount=quote_details["totalPrice"]["inclusiveTax"],
        )

    def get_payment_methods(
        self, *, supplier_id: int
    ) -> t.Sequence[types.ResponseDict]:
        return self._request(
            "GET", f"user-panel/services/{supplier_id}/payment-methods/"
        )

    def get_shipping_methods(
        self,
        *,
        supplier_id: int,
        quote_id: types.OptionalInteger = types.NoValue,
        shipping_address_id: types.OptionalInteger = types.NoValue,
    ) -> t.Sequence[types.ResponseDict]:
        query = self._get_parameters(
            quoteId=quote_id, shippingAddressId=shipping_address_id
        )
        return self._request(
            "GET",
            f"user-panel/services/{supplier_id}/shipping-methods/",
            params=query,
        )

    def create_catalog_item(self, basket_line_id: int) -> types.ResponseDict:
        return self._request(
            "POST",
            "user-panel/catalog/",
            json={"lineId": basket_line_id},
        )

    def upload_catalog_item_attachment(
            self,
            catalog_item_id: int,
            attachment_file: types.AttachmentFileSpecifier,
    ) -> types.ResponseDict:
        attachment_file_contents = utils.extract_file_content(attachment_file)
        return self._request(
            "POST",
            f"user-panel/catalog/{catalog_item_id}/attachments/",
            files={"file": attachment_file_contents},
        )

Ancestors

Class variables

var CHECK_FILE_STATUS_MAX_ATTEMPTS
var CHECK_FILE_STATUS_DELAY

Methods

def get_baskets(self, *, page: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue, page_size: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue) ‑> List[Dict[str, Any]]

Get all baskets of the current user. Returns paginated list.

Args

page
int, optional
page_size
int, optional

Returns

dictionary with the following keys:

  • count: total number of baskets, int
  • currentPage: page number, int
  • totalPages: total number of pages, int
  • pageSize: baskets per page, int
  • results: list of basket details
Expand source code
def get_baskets(
    self,
    *,
    page: types.OptionalInteger = NoValue,
    page_size: types.OptionalInteger = NoValue,
) -> t.List[types.ResponseDict]:
    """
    Get all baskets of the current user. Returns paginated list.

    Args:
        page: int, optional
        page_size: int, optional
    Returns:
        dictionary with the following keys:

        - count: total number of baskets, int
        - currentPage: page number, int
        - totalPages: total number of pages, int
        - pageSize: baskets per page, int
        - results: list of basket details
    """

    query = self._get_parameters(page=page, pageSize=page_size)
    return self._request("GET", "user-panel/baskets/", params=query)
def get_basket(self, *, basket_id: int) ‑> Dict[str, Any]

Args

basket_id
int

Returns

Basket details dict

Expand source code
def get_basket(self, *, basket_id: int) -> types.ResponseDict:
    """
    Args:
        basket_id: int

    Returns:
        Basket details dict
    """
    return self._request("GET", f"user-panel/baskets/{basket_id}/")
def get_basket_price(self, *, basket_id: int, currency: str, shipping_address_id: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue, billing_address_id: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue, shipping_method_id: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue, voucher_code: Union[str, bytes, Type[NoValue]] = push_to_3yourmind.types.NoValue) ‑> Dict[str, Any]

Calculate basket's price, given additional optional shipping, billing information

Args

basket_id
int
currency
str
shipping_address_id
int, optional
billing_address_id
int, optional
shipping_method_id
int, optional
voucher_code
str, optional

Returns

Dict containing basket prices

Expand source code
def get_basket_price(
    self,
    *,
    basket_id: int,
    currency: str,
    shipping_address_id: types.OptionalInteger = types.NoValue,
    billing_address_id: types.OptionalInteger = types.NoValue,
    shipping_method_id: types.OptionalInteger = types.NoValue,
    voucher_code: types.OptionalString = types.NoValue,
) -> types.ResponseDict:
    """
    Calculate basket's price, given additional optional shipping, billing information

    Args:
        basket_id: int
        currency: str
        shipping_address_id: int, optional
        billing_address_id: int, optional
        shipping_method_id: int, optional
        voucher_code: str, optional

    Returns:
        Dict containing basket prices
    """
    query = self._get_parameters(
        currency=currency,
        shippingAddressId=shipping_address_id,
        shippingMethodId=shipping_method_id,
        billingAddressId=billing_address_id,
        voucherCode=voucher_code,
    )
    return self._request(
        "GET", f"user-panel/baskets/{basket_id}/price/", params=query
    )
def create_basket(self) ‑> Dict[str, Any]
Expand source code
def create_basket(self) -> types.ResponseDict:
    return self._request("POST", "user-panel/baskets/")
def delete_basket(self, basket_id: int) ‑> str
Expand source code
def delete_basket(self, basket_id: int) -> str:
    return self._request("DELETE", f"user-panel/baskets/{basket_id}/")
def update_basket(self, *, basket_id: int, title: Union[str, Type[NoValue]] = push_to_3yourmind.types.NoValue) ‑> Dict[str, Any]
Expand source code
def update_basket(
    self, *, basket_id: int, title: t.Union[str, types.NoValueType] = NoValue
) -> types.ResponseDict:
    json = self._get_parameters(title=title)
    return self._request("PATCH", f"user-panel/baskets/{basket_id}/", json=json)
def get_basket_lines(self, basket_id: int) ‑> List[Dict[str, Any]]
Expand source code
def get_basket_lines(self, basket_id: int) -> t.List[types.ResponseDict]:
    return self._request("GET", f"user-panel/baskets/{basket_id}/lines/")
def get_basket_line(self, *, basket_id: int, line_id) ‑> Dict[str, Any]
Expand source code
def get_basket_line(self, *, basket_id: int, line_id) -> types.ResponseDict:
    return self._request("GET", f"user-panel/baskets/{basket_id}/lines/{line_id}/")
def create_basket_line(self, basket_id: int) ‑> Dict[str, Any]
Expand source code
def create_basket_line(self, basket_id: int) -> types.ResponseDict:
    return self._request("POST", f"user-panel/baskets/{basket_id}/lines/")
def update_basket_line(self, *, basket_id: int, line_id: int, quantity: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue, product_id: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue, post_processings: Sequence[push_to_3yourmind.types.PostProcessingConfig] = (), preferred_due_date: Union[datetime.date, Type[NoValue]] = push_to_3yourmind.types.NoValue) ‑> Dict[str, Any]
Expand source code
def update_basket_line(
    self,
    *,
    basket_id: int,
    line_id: int,
    quantity: types.OptionalInteger = types.NoValue,
    product_id: types.OptionalInteger = types.NoValue,
    post_processings: t.Sequence[types.PostProcessingConfig] = (),
    preferred_due_date: types.OptionalDate = types.NoValue,
) -> types.ResponseDict:

    if post_processings:
        post_processings = [
            {"postProcessingId": pp.post_processing_id, "colorId": pp.color_id}
            for pp in post_processings
        ]
    if isinstance(preferred_due_date, datetime.date):
        preferred_due_date = preferred_due_date.strftime("%Y-%m-%d")
    json = self._get_parameters(
        quantity=quantity,
        offerId=product_id,
        postProcessings=post_processings,
        preferredDueDate=preferred_due_date,
    )
    return self._request(
        "PATCH", f"user-panel/baskets/{basket_id}/lines/{line_id}/", json=json
    )
def add_part_requirements_to_basket_line(self, *, line_id: int, form_data: push_to_3yourmind.types.FormData)
Expand source code
def add_part_requirements_to_basket_line(
        self,
        *,
        line_id: int,
        form_data: types.FormData,
):
    json = {
        "formId": form_data.form_id,
        "fields": [
            {"formFieldId": field.form_field_id, "value": field.value} 
            for field in form_data.fields
        ]
    }
    return self._request(
        "POST", f"user-panel/forms/basket-line/{line_id}/",
        json=json,
    )
def get_materials(self, *, basket_id: int, line_id: int) ‑> List[Dict[str, Any]]
Expand source code
def get_materials(
    self, *, basket_id: int, line_id: int
) -> t.List[types.ResponseDict]:
    preferences = self._request("GET", "my-profile/preferences/")
    country = preferences["country"]
    query = {"country": country}

    return self._request(
        "GET",
        f"user-panel/baskets/{basket_id}/lines/{line_id}/materials/",
        params=query,
    )
def get_products(self, *, basket_id: int, line_id: int, material_id: int) ‑> List[Dict[str, Any]]
Expand source code
def get_products(
    self, *, basket_id: int, line_id: int, material_id: int
) -> t.List[types.ResponseDict]:
    preferences = self._request("GET", "my-profile/preferences/")
    country = preferences["country"]
    query = {"country": country}

    return self._request(
        "GET",
        f"user-panel/baskets/{basket_id}/lines/{line_id}/"
        f"materials/{material_id}/offers/",
        params=query,
    )
def upload_cad_file(self, *, basket_id: int, unit: Literal['mm', 'inch'], cad_file: Union[str, IO], line_id: int) ‑> Dict[str, Any]
Expand source code
def upload_cad_file(
    self,
    *,
    basket_id: int,
    unit: types.Unit,
    cad_file: types.CadFileSpecifier,
    line_id: int,
) -> types.ResponseDict:

    data = self._get_parameters(basket_id=basket_id, unit=unit, line_id=line_id)
    cad_file_contents = utils.extract_file_content(cad_file)

    return self._request(
        "POST", f"/upload/", data=data, files={"file": cad_file_contents}
    )
def create_line_with_cad_file_and_product(self, *, basket_id: int, cad_file: Union[str, IO], product_id: int, quantity: int, post_processings: Sequence[push_to_3yourmind.types.PostProcessingConfig] = (), preferred_due_date: Union[datetime.date, Type[NoValue]] = push_to_3yourmind.types.NoValue) ‑> Dict[str, Any]
Expand source code
def create_line_with_cad_file_and_product(
    self,
    *,
    basket_id: int,
    cad_file: types.CadFileSpecifier,
    product_id: int,
    quantity: int,
    post_processings: t.Sequence[types.PostProcessingConfig] = (),
    preferred_due_date: types.OptionalDate = types.NoValue,
) -> types.ResponseDict:
    preferences = self._request("GET", "my-profile/preferences/")
    unit = preferences["unit"]

    line_response = self.create_basket_line(basket_id=basket_id)
    line_id = line_response["id"]
    self.upload_cad_file(
        basket_id=basket_id, line_id=line_id, unit=unit, cad_file=cad_file
    )
    self.check_uploaded_file_status(basket_id=basket_id, line_id=line_id)

    return self.update_basket_line(
        basket_id=basket_id,
        line_id=line_id,
        quantity=quantity,
        product_id=product_id,
        post_processings=post_processings,
        preferred_due_date=preferred_due_date,
    )
def check_uploaded_file_status(self, *, basket_id: int, line_id: int) ‑> None
Expand source code
def check_uploaded_file_status(
    self,
    *,
    basket_id: int,
    line_id: int,
) -> None:
    max_attempts = self.CHECK_FILE_STATUS_MAX_ATTEMPTS
    for attempt_number in range(max_attempts):
        logger.debug(f"Checking file status {attempt_number}/{max_attempts}")
        response = self._request(
            "GET", f"user-panel/baskets/{basket_id}/lines/{line_id}/file-status/"
        )
        response_content = response.get("status")
        if response_content == "analysing":
            time.sleep(self.CHECK_FILE_STATUS_DELAY)
            continue
        elif response_content == "finished":
            logger.debug("File analysis done")
            return
        else:
            raise exceptions.FileAnalysisError()
def create_request_for_quote(self, *, basket_id: int, supplier_id: int, message: str) ‑> Dict[str, Any]
Expand source code
def create_request_for_quote(
    self, *, basket_id: int, supplier_id: int, message: str
) -> types.ResponseDict:
    json = {
        "basketId": basket_id,
        "partnerId": supplier_id,
        "message": message,
    }
    return self._request("POST", f"user-panel/requests-for-quote/", json=json)
def get_quotes(self) ‑> Dict[str, Any]
Expand source code
def get_quotes(self) -> types.ResponseDict:
    return self._request("GET", "user-panel/quotes/")
def get_orders(self) ‑> Dict[str, Any]
Expand source code
def get_orders(self) -> types.ResponseDict:
    return self._request("GET", "user-panel/orders/")
def get_order(self, *, order_id: int) ‑> Dict[str, Any]
Expand source code
def get_order(self, *, order_id: int) -> types.ResponseDict:
    return self._request("GET", f"user-panel/orders/{order_id}/")
def get_order_line(self, *, order_id: int, line_id: int) ‑> Dict[str, Any]
Expand source code
def get_order_line(self, *, order_id: int, line_id: int) -> types.ResponseDict:
    return self._request("GET", f"user-panel/orders/{order_id}/{line_id}/")
def get_quote(self, *, quote_id: int) ‑> Dict[str, Any]
Expand source code
def get_quote(self, *, quote_id: int) -> types.ResponseDict:
    return self._request("GET", f"user-panel/quotes/{quote_id}/")
def finalize_quote(self, *, quote_id: int, billing_address_id: Optional[int] = None, shipping_address_id: Optional[int] = None, shipping_method_id: Optional[int] = None, pickup_location_id: Optional[int] = None, delivery_instructions: Optional[str] = None) ‑> Dict[str, Any]
Expand source code
def finalize_quote(
    self,
    *,
    quote_id: int,
    billing_address_id: t.Optional[int] = None,
    shipping_address_id: t.Optional[int] = None,
    shipping_method_id: t.Optional[int] = None,
    pickup_location_id: t.Optional[int] = None,
    delivery_instructions: t.Optional[str] = None,
) -> types.ResponseDict:
    json = self._get_parameters(
        billingAddressId=billing_address_id,
        shippingAddressId=shipping_address_id,
        shippingMethodId=shipping_method_id,
        pickupLocationId=pickup_location_id,
        deliveryInstructions=delivery_instructions,
    )
    return self._request(
        "PUT",
        f"user-panel/quotes/{quote_id}/finalize/",
        json=json,
    )
def place_order_from_quote(self, *, quote_id: int, payment_method_id: int, authorized_amount: decimal.Decimal, reference: Optional[str] = None, currency: Optional[str] = None, voucher_code: str = '') ‑> Dict[str, Any]
Expand source code
def place_order_from_quote(
    self,
    *,
    quote_id: int,
    payment_method_id: int,
    authorized_amount: decimal.Decimal,
    reference: t.Optional[str] = None,
    currency: t.Optional[str] = None,
    voucher_code: str = "",
) -> types.ResponseDict:
    json = {
        "additionalInformation": {"reference": reference},
        "payment": {
            "currency": currency,
            "details": {},
            "methodId": payment_method_id,
            "authorizedAmount": authorized_amount,
        },
        "quoteId": quote_id,
        "voucherCode": voucher_code,
    }
    return self._request("POST", f"user-panel/orders/", json=json)
def quick_order_quote(self, *, quote_id: int) ‑> Dict[str, Any]

get quote details get supplier id

If quote is not finalized: get my addresses, pick one get supplier shipping methods, pick one finalize quote with one address as shipping and billing, one payment method and one shipping method

get payment methods, pick one place order from quote with payment method

Expand source code
def quick_order_quote(self, *, quote_id: int) -> types.ResponseDict:
    """
    get quote details
    get supplier id

    If quote is not finalized:
       get my addresses, pick one
       get supplier shipping methods, pick one
       finalize quote with one address as shipping and billing,
        one payment method and one shipping method

    get payment methods, pick one
    place order from quote with payment method
    """
    quote_details = self.get_quote(quote_id=quote_id)
    supplier_id = quote_details["partner"]["id"]
    quote_is_finalized = quote_details["status"] == "finalized"

    if not quote_is_finalized:
        addresses = self._request("GET", "my-profile/addresses/")
        address = addresses[0]
        address_id = address["id"]

        shipping_methods = self.get_shipping_methods(
            supplier_id=supplier_id,
            quote_id=quote_id,
            shipping_address_id=address_id,
        )
        shipping_method = shipping_methods[0]
        shipping_method_id = shipping_method["id"]

        self.finalize_quote(
            quote_id=quote_id,
            billing_address_id=address_id,
            shipping_address_id=address_id,
            shipping_method_id=shipping_method_id,
        )
    payment_methods = self.get_payment_methods(supplier_id=supplier_id)
    payment_method = payment_methods[0]
    payment_method_id = payment_method["id"]
    return self.place_order_from_quote(
        quote_id=quote_id,
        payment_method_id=payment_method_id,
        currency=quote_details["currency"],
        authorized_amount=quote_details["totalPrice"]["inclusiveTax"],
    )
def get_payment_methods(self, *, supplier_id: int) ‑> Sequence[Dict[str, Any]]
Expand source code
def get_payment_methods(
    self, *, supplier_id: int
) -> t.Sequence[types.ResponseDict]:
    return self._request(
        "GET", f"user-panel/services/{supplier_id}/payment-methods/"
    )
def get_shipping_methods(self, *, supplier_id: int, quote_id: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue, shipping_address_id: Union[int, Type[NoValue]] = push_to_3yourmind.types.NoValue) ‑> Sequence[Dict[str, Any]]
Expand source code
def get_shipping_methods(
    self,
    *,
    supplier_id: int,
    quote_id: types.OptionalInteger = types.NoValue,
    shipping_address_id: types.OptionalInteger = types.NoValue,
) -> t.Sequence[types.ResponseDict]:
    query = self._get_parameters(
        quoteId=quote_id, shippingAddressId=shipping_address_id
    )
    return self._request(
        "GET",
        f"user-panel/services/{supplier_id}/shipping-methods/",
        params=query,
    )
def create_catalog_item(self, basket_line_id: int) ‑> Dict[str, Any]
Expand source code
def create_catalog_item(self, basket_line_id: int) -> types.ResponseDict:
    return self._request(
        "POST",
        "user-panel/catalog/",
        json={"lineId": basket_line_id},
    )
def upload_catalog_item_attachment(self, catalog_item_id: int, attachment_file: Union[str, IO]) ‑> Dict[str, Any]
Expand source code
def upload_catalog_item_attachment(
        self,
        catalog_item_id: int,
        attachment_file: types.AttachmentFileSpecifier,
) -> types.ResponseDict:
    attachment_file_contents = utils.extract_file_content(attachment_file)
    return self._request(
        "POST",
        f"user-panel/catalog/{catalog_item_id}/attachments/",
        files={"file": attachment_file_contents},
    )