PK aO cdiscountapi/__init__.py"""
cdiscountapi is a wrapper around the Cdiscount Marketplace API
"""
__version__ = "0.1.10"
from cdiscountapi.cdiscountapi import Connection
PK |DO% cdiscountapi/cdiscountapi.py# -*- coding: utf-8 -*-
"""
cdiscountapi.cdiscountapi
-------------------------
:copyright: © 2019 Alexandria
"""
import lxml
import requests
from zeep import Client
from zeep.plugins import HistoryPlugin
from zeep.helpers import serialize_object
from cdiscountapi.exceptions import CdiscountApiConnectionError
from cdiscountapi.sections import (
Seller,
Offers,
Products,
Orders,
Relays,
Fulfillment,
WebMail,
Discussions,
)
from configparser import ConfigParser, ExtendedInterpolation
from pathlib import Path
import yaml
# CONSTANTS
DEFAULT_SITE_ID = 100
DEFAULT_CATALOG_ID = 1
DEFAULT_VERSION = "1.0"
class Connection(object):
"""A class to manage the interaction with the CdiscountMarketplace API
:param str login: The login
:param str password: The password
:param bool preprod: Whether we use the preprod (True) or the production environment (False)
(default value: False)
:param dict header_message: The header message
:param str config: The path to a YAML config file
Usage::
api = Connection(login, password, preprod, header_message=header_message, config=config)
"""
def __init__(self, login, password, preprod=False, header_message={}, config=""):
self.preprod = preprod
if self.preprod:
domain = "preprod-cdiscount.com"
else:
domain = "cdiscount.com"
self.wsdl = "https://wsvc.{0}/MarketplaceAPIService.svc?wsdl".format(domain)
self.auth_url = (
"https://sts.{0}/users/httpIssue.svc/"
"?realm=https://wsvc.{0}/MarketplaceAPIService.svc".format(domain)
)
self.login = login
self.password = password
self.history = HistoryPlugin()
self.client = Client(self.wsdl, plugins=[self.history])
self.factory = self.client.type_factory("http://www.cdiscount.com")
if self.login is None or self.password is None:
raise CdiscountApiConnectionError("Please provide valid login and password")
self.token = self.get_token()
if header_message != {} and config != "":
raise CdiscountApiConnectionError(
"You should provide header_message or config. Not both."
)
if header_message:
self.header = self.create_header_message(header_message)
elif config:
config_path = Path(config)
if config_path.exists():
conf = yaml.load(config_path.read_text(), Loader=yaml.FullLoader)
self.header = self.create_header_message(conf)
else:
raise CdiscountApiConnectionError(
"Can't find the configuration file {}".format(config)
)
else:
raise CdiscountApiConnectionError(
"You must provide header_message or config."
)
# Instanciated sections.
self.seller = Seller(self)
self.offers = Offers(self)
self.products = Products(self)
self.orders = Orders(self)
self.fulfillment = Fulfillment(self)
self.relays = Relays(self)
self.discussions = Discussions(self)
self.webmail = WebMail(self)
def get_token(self):
response = requests.get(self.auth_url, auth=(self.login, self.password))
return lxml.etree.XML(response.text).text
def _analyze_history(self, attr, error_msg):
if len(self.history._buffer) == 0:
return error_msg
envelope = getattr(self.history, attr)["envelope"]
return lxml.etree.tostring(envelope, pretty_print=True).decode("utf8")
@property
def last_request(self):
"""
Return the last SOAP request
"""
return self._analyze_history("last_sent", "No request sent.")
@property
def last_response(self):
"""
Return the last SOAP response
"""
return self._analyze_history("last_received", "No response received.")
def create_header_message(self, data):
messages_factory = self.client.type_factory(
"http://schemas.datacontract.org/2004/07/"
"Cdiscount.Framework.Core.Communication.Messages"
)
# Set default values if they are not provided
if "Context" in data:
data["Context"].setdefault("SiteID", DEFAULT_SITE_ID)
data["Context"].setdefault("CatalogID", DEFAULT_CATALOG_ID)
else:
data["Context"] = {
"SiteID": DEFAULT_SITE_ID,
"CatalogID": DEFAULT_CATALOG_ID,
}
if "Security" in data:
data["Security"].setdefault("TokenId", self.token)
else:
data["Security"] = {"UserName": "", "TokenId": self.token}
if "Version" not in data:
data["Version"] = DEFAULT_VERSION
return serialize_object(messages_factory.HeaderMessage(**data), dict)
PK |DOَAy y cdiscountapi/exceptions.py# -*- coding: utf-8 -*-
"""
cdiscountapi.exceptions
-------------------------
Define specific exceptions.
:copyright: © 2019 Alexandria
"""
class CdiscountApiException(Exception):
""""""
pass
class CdiscountApiConnectionError(CdiscountApiException):
""""""
pass
class CdiscountApiTypeError(CdiscountApiException):
""""""
pass
class CdiscountApiOrderError(CdiscountApiException):
"""
Raised when there's an error in the order
"""
class ValidationError(Exception):
"""
Raised when the parameters to create the Offers.xml or Products.xml are not valid
"""
PK O.$ $ cdiscountapi/helpers.py# -*- coding: utf-8 -*-
"""
cdiscountapi.helpers
--------------------
Implements various helpers.
:copyright: © 2019 Alexandria
"""
import json
import os
import zipfile
from functools import wraps
from pathlib import Path
from shutil import (
copytree,
rmtree,
)
import zeep
from cdiscountapi.packages import (
OfferPackage,
ProductPackage,
)
PRODUCT_CONDITIONS = {
"LikeNew": 1,
"VeryGoodState": 2,
"GoodState": 3,
"AverageState": 4,
"Refurbished": 5,
"New": 6,
}
def check_package_type(package_type):
if package_type.lower() not in ("offer", "product"):
raise ValueError('package_type must be either "offer" or "product".')
# TODO Check the necessary files are present before adding them to the archive
def make_package(package_type, path):
"""
Insert the files necessary for the offer/product in a zip file
"""
current_path = path.cwd()
check_package_type(package_type)
os.chdir(path)
files = (
"Content/{}s.xml".format(package_type.capitalize()),
"_rels/.rels",
"[Content_Types].xml",
)
with zipfile.ZipFile(path.parent / (path.name + ".zip"), "w") as zf:
for f in files:
zf.write(f, compress_type=zipfile.ZIP_DEFLATED)
os.chdir(current_path)
# TODO Print the name of the package after its creation
# TODO Remove package_type. Determine package_type from the keys in data
def generate_package(package_type, package_path, data, overwrite=True):
"""
Generate a zip package for the offers or the products
Usage::
generate_package(package_type, package_path, data)
Example::
# Generate Offer package:
generate_package('offer', package_path, {'OfferCollection': offers,
'OfferPublicationList': offer_publications,
'PurgeAndReplace': purge_and_replace})
# Generate Product package:
generate_package('product', package_path, {'Products': products})
:param str package_type: 'offer' or 'product'
:param str package_path: the full path to the package (without .zip)
:param dict data: offers or products as you can see on
tests/samples/products/products_to_submit.json or
tests/samples/offers/offers_to_submit.json
"""
check_package_type(package_type)
package_path = Path(package_path)
package = package_path.with_suffix(".zip")
# The directory in which the package will be created must exist
if not package_path.parent.exists():
raise FileNotFoundError(
"The directory {} does not exist.".format(package_path.parent)
)
if overwrite:
if package.exists():
package.unlink()
if package_path.exists():
rmtree(package_path)
else:
if package.exists():
raise FileExistsError("The package {} already exists.".format(package))
if package_path.exists():
raise FileExistsError("The package_path {} already exists.".format(package_path))
# Copy tree package.
package_template = os.path.join(
os.path.dirname(__file__),
"packages",
f"{package_type}_package"
)
package = copytree(package_template, package_path)
xml_filename = package_type.capitalize() + "s.xml"
# TODO Fix offer_dict
# Add Products.xml from product_dict.
with open(f"{package}/Content/{xml_filename}", "wb") as f:
xml_generator = XmlGenerator(data)
f.write(xml_generator.generate().encode("utf8"))
make_package(package_type, package_path)
rmtree(package_path)
print("Successfully created {}.zip".format(package_path))
def check_element(element_name, dynamic_type):
"""
Raise an exception if the is not in the dynamic_type
Example
>>> check_element('CarrierName', api.factory.ValidateOrder)
"""
valid_elements = [x[0] for x in dynamic_type.elements]
if element_name not in valid_elements:
raise TypeError(
f"{element_name} is not a valid element of {dynamic_type.name}."
f" Valid elements are {valid_elements}"
)
# TODO Damien: voir car l'utilisateur peut écrire
# "Shipping Fees" ou "ShippingFees" au lieu de "shipping_fees"
def get_motive_id(label):
label_to_motive_id = {
"compensation_on_missing_stock": 131,
"product_delivered_damaged": 132,
"product_delivered_missing": 132,
"error_of_reference": 133,
"error_of_color": 133,
"error_of_size": 133,
"fees_unduly_charged_to_the_customer": 134,
"late_delivery": 135,
"product_return_fees": 136,
"shipping_fees": 137,
"warranty_period_passed": 138,
"rights_of_withdrawal_passed": 138,
"others": 139,
}
if label not in label_to_motive_id:
raise KeyError(
"Please choose a valid label ({})".format(list(label_to_motive_id))
)
return label_to_motive_id[label]
# TODO Make sure the exceptions is well chosen for an outdated token
def auto_refresh_token(func):
"""
Refresh the token when it's outdated and resend the request
"""
@wraps(func)
def wrapper(*args, **kwargs):
self = args[0]
try:
return func(*args, **kwargs)
except zeep.exceptions.Fault:
print("Refreshing token...")
self.api.token = self.api.get_token()
self.api.header["Security"]["TokenId"] = self.api.token
print("Resending request...")
return func(*args, **kwargs)
return wrapper
class XmlGenerator(object):
"""
Generate offers or products to upload
Usage::
xml_generator = XmlGenerator(data, preprod=preprod)
content = xml_generator.generate()
Example::
# Render the content of Offers.xml
shipping_info1 = {
'AdditionalShippingCharges': 1,
'DeliveryMode': 'RelaisColis',
'ShippingCharges': 1,
}
shipping_info2 = {
'AdditionalShippingCharges': 5.95,
'DeliveryMode': 'Tracked',
'ShippingCharges': 2.95
}
discount_component = {
'StartDate': datetime.datetime(2019, 11, 23),
'EndDate': datetime.datetime(2019, 11, 25),
'Price': 85,
'DiscountValue': 1,
'Type': 1
}
offer = {
'ProductEan': 1,
'SellerProductId': 1,
'ProductCondition': '6'
'Price': 100,
'EcoPart': 0,
'Vat': 0.19,
'DeaTax': 0,
'Stock': 1,
'Comment': 'Offer with discount Tracked or RelaisColis'
'PreparationTime': 1,
'PriceMustBeAligned': 'Align',
'ProductPackagingUnit': 'Kilogram',
'ProductPackagingValue': 1,
'MinimumPriceForPriceAlignment': 80,
'StrikedPrice': 150,
'DiscountList': {'DiscountComponent': [discount_component]},
'ShippingInformationList': {'ShippingInformation': [shipping_info1, shipping_info2]}
}
offers_xml = XmlGenerator({'OfferCollection': [offer],
'PurgeAndReplace': False,
'OfferPublicationList': [1, 16],
'Name': 'A package name',
'PackageType': "Full",
},
preprod=preprod)
content = offers_xml.generate()
# Render the content of Products.xml
products_xml = XmlGenerator({'Products': [product]}, preprod=preprod)
content = products_xml.generate()
"""
def __init__(self, data, preprod=False):
if OfferPackage.has_required_keys(data):
self.package = OfferPackage(data, preprod)
elif ProductPackage.has_required_keys(data):
self.package = ProductPackage(data, preprod)
else:
msg = (
"The data should be a dictionary with the keys {offers} for"
"Offers.xml and {products} for Products.xml".format(
offers=OfferPackage.required_keys,
products=ProductPackage.required_keys,
)
)
raise ValueError(msg)
self.data = self.package.data
def add(self, data):
self.package.add(data)
def generate(self):
return self.package.generate()
def analyze_offer_report_property_log(response):
"""
Return the meaning of the PropertyCode and PropertyError in the
node OfferReportPropertyLog returned by GetOfferPackageSubmissionResult
"""
with open("cdiscountapi/assets/offer.json", "r") as f:
codes = json.load(f)
meanings = []
for offer_report_property_log in response["OfferLogList"]["OfferReportLog"][
"PropertyList"
]:
meanings.append(
{
"PropertyCode": codes["property_codes"].get(
offer_report_property_log["PropertyCode"], ""
),
"PropertyError": codes["error_codes"].get(
offer_report_property_log["PropertyError"], ""
),
}
)
return meanings
PK |DO cdiscountapi/.tox/.package.lockPK |DOHw cdiscountapi/assets/config.yamlVersion: '1.0'
Context:
SiteID: 100
CatalogID: 1
Localization:
Country: 'Fr'
Currency: 'Eur'
DecimalPosition: 2
Security:
UserName: ''
PK |DOvt cdiscountapi/assets/offer.json{
"property_codes": {
"0": "",
"1": "EAN",
"2": "Product status",
"3": "Seller product reference",
"4": "SKU",
"5": "Stock",
"6": "Price",
"7": "VAT",
"8": "Eco part",
"9": "Min shipping time",
"10": "Max shipping time",
"11": "Shipping fee - Standard mode",
"12": "Shipping fee - Tracked mode",
"13": "Shipping fee - Registered mode",
"14": "Offers comment",
"15": "Additional Shipping fee - Standard mode",
"16": "Additional Shipping fee - Tracked mode",
"17": "Additional Shipping fee - Registered mode",
"18": "Reference price",
"19": "Promotion type",
"20": "Promotion type - Start date",
"21": "Promotion type - Start hour",
"22": "Promotion type - End date",
"23": "Package",
"24": "Product",
"25": "Promotion type - End Hour",
"26": "Discount %",
"27": "Sales Reference Price",
"28": "Offer",
"29": "Delivery charges - Mode Immediate Withdrawal",
"30": "Additional Delivery Fee - Immediate Withdrawal Mode",
"31": "DEA",
"40": "Store delivery",
"41": "Store delivery - Min shipping time",
"42": "Store delivery - Max shipping time",
"43": "Store delivery - shipping fee",
"44": "Store delivery - Additional shipping fee",
"45": "Home delivery",
"46": "Lowest allowable price",
"48": "Mondial Relay pick up - Min shipping time",
"49": "Mondial Relay pick up - Shipping fee",
"50": "Mondial Relay pick up - Shipping fee",
"51": "Mondial Relay pick up - Additional shipping fee",
"52": "Relais colis pick up",
"53": "Relais colis pick up - Min shipping time",
"54": "Relais colis pick up - Max shipping time",
"55": "Relais colis pick up - Shipping fee",
"56": "Relais colis pick up - Additional shipping fee",
"57": "SoColissimo pick up",
"58": "SoColissimo pick up - Min shipping time",
"59": "SoColissimo pick up - Max shipping time",
"60": "SoColissimo pick up - Shipping fee",
"61": "SoColissimo pick up - Additional shipping fee",
"62": "Packaging unit",
"63": "Product packaging",
"64": "Fullfilment"
},
"error_codes": {
"10": "Required field, please enter data",
"11": "Incorrect data format, check the required format",
"12": "Data incorrect length, check the required format",
"13": "Non existent - Request its creation in the 'product creation' section of the seller interface",
"14": "Product already known",
"15": "Ask permission to sell this product to the seller support",
"16": "Invalid key, check EAN",
"17": "Incorrect value as greater than the maximum value, check the consistency of values",
"17": "Incorrect value as lower than the minimum value, check the consistency of values",
"18": "Not enough stock relative to pending orders",
"19": "Incorrect value, price lower than or equal to €0",
"20": "Incorrect value, reference price lower than 0,10€",
"21": "Error occurred during integration",
"23": "Incorrect value, reference price lower than the sale price",
"24": "A offer already exists for this produit with another reference, impossible to modify, please use the old reference",
"25": "Seller reference already used for another EAN or another produit condition. Please change your reference",
"26": "End date/time earlier than Start date/time",
"27": "Products with variations (size/colour): to be created for your seller account in the 'product creation'",
"28": "Offer already submitted and awaiting processing",
"29": "Price {0} is an amount lower than the eco part + DEA",
"30": "Reference unknown. You must create your product before putting an offer, or put an offer on an existing product using the EAN",
"31": "You have several products with the reference seller. Please use the EAN",
"32": "Off (being activated), the offer is on hold, it will be published in the hour",
"34": "Existing but not open to the marketplace. Please contact the sellersupport",
"35": "Value greater than the permitted ceiling 30.00",
"36": "This offer does not exist and can not be updated. You must first create",
"39": "Fulfillment: Product dereferenced of Fulfillment with stock> 0. You must take all your products on our warehouse before reactivating this offer",
"40": "Not configured - You must set the {0} Mode 'in the section' Your shipping choices 'before entering the file data'",
"43": "Not accessible to your subscription",
"44": "Thank you to first take back your stock ....",
"45": "Non-salable only",
"48": "Several eans match the product, please contact the account manager",
"80": "Offer created",
"90": "Offer updated"
}
}
PK 2\OI" " ! cdiscountapi/packages/__init__.py# -*- coding: utf-8 -*-
"""
cdiscountapi.packages
---------------------
Implements the Offers.xml and Products.xml content generation.
:copyright: © 2019 Alexandria
"""
# Python imports
from copy import deepcopy
# Third-party imports
import zeep
from jinja2 import Environment, FileSystemLoader
from cdiscountapi.packages.validator import (
OfferValidator,
ProductValidator,
DiscountComponentValidator,
ShippingInformationValidator,
ProductEanValidator,
ProductImageValidator,
)
class BasePackage(object):
required_keys = []
def __init__(self, preprod=False):
self.preprod = preprod
if self.preprod:
domain = "preprod-cdiscount.com"
else:
domain = "cdiscount.com"
self.wsdl = "https://wsvc.{0}/MarketplaceAPIService.svc?wsdl".format(domain)
self.client = zeep.Client(self.wsdl)
self.factory = self.client.type_factory("http://www.cdiscount.com")
self.data = []
def validate(self, **kwargs):
raise NotImplementedError
def generate(self):
raise NotImplementedError
@classmethod
def has_required_keys(cls, data):
"""
Return True if the data passed to the package have the required keys
"""
for required_key in cls.required_keys:
if required_key not in data.keys():
return False
return True
class OfferPackage(BasePackage):
required_keys = ["OfferCollection"]
def __init__(self, data, preprod=False):
super().__init__(preprod=preprod)
self.check_offer_publication_list(data.get("OfferPublicationList"))
self.purge_and_replace = data.get("PurgeAndReplace", False)
self.name = data.get("Name", "A package")
self.package_type = data.get("PackageType", "Full")
self.add(data["OfferCollection"])
def check_offer_publication_list(self, ids):
"""
The offer_publication_list should be a list of integers representing
the id of the marketplaces if it exists.
"""
if not ids:
self.offer_publication_list = []
return None
msg = (
"The value OfferPublicationList should be a list of"
" integers representing the ids of the marketplaces."
)
if not isinstance(ids, (list, tuple)):
raise TypeError(msg)
for _id in ids:
if not isinstance(_id, int):
raise TypeError(msg)
self.offer_publication_list = [{"Id": _id} for _id in ids]
def add(self, offers):
for offer in offers:
valid_offer = self.validate(**offer)
if valid_offer not in self.data:
self.data.append(valid_offer)
def extract_from(self, offer, attr1, attr2):
"""
Extract the elements of a list from Offer
(ex: the ShippingInformation elements in ShippingInformationList,
the DiscountComponent elements in DiscountList...)
"""
if attr1 in offer:
sub_record = offer.get(attr1, None)
if sub_record:
datum = sub_record[attr2]
del offer[attr1]
return datum
else:
return []
else:
return []
def validate(self, **kwargs):
"""
Return the valid offer
Usage::
offer_package.validate(**kwargs)
"""
new_kwargs = kwargs.copy()
# We validate the lists in Offer
if "DiscountList" in kwargs:
new_kwargs["DiscountList"] = {
"DiscountComponent": [
DiscountComponentValidator.validate(x)
for x in new_kwargs["DiscountList"]["DiscountComponent"]
]
}
if "ShippingInformationList" in kwargs:
new_kwargs["ShippingInformationList"] = {
"ShippingInformation": [
ShippingInformationValidator.validate(x)
for x in new_kwargs["ShippingInformationList"][
"ShippingInformation"
]
]
}
return OfferValidator.validate(new_kwargs["Offer"])
def generate(self):
loader = FileSystemLoader("cdiscountapi/templates")
env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
template = env.get_template("Offers.xml")
offers = deepcopy(self.data)
extraction_mapping = {
"shipping_information_list": (
"ShippingInformationList",
"ShippingInformation",
),
"discount_list": ("DiscountList", "DiscountComponent"),
}
offers_data = []
for offer in offers:
offers_datum = {}
for key, (attr1, attr2) in extraction_mapping.items():
# Only add if the user provided the attribute
if attr1 in offer:
if key not in offers_datum and attr1 in offer:
offers_datum[key] = []
offers_datum[key].extend(self.extract_from(offer, attr1, attr2))
if "attributes" not in offers_datum:
offers_datum["attributes"] = ""
# We keep only key:value pairs whose values are not None
offers_datum["attributes"] += " ".join(
'{}="{}"'.format(k, v) for k, v in offer.items() if v is not None
)
offers_data.append(offers_datum)
return template.render(
offers=offers_data,
offer_publication_list=self.offer_publication_list,
purge_and_replace=self.purge_and_replace,
package_type=self.package_type,
name=self.name
)
class ProductPackage(BasePackage):
required_keys = ["Products"]
def __init__(self, data, preprod=False):
super().__init__(preprod=preprod)
self.add(data["Products"])
def add(self, products):
for product in products:
valid_product = self.validate(**product)
if valid_product not in self.data:
self.data.append(valid_product)
def extract_from(self, product, attr1, attr2):
"""
Extract the elements of a list from Offer
(ex: the ShippingInformation elements in ShippingInformationList,
the DiscountComponent elements in DiscountList...)
"""
if attr1 in product:
sub_record = product.get(attr1, None)
if sub_record:
datum = sub_record[attr2]
del product[attr1]
return datum
else:
return []
else:
return []
def validate(self, **kwargs):
new_kwargs = kwargs.copy()
if "EanList" in kwargs:
new_kwargs["EanList"] = {
"ProductEan": [
ProductEanValidator.validate(x)
for x in new_kwargs["EanList"]["ProductEan"]
]
}
if "Pictures" in kwargs:
new_kwargs["Pictures"] = {
"ProductImage": [
ProductImageValidator.validate(x)
for x in new_kwargs["Pictures"]["ProductImage"]
]
}
return ProductValidator.validate(new_kwargs["Product"])
def generate(self):
loader = FileSystemLoader("cdiscountapi/templates")
env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
template = env.get_template("Products.xml")
products = deepcopy(self.data)
extraction_mapping = {
"EanList": ("EanList", "ProductEan"),
"Pictures": ("Pictures", "ProductImage"),
}
products_data = []
for product in products:
products_datum = {}
for key, (attr1, attr2) in extraction_mapping.items():
if key not in products_datum:
products_datum[key] = []
products_datum[key].extend(self.extract_from(product, attr1, attr2))
if "ModelProperties" in product:
products_datum["ModelProperties"] = product["ModelProperties"]
del product["ModelProperties"]
if "attributes" not in products_datum:
products_datum["attributes"] = ""
# We keep only key:value pairs whose values are not None
products_datum["attributes"] += " ".join(
'{}="{}"'.format(k, v) for k, v in product.items() if v is not None
)
products_data.append(products_datum)
capacity = sum(len(p['Pictures']) for p in products_data)
return template.render(products=products_data, capacity=capacity)
PK ZOd(`
" cdiscountapi/packages/validator.py# Project imports
from cdiscountapi.exceptions import ValidationError
class BaseValidator(object):
required = set()
optional = set()
@classmethod
def package_type(cls):
return cls.__name__.split("Validator")[0]
@classmethod
def validate(cls, data):
"""
Return the data if the keys are valid attributes
"""
provided = set(data.keys())
missing_required = cls.required - provided
if len(missing_required) > 0:
raise ValidationError(
"Missing required attributes for {}: {}".format(
cls.package_type(), missing_required
)
)
invalid_attributes = provided - cls.required - cls.optional
if len(invalid_attributes) > 0:
raise ValidationError(
"These attributes are not valid: {}."
" Please use only the following ones if necessary: {}".format(
invalid_attributes, cls.optional
)
)
return data
class OfferValidator(BaseValidator):
required = {
"ProductEan",
"SellerProductId",
"ProductCondition",
"Price",
"EcoPart",
"Vat",
"DeaTax",
"Stock",
"PreparationTime",
}
optional = {
"Comment",
"StrikedPrice",
"PriceMustBeAligned",
"MinimumPriceForPriceAlignment",
"ProductPackagingUnit",
"ProductPackagingValue",
"BluffDeliveryMax",
"DiscountList",
"ShippingInformationList",
}
class ProductValidator(BaseValidator):
required = {
"ShortLabel",
"SellerProductId",
"CategoryCode",
"ProductKind",
"Model",
"LongLabel",
"Description",
"BrandName",
"EanList",
"Pictures",
}
optional = {
"Width",
"Weight",
"Size",
"SellerProductFamily",
"SellerProductColorName",
"ManufacturerPartNumber",
"Length",
"ISBN",
"Height",
"EncodedMarketingDescription",
"ModelProperties",
"Navigation",
}
class DiscountComponentValidator(BaseValidator):
required = {"DiscountValue", "Type", "StartDate", "EndDate"}
class ShippingInformationValidator(BaseValidator):
required = {"ShippingCharges", "AdditionalShippingCharges", "DeliveryMode"}
class ProductEanValidator(BaseValidator):
required = {"Ean"}
class ProductImageValidator(BaseValidator):
required = {"Uri"}
PK |DOڸPN 7 cdiscountapi/packages/offer_package/[Content_Types].xml
PK |DO 6 cdiscountapi/packages/offer_package/Content/Offers.xmlPK rO: / cdiscountapi/packages/offer_package/_rels/.rels
PK |DOpWN N 9 cdiscountapi/packages/product_package/[Content_Types].xml
PK |DO : cdiscountapi/packages/product_package/Content/Products.xmlPK |DOGj> 1 cdiscountapi/packages/product_package/_rels/.rels
PK |DO^ ! cdiscountapi/sections/__init__.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections
---------------------
The different sections available to interact with the API.
:copyright: © 2019 Alexandria
"""
from .seller import Seller
from .offers import Offers
from .products import Products
from .orders import Orders
from .relays import Relays
from .fulfillment import Fulfillment
from .webmail import WebMail
from .discussions import Discussions
PK |DOsPZD| | cdiscountapi/sections/base.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.base
--------------------------
Base class for the different section classes
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
class BaseSection(object):
def __init__(self, api):
self.api = api
self.arrays_factory = self.api.client.type_factory(
"http://schemas.microsoft.com/2003/10/Serialization/Arrays"
)
def array_of(self, type_name, sequence):
"""
Cast the sequence into an array of the given type.
The arrays are defined in the XSD file
(cf http://schemas.microsoft.com/2003/10/Serialization/Arrays)
(cf https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-ipamm/38d7c101-385d-4180-bb95-983955f41e19) ?
"""
valid_type_names = (
"int",
"string",
"long",
"KeyValueOfstringArrayOfstringty7Ep6D1",
"KeyValueOfintstring",
)
if type_name not in valid_type_names:
raise TypeError(
"Invalid type_name. "
"Please choose between {}".format(valid_type_names)
)
array = getattr(self.arrays_factory, "ArrayOf{}".format(type_name))(sequence)
return serialize_object(array, dict)
def update_with_valid_array_type(self, record, keys_to_cast):
"""
Update the dictionary with the array having the valid type
:param dict record: The dictionary to update
:param dict keys_to_cast: The dictionary specifying what keys to convert and what types
Example::
section = BaseSection(api)
new_record = section.update_with_valid_array_type(
{'DepositIdList': [1, 2, 3], 'PageSize': 10},
{'DepositIdList': 'int'}
)
:returns: The updated dictionary with the valid array type
"""
new_record = record.copy()
for key, type_name in keys_to_cast.items():
if key in new_record:
new_record[key] = self.array_of(type_name, new_record[key])
return new_record
PK |DOQQY Y $ cdiscountapi/sections/discussions.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.discussions
---------------------------------
Handles the discussions.
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
from .base import BaseSection
from ..helpers import auto_refresh_token
class Discussions(BaseSection):
"""
There are 3 ways to get discussions: discussions id, the discussions
status, and all messages.
Thanks to the discussion Id and the method GetDiscussionMailList you can
get an encrypted mail address to reply to a question or a claim. You can
close a discussion list with the method CloseDiscussionList and the
Discussion id, you cannot close a discussion without having answered.
Methods::
get_order_claim_list(**order_claim_filter)
get_offer_question_list(**offer_question_filter)
get_order_question_list(**order_question_filter)
close_discussion_list(discussion_ids)
Operations are included in the Discussions API section.
(https://dev.cdiscount.com/marketplace/?page_id=148)
"""
@auto_refresh_token
def get_order_claim_list(self, **order_claim_filter):
"""
Return the list of order claims
"""
order_claim_filter = self.api.factory.OrderClaimFilter(**order_claim_filter)
response = self.api.client.service.GetOrderClaimList(
headerMessage=self.api.header, orderClaimFilter=order_claim_filter
)
return serialize_object(response, dict)
@auto_refresh_token
def get_offer_question_list(self, **offer_question_filter):
"""
Return the list of questions about offers with the specified criteria
:param offer_question_filter: The keywords for the filter
``offerQuestionFilter``:
- BeginCreationDate
- BeginModificationDate
- EndCreationDate
- EndModificationDate
- StatusList
- DiscussionStateFilter can have the values
- All
- Open
- Closed
- NotProcessed
- ProductEANList
- ProductSellerReferenceList
Example::
response = api.get_offer_question_list(
StatusList={'DiscussionStateFilter': 'Open'},
BeginCreationDate='2019-01-01'
)
:returns: An OfferQuestionListMessage dictionary.
.. note:: A date is mandatory in the query.
"""
offer_question_filter = self.api.factory.OfferQuestionFilter(
**offer_question_filter
)
response = self.api.client.service.GetOfferQuestionList(
headerMessage=self.api.header, offerQuestionFilter=offer_question_filter
)
return serialize_object(response, dict)
@auto_refresh_token
def get_order_question_list(self, **order_question_filter):
"""
Return the list of questions about orders with the specified criteria
:param order_question_filter: The keywords for the filter
``orderQuestionFilter``:
- BeginCreationDate
- BeginModificationDate
- EndCreationDate
- EndModificationDate
- StatusList
- DiscussionStateFilter can have the values
- All
- Open
- Closed
- NotProcessed
Example::
response = api.get_order_question_list(
StatusList={'DiscussionStateFilter': 'Open'},
BeginCreationDate='2019-01-01'
)
:returns: An OrderQuestionListMessage dictionary.
.. note:: A date is mandatory in the query.
"""
order_question_filter = self.api.factory.OrderQuestionFilter(
**order_question_filter
)
response = self.api.client.service.GetOrderQuestionList(
headerMessage=self.api.header, orderQuestionFilter=order_question_filter
)
return serialize_object(response, dict)
@auto_refresh_token
def close_discussion_list(self, discussion_ids):
"""
Close a discussion list
:param list discussion_ids: The list of discussion_ids to close
Usage::
response = api.discussions.close_discussion_list([31, 4, 159])
"""
close_discussion_request = self.api.factory.CloseDiscussionRequest(
DiscussionIds=self.array_of("long", discussion_ids)
)
response = self.api.client.service.CloseDiscussionList(
headerMessage=self.api.header,
closeDiscussionRequest=close_discussion_request,
)
return serialize_object(response, dict)
PK |DOm5 5 $ cdiscountapi/sections/fulfillment.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.fulfillment
---------------------------------
Handles the supply order fulfillment.
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
from .base import BaseSection
from ..helpers import auto_refresh_token, check_element
class Fulfillment(BaseSection):
"""
Allows to manage the fulfillment of the supply orders.
Methods::
submit_fulfillment_supply_order(prod_desc_list)
submit_fulfillment_on_demand_supply_order(order_list)
get_fulfillment_supply_order_report_list(**request)
get_fulfillment_delivery_document(deposit_id)
get_fulfillment_supply_order
submit_offer_state_action(request)
get_fulfillment_activation_report_list(**request)
get_fulfillment_order_list_to_supply(**request)
submit_offer_state_action(**request)
get_external_order_status(**request)
get_product_stock_list(**request):
Operations are included in the Fulfillment API section
(https://dev.cdiscount.com/marketplace/?page_id=2222)
"""
@auto_refresh_token
def submit_fulfillment_supply_order(self, prod_desc_list):
"""
This operation create a deposit that will asynchronously triggered supply order creation.
:param prod_desc_list: list of FulfilmentProductDescription:
- ProductEAN *(str)*: [Mandatory]
- Warehouse *(str)*: [Mandatory]
- "CEM"
- "ANZ"
- "SMD"
- Quantity *(int)*: [Mandatory]
- ExtSupplyOrderID *(str)*: Seller external supply order reference
- WarehouseReceptionMinDate *(date)*
- SellerProductReference *(str)*
Example::
response = api.fulfillment.submit_fulfillment_supply_order(
[
{
'ProductEAN': '1051248556517',
'Warehouse': 'SMD'
'Quantity': 3,
},
{
'ProductEAN': '1234567891234',
'Warehouse': 'ANZ',
'Quantity': 122,
'SellerProductReference': '154'
}
]
)
:return:
"""
for desc in prod_desc_list:
check_element(desc, self.api.factory.FulfilmentProductDescription)
response = self.api.client.service.SubmitFulfilmentSupplyOrder(
headerMessage=self.api.header, request=prod_desc_list
)
return serialize_object(response, dict)
@auto_refresh_token
def submit_fulfillment_on_demand_supply_order(self, order_list):
"""
:param order_list: list of dict as:
[{'OrderReference': 1703182124BNXCO,'ProductEan': 2009854780777}]
Example::
response = api.fulfillment.submit_fulfillment_on_demand_supply_order(
order_list= [
{
'OrderReference': '1703182124BNXCO',
'ProductEan': 2009854780777
},
{
'OrderReference': '2813182124AMYBP',
'ProductEan': '3009854780789'
}
]
"""
for order in order_list:
check_element(order, self.api.factory.FulfilmentOrderLineRequest)
response = self.api.client.service.SubmitFulfilmentOnDemandSupplyOrder(
headerMessage=self.api.header, request={"OrderLineList": order_list}
)
return serialize_object(response, dict)
@auto_refresh_token
def get_fulfillment_supply_order_report_list(self, **request):
"""
To search supply order reports.
:param date BeginCreationDate:
:param list DepositIdList: list of ints
:param date EndCreationDate:
:param int PageNumber:
:param int PageSize:
Examples::
response = api.fulfillment.get_fulfillment_supply_order_report_list(
PageSize=10,
BeginCreationDate=datetime.datetime(2019, 1, 1),
EndCreationDate=datetime.datetime(2019, 1, 2),
)
response = api.fulfillment.get_fulfillment_supply_order_report_list(
PageSize=10, DepositIdList=[1, 2, 3]
)
:return: supply order reports
"""
request = self.update_with_valid_array_type(request, {"DepositIdList": "int"})
supply_order_report_request = self.api.factory.SupplyOrderReportRequest(
**request
)
response = self.api.client.service.GetFulfilmentSupplyOrderReportList(
headerMessage=self.api.header, request=supply_order_report_request
)
return serialize_object(response, dict)
@auto_refresh_token
def get_fulfillment_delivery_document(self, deposit_id):
"""
To get pdf document data for a supply order delivery, in the form of a Base64-encoded string.
:param int deposit_id: Unique identification number of the supply order request
Usage::
response = api.fulfillment.get_fulfillment_delivery_document(233575)
:return: data for printing PDF documents, in the form of a Base64-encoded string.
"""
response = self.api.client.service.GetFulfilmentDeliveryDocument(
headerMessage=self.api.header, request={"DepositId": deposit_id}
)
return serialize_object(response, dict)
@auto_refresh_token
def get_fulfillment_supply_order(self, **request):
"""
:param int PageSize:
:param date BeginModificationDate:
:param date EndModificationDate:
:param int PageNumber:
:param list SupplyOrderNumberList: list of strings
Examples::
response = api.fulfillment.get_fulfillment_supply_order(
PageSize=10,
BeginCreationDate=datetime.datetime(2019, 1, 1),
EndCreationDate=datetime.datetime(2019, 1, 2),
)
response = api.fulfillment.get_fulfillment_supply_order(
SupplyOrderNumberList=['X', 'Y', 'Z']
)
:return: supply orders
"""
request = self.update_with_valid_array_type(
request, {"SupplyOrderNumberList": "string"}
)
supply_order_report_request = self.api.factory.SupplyOrderRequest(**request)
response = self.api.client.service.GetFulfilmentSupplyOrder(
headerMessage=self.api.header, request=supply_order_report_request
)
return serialize_object(response, dict)
# TODO Make this method more robust
@auto_refresh_token
def submit_fulfillment_activation(self, request):
"""
To ask for products activation (or deactivation)
:param list ProductList:
- Action *(str)*:
- 'Activation'
- 'Deactivation'
- Length *(double/float64)*:
- Width *(double/float64)*:
- Height *(double/float64)*:
- Weight *(double/float64)*:
- ProductEAN *(str)*:
- SellerProductReference *(str)*: [optional]
Example::
api.fulfillment.submit_fulfillment_activation(
product_list = [
{
'Action': 'Activation',
'Height': 1,
'Length': 20,
'ProductEAN': '2009863600561'
'Weight': 50,
'Width': 10
},
{
'Action': 'Activation',
'Height': 1,
'Length': 20,
'ProductEAN': 'BZ34567891234',
'Weight': 20,
'Width': 10
)
:return: deposit id
"""
response = self.api.client.service.SubmitFulfilmentActivation(
headerMessage=self.api.header, request=request
)
return serialize_object(response, dict)
@auto_refresh_token
def get_fulfillment_activation_report_list(self, **request):
"""
To get status and details about fulfillment products activation.
:param str BeginDate: date
:param list DepositIdList: int list
:param str EndDate: date
Example::
response = api.fulfillment.get_fulfillment_activation_report_list(
BeginDate='2019-04-26T09:54:38:72'
)
"""
activation_report_request = self.api.factory.FulfilmentActivationReportRequest(
**request
)
response = self.api.client.service.GetFulfilmentActivationReportList(
headerMessage=self.api.header, request=activation_report_request
)
return serialize_object(response, dict)
@auto_refresh_token
def get_fulfillment_order_list_to_supply(self, **request):
"""
To ask for fulfillment on demand order lines to supply.
:param str OrderReference:
:param str ProductEan:
:param str Warehouse: name of warehouses where products are stored:
- 'CEM'
- 'ANZ'
- 'SMD'
Example::
response = api.fulfillment.get_fulfillment_order_list_to_supply(
ProductEan='2009863600561'
)
:return: fulfillment on demand order lines to supply answering the search criterion.
"""
references = self.api.factory.FulfilmentOnDemandOrderLineFilter(**request)
response = self.api.client.service.GetFulfilmentActivationReportList(
headerMessage=self.api.header, request=references
)
return serialize_object(response, dict)
@auto_refresh_token
def submit_offer_state_action(self, **request):
"""
To set an offer online or offline
:param str Action: 'Publish' or 'Unpublish'
:param str SellerProductId:
Usage::
response = api.fulfillment.submit_offer_state_action(
Action='Unpublish',
SellerProductId='11504'
)
:return:
"""
seller_action = self.api.factory.OfferStateActionRequest(**request)
response = self.api.client.service.SubmitOfferStateAction(
headerMessage=self.api.header, offerStateRequest=seller_action
)
return serialize_object(response, dict)
@auto_refresh_token
def create_external_order(self, **order):
"""
create an order from another marketplace.
:param order:
-
Example::
response = api.fufillment.create_external_order(
Comments: str,
Corporation: str (ex:FNAC),
Customer: customer info dict,
CustomerOrderNumber: str,
OrderDate: date,
ShippingMode: str,
OrderLineList: [
'ExternalOrderLine': {
'ProductEan': str,
'ProductReference': str,
'Quantity': int
}
],
}
:return: The response
"""
response = self.api.client.service.CreateExternalOrder(
headerMessage=self.api.header, request={"Order": order}
)
return serialize_object(response, dict)
@auto_refresh_token
def get_external_order_status(self, **request):
"""
to get order integration status
:param str Corporation: website from which the order comes.
:param str CustomerOrderNumber:
Usage::
response = api.fufillment.get_external_order_status(
Corporation='FNAC',
CustomerOrderNumber='100-00110101-10011100'
)
:return: Status ("OK", "Pending", "KO")
"""
response = self.api.client.service.GetExternalOrderStatus(
headerMessage=self.api.header,
request={
"Corporation": request.get("Corporation"),
"CustomerOrderNumber": request.get("CustomerOrderNumber"),
},
)
return serialize_object(response, dict)
@auto_refresh_token
def get_product_stock_list(self, **request):
"""
List seller product
:param list BarCodeList: list of EAN
:param str FulfilmentReferencement:
- 'All'
- 'OnlyReferenced'
- 'OnlyNotReferenced'
:param str ShippableStock:
- 'All'
- 'WithStock'
- 'WithoutStock'
- 'ShippableStock'
:param str BlockedStock:
- 'All'
- 'WithStock'
- 'WithoutStock'
- 'BlockedStock'
:param str SoldOut:
- 'None'
- 'All'
- 'InSoldOut'
- 'InSoldOutFiveDays'
- 'InSoldOutFifteenDays'
- 'SoldOut'
Example::
response = api.fulfillment.get_product_stock_list(
FulfilmentReferencement='OnlyReferenced',
ShippableStock='WithStock',
BlockedStock='All',
SoldOut='InSoldOut'
)
:return: ProductStockList, Status ("OK", "NoData", "KO") and TotalProductCount
"""
response = self.api.client.service.GetProductStockList(
headerMessage=self.api.header, request=request
)
return serialize_object(response, dict)
PK |DO`W! ! cdiscountapi/sections/offers.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.offers
----------------------------
Handles the offers.
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
from cdiscountapi.helpers import generate_package
from .base import BaseSection
from ..helpers import auto_refresh_token
class Offers(BaseSection):
"""
Offers section lets sellers retrieve information about their offers.
Methods::
get_offer_list(**filters)
get_offer_list_paginated(**filters)
generate_offer_package(package_name,
offers_list,
offer_publication_list=offer_publication_list,
purge_and_replace=purge_and_replace)
submit_offer_package(url)
get_offer_package_submission_result(package_id)
Operations are included in the Products API section
(https://dev.cdiscount.com/marketplace/?page_id=84)
"""
@auto_refresh_token
def get_offer_list(self, **filters):
"""
To search offers.
This operation seeks offers according to the
following criteria:
- SellerProductIdList: list of seller product references
- OfferPoolId (int) is the distribution website Id
Example::
response = api.offers.get_offer_list(
SellerProductIdList=['REF1', 'REF2', 'REF3'],
OfferPoolId=1
)
:return: offers answering the search criterion
"""
filters = self.update_with_valid_array_type(
filters, {"SellerProductIdList": "string"}
)
offer_filter = self.api.factory.OfferFilter(**filters)
response = self.api.client.service.GetOfferList(
headerMessage=self.api.header, offerFilter=offer_filter
)
return serialize_object(response, dict)
@auto_refresh_token
def get_offer_list_paginated(self, **filters):
"""
Recovery of the offers page by page.
- PageNumber (int) [mandatory]
- OfferFilterCriterion (str):
- 'NewOffersOnly'
- 'UsedOffersOnly'
- OfferPoolId (int) is the distribution website Id
- OfferSortOrder (str):
- ByPriceAscending
- ByPriceDescending
- BySoldQuantityDescending
- ByCreationDateDescending
- OfferStateFilter (str):
- WaitingForProductActivation
- Active
- Inactive
- Archived
- Fulfillment
- SellerProductIdList (list of str)
Example::
response = api.offers.get_offer_list_paginated(
PageNumber=1,
OfferFilterCriterion='NewOffersOnly',
OfferSortOrder='BySoldQuantityDescending',
OfferStateFilter='Active'
)
:return: offers answering the search criterion
"""
filters = self.update_with_valid_array_type(
filters, {"SellerProductIdList": "string"}
)
offer_filter = self.api.factory.OfferFilterPaginated(**filters)
response = self.api.client.service.GetOfferListPaginated(
headerMessage=self.api.header, offerFilter=offer_filter
)
return serialize_object(response, dict)
@staticmethod
def generate_offer_package(
package_name,
package_path,
offers_list,
package_type="Full",
offer_publication_list=[],
purge_and_replace=False,
overwrite=True
):
"""
Generate a zip offers package as cdiscount wanted.
:param str package_name: The name of the package
:param str package_path: [mandatory] the full path to the offer package (without .zip)
:param str package_type: [optional] The type of package ("Full" or
"StockAndPrice")
(default: "Full")
:param list offer_publication_list: [optional]
:param bool purge_and_replace: [optional]
:param bool overwrite: [optional] Determine if an existing package is
overwritten when a new one with the same name is created (default: True)
:param list offers_list: list of dict [{offer, shipping}, ...]:
-Offer:
- Mandatory attributes:
- ProductEan *(str)*
- SellerProductId *(str)*
- ProductCondition *(int)*:
- 1: 'LikeNew',
- 2: 'VeryGoodState',
- 3: 'GoodState',
- 4: 'AverageState',
- 5: 'Refurbished',
- 6: 'New',
- Price *(float)*
- EcoPart *(float)*
- Vat *(float)*
- DeaTax *(float)*
- Stock *(int)*
- PreparationTime *(int)*
- Optional attributes:
- Comment *(str)*
- StrikedPrice *(float)*
- PriceMustBeAligned *(str)*:
- 'Empty',
- 'Unknown',
- 'Align',
- 'DontAlign',
- MinimumPriceForPriceAlignment *(float)*
- ProductPackagingUnit *(str)*:
- 'None',
- 'Liter',
- 'Kilogram',
- 'SquareMeter',
- 'CubicMeter'
- ProductPackagingValue *(float)*
- BluffDeliveryMax *(int)*
-ShippingInformation:
- AdditionalShippingCharges *(float)*
- DeliveryMode *(DeliveryModeInformation)*
- 'STD' ('Standart')
- 'TRK' ('Tracking')
- 'REG' ('Registered')
- 'COL' ('Collissimo')
- 'RCO' ('Relay Colis')
- 'REL' ('Mondial Relay')
- 'SO1' ('So Colissimo')
- 'MAG' ('in shop')
- 'LV1'
- 'LV2'
- 'LV'
- 'FST'
- 'EXP'
- 'RIM'
- ShippingCharges *(float)*
Example::
response = api.offers.generate_offer_package(
package_name,
package_path,
offers_list,
offer_publication_list=offer_publication_list,
purge_and_replace=purge_and_replace
)
:returns: None
"""
return generate_package(
"offer",
package_path,
{
"OfferCollection": offers_list,
"OfferPublicationList": offer_publication_list,
"PurgeAndReplace": purge_and_replace,
"Name": package_name,
"PackageType": package_type
},
overwrite=overwrite
)
@auto_refresh_token
def submit_offer_package(self, url):
"""
To import offers.
It is used to add new offers to the Cdiscount marketplace
or to modify/update offers that already exists.
.. note::
The wanted zip package could be generate by calling
``api.offers.generate_offer_package(offer_list)``
Then you'll have to get an url to download zip package
Finally, you can use submit_offer_package(url)
Examples::
api.offers.submit_offer_package(url)
:return: the id of package or -1
"""
offer_package = self.api.factory.OfferPackageRequest(url)
# Send request.
response = self.api.client.service.SubmitOfferPackage(
headerMessage=self.api.header, offerPackageRequest=offer_package
)
return serialize_object(response, dict)
@auto_refresh_token
def get_offer_package_submission_result(self, package_id):
"""
This operation makes it possible to know the progress report of the offers import.
:param long package_id: id of package we want to know the progress
:return: Offer report logs
"""
package = self.api.factory.PackageFilter(package_id)
response = self.api.client.service.GetOfferPackageSubmissionResult(
headerMessage=self.api.header, offerPackageFilter=package
)
return serialize_object(response, dict)
PK |DO'%n1C C cdiscountapi/sections/orders.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.orders
----------------------------
Handles the orders.
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
from cdiscountapi.helpers import check_element, get_motive_id
from .base import BaseSection
from ..helpers import auto_refresh_token
def update_date_filter(date_filter, p1, p2):
"""
Make sure the filter on a date with param p1 is always the same as param p2 if p2 is
not provided.
Example:
new_filter = update_date_filter(
{
'BeginCreationDate': datetime.datetime(2019, 9, 6)},
'BeginCreationDate', 'BeginModificationDate'
)
new_filter is:
{
'BeginCreationDate': datetime.datetime(2019, 9, 6),
'BeginModificationDate': datetime.datetime(2019, 9, 6),
}
"""
# If the first parameter is not in the filter, we just return the original filter
if p1 not in date_filter:
return date_filter
new_date_filter = date_filter.copy()
if p1 in date_filter and p2 not in new_date_filter:
new_date_filter.update({p2: new_date_filter[p1]})
return new_date_filter
def complete_date_filter(date_filter):
"""
Update the date filter with the missing date parameters
If BeginCreationDate is present but not BeginModificationDate, add
BeginModificationDate (and conversely)
If EndCreationDate is present but not EndModificationDate, add
EndModificationDate (and conversely)
"""
date_filter = update_date_filter(
date_filter, "BeginCreationDate", "BeginModificationDate"
)
date_filter = update_date_filter(
date_filter, "BeginModificationDate", "BeginCreationDate"
)
date_filter = update_date_filter(
date_filter, "EndCreationDate", "EndModificationDate"
)
date_filter = update_date_filter(
date_filter, "EndModificationDate", "EndCreationDate"
)
return date_filter
class Orders(BaseSection):
"""
Allows to list, validate or refund orders.
Methods::
get_order_list(**order_filter)
get_global_configuration()
prepare_validations(data)
validate_order_list(**validate_order_list_message)
create_refund_voucher(**request)
manage_parcel(parcel_actions_list=parcel_actions_list, scopus_id=scopus_id)
Operations are included in the Orders API section.
(https://dev.cdiscount.com/marketplace/?page_id=128)
"""
@auto_refresh_token
def get_order_list(self, **order_filter):
"""
To search orders.
This operation makes it possible to seek orders according to the
following criteria:
Example::
response = api.orders.get_order_list()
- The order state:
- CancelledByCustomer
- WaitingForSellerAcceptation
- AcceptedBySeller
- PaymentInProgress
- WaitingForShipmentAcceptation
- Shipped
- RefusedBySeller
- AutomaticCancellation (ex: no answer from the seller)
- PaymentRefused
- ShipmentRefusedBySeller
- Waiting for Fianet validation "A valider Fianet" (None)
- Validated Fianet
- RefusedNoShipment
- AvailableOnStore
- NonPickedUpByCustomer
- PickedUp
- Filled
Example::
response = api.orders.get_order_list(
States=['CancelledByCustomer', 'Shipped']
)
- Recovery or not of the products of the order
- Filter on date:
- BeginCreationDate
- EndCreationDate
- BeginModificationDate
- EndModificationDate
Example::
response = api.orders.get_order_list(
BeginCreationDate=datetime.datetime(2077, 1, 1),
States=['CancelledByCustomer', 'Shipped']
)
.. note:: The date parameters must be combined with another parameter
(like ``States``). Otherwise all the orders will be taken
into account.
- Order number list Liste (OrderReferenceList) Warning, this filter
cannot be combined with others. If there is an order list, the other
filters are unaccounted.
Example::
response = api.orders.get_order_list(OrderReferenceList=['X1', 'X2'])
- Filter on website thanks to the corporationCode.
Example::
response = api.orders.get_order_list(CorporationCode='CDSB2C')
- Filter by Order Type:
- MKPFBC Orders (Marketplace fulfillment by Cdiscount)
- EXTFBC Orders (External fulfillment by Cdiscount)
- FBC Orders (Isfulfillment)
- None
Example::
response = api.get_order_list(OrderType=None)
- PartnerOrderRef filter from 1 to N external order (if it's a multiple
search, separate PartnerOrderRefs by semicolon).
PartnerOrderRef is the seller's reference
Example::
response = api.get_order_list(PartnerOrderRef='SELLER_REF')
- Recovery or not of the parcels of the order
Example::
response = api.get_order_list(FetchParcels=True, OrderReferenceList=['X1'])
.. note:: If ``FetchOrderLines`` is not specified in keywords, its
default value will be ``True``.
"""
if "States" in order_filter:
order_filter.update(
States=self.api.factory.ArrayOfOrderStateEnum(order_filter["States"])
)
# For some reasons, when a date parameter is used, its "pair" must be used too
# Example: BeginCreationDate and BeginModificationDate must be used together
# (idem for EndCreationDate and EndModificationDate)
# We address this weird behavior with `complete_date_filter`.
order_filter = complete_date_filter(order_filter)
order_filter = self.update_with_valid_array_type(
order_filter, {"OrderReferenceList": "string"}
)
if "FetchOrderLines" not in order_filter:
order_filter.update(FetchOrderLines=True)
order_filter = self.api.factory.OrderFilter(**order_filter)
response = self.api.client.service.GetOrderList(
headerMessage=self.api.header, orderFilter=order_filter
)
return serialize_object(response, dict)
@auto_refresh_token
def get_global_configuration(self):
"""
Get cdiscount settings. This method allows to get a list of several
settings:
- Carrier list
"""
response = self.api.client.service.GetGlobalConfiguration(
headerMessage=self.api.header
)
return serialize_object(response, dict)
def _prepare_validation(self, data):
"""
Return the validation data for an order.
:param dict data: The information about the order to validate. (cf
`Seller.prepare_validations`)
"""
data = data.copy()
# Check elements in ValidateOrder
for element_name in data.keys():
check_element(element_name, self.api.factory.ValidateOrder)
# check elements ValidateOrderLine
for i, validate_order_line in enumerate(data["OrderLineList"]):
for element_name in validate_order_line.keys():
check_element(element_name, self.api.factory.ValidateOrderLine)
data["OrderLineList"] = {
"ValidateOrderLine": [x for x in data.pop("OrderLineList")]
}
return serialize_object(self.api.factory.ValidateOrder(**data), dict)
def prepare_validations(self, data):
"""
Return the dictionary used to validate the orders in
:py:meth:`Orders.validate_order_list`
This method tries to simplify the creation of the data necessary to
validate the orders by letting the user provide a more intuitive data
structure than the one required for the request.
:param list data: The validation data for the orders. A list of dictionaries with the following structure:
.. code-block:: python
{
'CarrierName': carrier_name,
'OrderNumber': order_number,
'OrderState': order_state,
'TrackingNumber': tracking_number,
'TrackingUrl': tracking_url,
'OrderLineList': [
{
'AcceptationState': acceptation_state,
'ProductCondition': product_condition,
'SellerProductId': seller_product_id,
'TypeOfReturn': type_of_return
},
...
]
}
:returns: The ``validate_order_list_message`` dictionary created with ``data``
"""
return {
"OrderList": {"ValidateOrder": [self._prepare_validation(x) for x in data]}
}
# TODO Use for accept_orders
@auto_refresh_token
def validate_order_list(self, **validate_order_list_message):
"""
Validate a list of orders
:param validate_order_list_message: The information about the orders to validate.
There are two ways to create a ``validate_order_list_message``:
1. you can build the dictionary by yourself:
Example::
response = api.validate_order_list(
OrderList= {'ValidateOrder':
[{'CarrierName': carrier_name,
'OrderNumber': order_number,
'OrderState': order_state,
'TrackingNumber': tracking_number,
'TrackingUrl': tracking_url,
'OrderLineList': {
'ValidateOrderLine': [
{'AcceptationState': 'acceptation_state',
'ProductCondition': product_condition,
'SellerProductId': seller_product_id,
'TypeOfReturn': type_of_return},
...
]},
},
...
]})
2. you can use :py:meth:`Orders.prepare_validations`:
Example::
validate_order_list_message = api.orders.prepare_validations(
[{'CarrierName': carrier_name,
'OrderNumber': order_number,
'OrderState': order_state,
'TrackingNumber': tracking_number,
'TrackingUrl': tracking_url,
'OrderLineList': [
{'AcceptationState': 'acceptation_state',
'ProductCondition': product_condition,
'SellerProductId': seller_product_id,
'TypeOfReturn': type_of_return},
...
]},
...]
)
response = api.orders.validate_order_list_message(**validate_order_list_message)
"""
validate_order_list_message = self.api.factory.ValidateOrderListMessage(
**validate_order_list_message
)
response = self.api.client.service.ValidateOrderList(
headerMessage=self.api.header,
validateOrderListMessage=validate_order_list_message,
)
return serialize_object(response, dict)
@auto_refresh_token
def create_refund_voucher(self, **request):
"""
This method still allows refunding lines of an order whose state is
"ShippedBySeller".
An additional feature allows to make a commercial gesture on an order
MKPCDS before and after shipping and on an order MKPFBC after shipping.
:param list CommercialGestureList:
- Amount *(decimal)*
- Sku *(str)*: The product number
- MotiveId *(int)*:
- 131: 'Compensation on missing stock',
- 132: 'Product / Accessory delivered damaged or missing',
- 133: 'Error of reference, color, size',
- 134: 'Fees unduly charged to the customer',
- 135: 'Late delivery',
- 136: 'Product return fees',
- 137: 'Shipping fees',
- 138: 'Warranty period or rights of with drawal passed',
- 139: 'Others'
:param str OrderNumber:
:param list SellerRefundList:
- Mode *(str)*:
- 'Claim'
- 'Retraction'
- Motive *(str)*:
- 'VendorRejection',
- 'ClientCancellation',
- 'VendorRejectionAndClientCancellation',
- 'ClientClaim',
- 'VendorInitiatedRefund',
- 'ClientRetraction',
- 'NoClientWithDrawal',
- 'ProductStockUnavailable'
- SellerRefundOrderLine:
- EAN *(str)*
- RefundShippingChanges *(bool)*
- SellerProductId *(str)*
Example::
response = api.orders.create_refund_voucher(
CommercialGestureList=[
{
'Amount': 10,
'MotiveId': 135
}
],
OrderNumber='ORDER_NUMBER_1',
SellerRefundList={
'Mode': 'Claim',
'Motive': 'ClientClaim',
'RefundOrderLine': {
'Ean': '4005274238223',
'RefundShippingCharges': True,
'SellerProductId': '42382235'
}
}
)
"""
# Check CommercialGestureList
if "CommercialGestureList" in request:
commercial_gestures = request["CommercialGestureList"]
if isinstance(commercial_gestures, dict):
commercial_gestures = [commercial_gestures]
for commercial_gesture in commercial_gestures:
motive_id = commercial_gesture.get("MotiveId")
# MotiveId is a label
# if not isinstance(motive_id, int):
# motive_id = get_motive_id(motive_id)
# TODO Damien: voir pour obligation d'int
if isinstance(motive_id, int):
commercial_gesture["MotiveId"] = motive_id
else:
commercial_gestures = None
# Request
request = self.api.factory.CreateRefundVoucherRequest(
OrderNumber=request.get("OrderNumber"),
CommercialGestureList=self.api.factory.ArrayOfRefundInformation(
commercial_gestures
),
SellerRefundList=self.api.factory.ArrayOfSellerRefundRequest(
request.get("SellerRefundList")
),
)
response = self.api.client.service.CreateRefundVoucher(
headerMessage=self.api.header, request=request
)
return serialize_object(response, dict)
@auto_refresh_token
def manage_parcel(self, parcel_actions_list=None, scopus_id=None):
"""
Ask for investigation or ask for delivery certification.
:param list parcel_actions_list: The list of dictionaries with the keys:
- ManageParcel: ('AskingForDeliveryCertification' or 'AskingForInvestigation')
- ParcelNumber: The parcel customer number
- Sku: The product number
:param int scopus_id: The scopus id
Usage::
api.manage_parcel(parcel_actions_list=[
{'ManageParcel': manage_parcel, 'ParcelNumber': parcel_number, 'Sku': sku},
...
],
scopus_id=scopus_id
)
"""
# Handle properly the case where no information is provided for
# parcel_actions_list
if parcel_actions_list is not None:
new_parcel_actions_list = []
for parcel_infos in parcel_actions_list:
new_parcel_infos = self.api.factory.ParcelInfos(**parcel_infos)
if not isinstance(new_parcel_infos.ManageParcel, list):
new_parcel_infos.ManageParcel = [new_parcel_infos.ManageParcel]
new_parcel_actions_list.append(new_parcel_infos)
else:
new_parcel_actions_list = None
manage_parcel_request = self.api.factory.ManageParcelRequest(
ParcelActionsList=self.api.factory.ArrayOfParcelInfos(
new_parcel_actions_list
),
ScopusId=scopus_id,
)
response = self.api.client.service.ManageParcel(
headerMessage=self.api.header, manageParcelRequest=manage_parcel_request
)
return serialize_object(response, dict)
PK |DOKŁ# # ! cdiscountapi/sections/products.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.products
------------------------------
Handles the products.
:copyright: © 2019 Alexandria
"""
from tempfile import gettempdir
from zeep.helpers import serialize_object
from cdiscountapi.helpers import generate_package
from .base import BaseSection
from ..helpers import auto_refresh_token
class Products(BaseSection):
"""
Allows to get information about products and submit new products on Cdiscount.
Methods::
get_all_allowed_category_tree()
get_allowed_category_tree()
get_product_list(category_code)
get_model_list(category=category)
get_all_model_list()
get_brand_list()
generate_product_package(package_name, products_list)
submit_product_package(url)
get_product_package_submission_result(package_ids=package_ids)
get_product_package_product_matching_file_data(package_id)
get_product_list_by_identifier(ean_list=ean_list)
Operations are included in the Products API section.
(https://dev.cdiscount.com/marketplace/?page_id=220)
"""
@auto_refresh_token
def get_allowed_category_tree(self):
"""
Categories which are accessible to the seller.
Usage::
response = api.products.get_allowed_category_tree()
:return: tree of the categories leaves of which are authorized for the integration of products and/or offers
"""
response = self.api.client.service.GetAllowedCategoryTree(
headerMessage=self.api.header
)
return serialize_object(response, dict)
@auto_refresh_token
def get_all_allowed_category_tree(self):
"""
All categories.
Usage::
response = api.products.get_all_allowed_category_tree()
:return: tree of the categories leaves of which are authorized for the integration of products and/or offers
"""
from ..cdiscountapi import Connection
api_all = Connection("AllData", "pa$$word", header_message=self.header)
response = api_all.client.service.GetAllAllowedCategoryTree(
headerMessage=api_all.header
)
return serialize_object(response, dict)
@auto_refresh_token
def get_product_list(self, category_code):
"""
Search products in the reference frame.
:param str category_code: code to filter products by category
Usage::
response = api.products.get_product_list("13380D0501")
:return: products corresponding to research
"""
filters = self.api.factory.ProductFilter(category_code)
response = self.api.client.service.GetProductList(
headerMessage=self.api.header, productFilter=filters
)
return serialize_object(response, dict)
@auto_refresh_token
def get_model_list(self, category=None):
"""
Model categories allocated to the seller.
:param str category: category code to filter results
Usages::
response = api.products.get_model_list()
response = api.products.get_model_list("13380D0501")
:return: models and mandatory model properties
"""
categories = category if isinstance(category, (list, tuple)) else [category]
model_filter = self.api.factory.ModelFilter(
self.array_of('string', categories)
)
response = self.api.client.service.GetModelList(
headerMessage=self.api.header, modelFilter=model_filter
)
return serialize_object(response, dict)
# TODO find a way to call it.
@auto_refresh_token
def get_all_model_list(self):
"""
.. warning::
Doesn't work at the moment.
Model categories opened on marketplace.
Usage::
response = api.products.get_all_model_list()
:return: models and mandatory model properties
"""
# api_all = Connection('AllData', 'pa$$word')
# response = api_all.client.service.GetAllModelList(
# headerMessage=api_all.header,
# )
# return serialize_object(response, dict)
pass
@auto_refresh_token
def get_brand_list(self):
"""
Complete list of the brands
Usage::
response = api.products.get_brand_list()
:return: all brands
"""
response = self.api.client.service.GetBrandList(headerMessage=self.api.header)
return serialize_object(response, dict)
@staticmethod
def generate_product_package(package_name, products_list, overwrite=True):
"""
Generate a zip product package as cdiscount wanted.
:param str package_name: [mandatory] the full path to the offer package (without .zip)
:param bool overwrite: [optional] Determine if an existing package is
overwritten when a new one with the same name is created (default: True)
:param list products_list:
- Mandatory attributes:
- BrandName *(str)*
- Description *(str)*
- LongLabel *(str)*
- Model *(str)*
- Navigation *(str)*
- ProductKind *(str)*
- 'Variant'
- 'Standart'
- SellerProductId *(str)*
- ShortLabel *(str)*
- Optional attributes:
- Width *(int)*
- Weight *(int)*
- Length *(int)*
- Height *(int)*
- Size *(str)*
- SellerProductFamily *(str)*
- SellerProductColorName *(str)*
- ManufacturerPartNumber *(str)*
- ISBN *(str)*
- EncodedMarketingDescription *(str)*
Example::
response = api.products.generate_product_package(products_list)
"""
return generate_package(
"product",
package_name,
{"Products": products_list},
overwrite=overwrite
)
@auto_refresh_token
def submit_product_package(self, url):
"""
To ask for the creation of products.
It could included between 10K and 20K products by package.
There is 2 ways to use it.
1. You can generate a zip package with:
api.products.generate_product_package(products_dict)
2. You can generate the package yourself before uploading.
Then you'll have to get an url to download zip package
Finally, you can use submit_product_package(url)
Examples::
api.products.submit_product_package(url)
:return: the id of package or -1
"""
product_package = self.api.factory.ProductPackageRequest(url)
# Send request.
response = self.api.client.service.SubmitProductPackage(
headerMessage=self.api.header, productPackageRequest=product_package
)
return serialize_object(response, dict)
# TODO find why it doesn't work.
@auto_refresh_token
def get_product_package_submission_result(self, package_ids=None):
"""
Progress status of a product import.
:param long package_ids: PackageID
Usage::
response = api.products.get_product_package_submission_result(2154894)
:return: partial or complete report of package integration
"""
filters = self.api.factory.PackageFilter(package_ids)
response = self.api.client.service.GetProductPackageSubmissionResult(
headerMessage=self.api.header, productPackageFilter=filters
)
return serialize_object(response, dict)
@auto_refresh_token
def get_product_package_product_matching_file_data(self, package_id):
"""
Information of the created products.
:param long package_id: package id to filter results
Usage::
response = api.products.get_product_package_product_matching_file_data(21454894)
:return: information of the created products
"""
if package_id:
response = self.api.client.service.GetProductPackageProductMatchingFileData(
headerMessage=self.api.header,
productPackageFilter={"PackageID": package_id},
)
return serialize_object(response, dict)
@auto_refresh_token
def get_product_list_by_identifier(self, ean_list=[]):
"""
Obtain details for a list of products
:param list ean_list: list of EAN to filter
Usage::
response = api.products.get_product_list_by_identifier('2009863600561')
:return: complete list of products
"""
request = {"IdentifierType": "EAN", "ValueList": ean_list}
response = self.api.client.service.GetProductListByIdentifier(
headerMessage=self.api.header, identifierRequest=request
)
return serialize_object(response, dict)
PK |DO
cdiscountapi/sections/relays.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.relays
----------------------------
Handles the relays.
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
from .base import BaseSection
from ..helpers import auto_refresh_token
class Relays(BaseSection):
"""
Allows to get information about the different available relays and submit
new ones.
Methods::
get_parcel_shop_list()
submit_relays_file(relays_file_uri)
get_relays_file_submission_result(relays_file_ids)
Operations are included in the Relays API section.
(https://dev.cdiscount.com/marketplace/?page_id=108)
"""
@auto_refresh_token
def get_parcel_shop_list(self):
"""
To get a list of relays known.
Usage::
response = api.relays.get_parcel_shop_list()
"""
response = self.api.client.service.GetParcelShopList(
headerMessage=self.api.header
)
return serialize_object(response, dict)
@auto_refresh_token
def submit_relays_file(self, relays_file_uri):
"""
Send information about relays in a file
:param str relays_file_uri: A link pointing to a XLSX file with information about relays
Usage::
response = api.relays.submit_relays_file(
'http://spreadsheetpage.com/downloads/xl/worksheet%20functions.xlsx'
)
where relays_file_uri is the URI to a XLSX file
:returns: The response with the RelaysFileId for the file.
"""
relays_file_request = self.api.factory.RelaysFileIntegrationRequest(
relays_file_uri
)
response = self.api.client.service.SubmitRelaysFile(
headerMessage=self.api.header, relaysFileRequest=relays_file_request
)
return serialize_object(response, dict)
@auto_refresh_token
def get_relays_file_submission_result(self, relays_file_ids):
"""
Get the state of progress of the relays file submission.
:param list relays_file_ids: IDs referencing the relays file submitted.
Usage::
response = api.get_relays_file_submission_result([15645,52486])
where ``relays_file_id`` is the value of RelaysFileId returned by
`SubmitRelaysFile `_.
:returns: The response with the information about the integration of the specified relays.
"""
relays_file_filter = self.api.factory.RelaysFileFilter(relays_file_ids)
response = self.api.client.service.GetRelaysFileSubmissionResult(
headerMessage=self.api.header, relaysFileFilter=relays_file_filter
)
return serialize_object(response, dict)
PK |DO>u cdiscountapi/sections/sandbox.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.sandbox
-----------------------------
Simulates the different processes involved in an order.
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
from .base import BaseSection
class Sandbox(BaseSection):
"""
Allows create fake orders, simulate payments, simulate cancellations, get,
validate fake orders and create refund vouchers after the shipment.
Operations are included in the Sandbox API section.
(https://dev.cdiscount.com/marketplace/?page_id=2224)
"""
def create_fake_order(self, **request):
"""
Create a fake order
Usage::
response = api.sandbox.create_fake_order(request={
'NumberOfProducts': 1, ProductType: 'Variant',
'PaymentMode': 'CB1X', 'Quantity': 1,
'ShippingCode': 'Normal', 'Tenant': 'CDiscount'})
"""
response = self.api.factory.
PK |DO^" cdiscountapi/sections/seller.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.seller
----------------------------
Handles the seller information.
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
from .base import BaseSection
from ..helpers import auto_refresh_token
class Seller(BaseSection):
"""
Seller section lets sellers retrieve information about their seller account
and their performance indicator.
Methods::
get_seller_info()
get_seller_indicators()
Operations are included in the Seller API section.
(https://dev.cdiscount.com/marketplace/?page_id=36)
"""
@auto_refresh_token
def get_seller_info(self):
"""
To get seller info as:
- Delivery Modes
- Offer Pool
- Email
- Login
- Phone Number
- Adress
- Name
- Relays
- Shop Name/Url
- SIRET
- Seller Availability
- Account State
:return: Information of the authenticated seller.
"""
response = self.api.client.service.GetSellerInformation(
headerMessage=self.api.header
)
return serialize_object(response, dict)
@auto_refresh_token
def get_seller_indicators(self):
"""
To get all rates about seller as:
- Order Acceptation
- Product Shipping
- Order with claim
- Order with refund
- Preparation Deadline respected
:return: a dict with the data of the user
"""
response = self.api.client.service.GetSellerIndicators(
headerMessage=self.api.header
)
return serialize_object(response, dict)
PK |DO|+ + cdiscountapi/sections/webmail.py# -*- coding: utf-8 -*-
"""
cdiscountapi.sections.webmail
-----------------------------
Handles the email addresses of the customers.
:copyright: © 2019 Alexandria
"""
from zeep.helpers import serialize_object
from .base import BaseSection
from ..helpers import auto_refresh_token
class WebMail(BaseSection):
"""
The WebMail API allows the seller to retrieve encrypted email address to
contact a customer
Methods::
generate_discussion_mail_guid(order_id)
get_discussion_mail_list(discussion_ids)
Operations are included in the WebMail API section.
(https://dev.cdiscount.com/marketplace/?page_id=167)
"""
@auto_refresh_token
def generate_discussion_mail_guid(self, order_id):
"""
Generate an encrypted mail address from an order.
This operation allows getting an encrypted mail address to contact a
customer.
:param str order_id: Order id for which an e-mail is to be sent.
Usage::
response = api.generate_discussion_mail_guid(order_id)
"""
response = self.api.client.service.GenerateDiscussionMailGuid(
headerMessage=self.api.header, request={"ScopusId": order_id}
)
return serialize_object(response, dict)
@auto_refresh_token
def get_discussion_mail_list(self, discussion_ids):
"""
Get encrypted mail addresses from discussions.
This operation allows getting an encrypted mail address to contact a
customer.
Usages::
id = 113163877
response = api.webmail.get_discussion_mail_list(id)
ids = [113163877, 224274988]
response = api.webmail.get_discussion_mail_list(ids)
"""
request = self.api.factory.GetDiscussionMailListRequest(
DiscussionIds=self.array_of("long", discussion_ids)
)
response = self.api.client.service.GetDiscussionMailList(
headerMessage=self.api.header, request=request
)
return serialize_object(response, dict)
PK |DO ! cdiscountapi/templates/Offers.xml
{% for offer in offers %}
{% for shipping_info in offer['shipping_information_list'] %}
{% endfor %}
{% if 'discount_list' in offer %}
{% for discount_component in offer['discount_list'] %}
{% endfor %}
{% endif %}
{% endfor %}
{% if offer_publication_list %}
{% for publication_pool in offer_publication_list %}
{% endfor %}
{% endif %}
PK |DO8a # cdiscountapi/templates/Products.xml
{% for product in products %}
{% for product_ean in product['EanList'] %}
{% endfor %}
{% for model_property in product['ModelProperties'] %}
{% for key, value in model_property.items() %}
{{ value }}
{% endfor %}
{% endfor %}
{% for product_image in product['Pictures'] %}
{% endfor %}
{% endfor %}
PK NN'
=J J % cdiscountapi-0.1.10.dist-info/LICENSEThe MIT License (MIT)
Copyright (c) 2019 ZIBOURA Mathilde, RABOIS Damien
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK !HP O # cdiscountapi-0.1.10.dist-info/WHEELHM
K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&Ur PK !H &