def add_stock(self, payload=None, provided_oauth=None): ## https://api.cardmarket.com/ws/documentation/API_2.0:Stock_Management url = f"{self.base_url}/stock" # idProduct # count # idLanguage # comments # price # condition # isFoil # isSigned # isAltered # isPlayset # isFirstEd # clean data because the API treats "False" as true, must be "false". for entry in payload: for key, value in entry.items(): if isinstance(value, bool): entry[key] = str.lower(str(value)) mkm_oauth = self.__setup_auth_session(url, provided_oauth) self.logger.debug(">> Adding stock") chunked_list = list(self.__chunks(payload, 100)) r = None for chunk in chunked_list: # chunk[0]["comments"] = "DO NOT BUY" # HACK: temp comment for testing try: xml_payload = PyMkmHelper.dicttoxml(chunk) r = mkm_oauth.post( url, data=xml_payload, timeout=self.config["cardmarket_request_timeout"], ) mkm_oauth.close() inserted = r.json() if "error" in inserted: raise CardmarketError(inserted["error"]) else: for item in inserted["inserted"]: if not item["success"]: raise CardmarketError( f"{item['error']}: {item['tried']}" # , url=r.request.url ) else: self.logger.debug( f">> Added {item['idArticle']['product']['enName']}." ) except CardmarketError as err: self.logger.error(f"{err.mkm_msg()} {err.url}") # print(err.mkm_msg()) # TODO: Only considers the last response. if self.__handle_response(r): return r.json()
def delete_stock(self, payload=None, provided_oauth=None): ## https://api.cardmarket.com/ws/documentation/API_2.0:Stock_Management url = f"{self.base_url}/stock" mkm_oauth = self.__setup_auth_session(url, provided_oauth) self.logger.debug(">> Deleting stock") chunked_list = list(self.__chunks(payload, 100)) r = None for chunk in chunked_list: xml_payload = PyMkmHelper.dicttoxml(chunk) r = mkm_oauth.delete(url, data=xml_payload) # TODO: Only considers the last response. if self.__handle_response(r): return r.json()
def set_stock(self, payload=None, provided_oauth=None): ## https://api.cardmarket.com/ws/documentation/API_2.0:Stock_Management url = f"{self.base_url}/stock" allowed_items = [ "idArticle", "idLanguage", "comments", "count", "price", "condition", "isFoil", "isSigned", "isPlayset", ] # clean data because the API treats "False" as true, must be "false". clean_payload = [] for entry in payload: clean_entry = { k: v for k, v in entry.items() if k in allowed_items } for key, value in clean_entry.items(): if isinstance(value, bool): clean_entry[key] = str.lower(str(value)) clean_payload.append(clean_entry) mkm_oauth = self.__setup_auth_session(url, provided_oauth) self.logger.debug(">> Updating stock") chunked_list = list(self.__chunks(clean_payload, 100)) index = 0 r = None for chunk in chunked_list: index += 1 self.logger.debug(f"chunk {index}/{len(chunked_list)}") xml_payload = PyMkmHelper.dicttoxml(chunk) r = mkm_oauth.put( url, data=xml_payload, timeout=self.config["cardmarket_request_timeout"]) mkm_oauth.close() try: json_response = r.json() if len(json_response["updatedArticles"]) > 0: for success in json_response["updatedArticles"]: self.logger.debug( f"Updated price for aid: {success['idArticle']}, pid: {success['idProduct']}, {success['product']['enName']})." ) if len(json_response["notUpdatedArticles"]) > 0: for failure in json_response["notUpdatedArticles"]: self.logger.warning( f"Failed update price for aid: {failure['tried']['idArticle']})." ) print(failure) # TODO: remove this when stable. update config to log level WARNING as default except Exception as err: self.logger.error(err) # TODO: Only considers the last response. if self.__handle_response(r): return r.json()
class TestPyMkmHelperFunctions(unittest.TestCase): def setUp(self): self.helper = PyMkmHelper() def test_dict_to_cardmarket_xml_request(self): add_to_stock_dict = [{ "idProduct": 100569, "idLanguage": 1, "comments": "Inserted through the API", "count": 1, "price": 4, "condition": "EX", "isFoil": True, "isSigned": False, "isPlayset": False, }] add_to_stock_xml = """<request> <article> <idProduct>100569</idProduct> <idLanguage>1</idLanguage> <comments>Inserted through the API</comments> <count>1</count> <price>4</price> <condition>EX</condition> <isFoil>true</isFoil> <isSigned>false</isSigned> <isPlayset>false</isPlayset> </article> </request>""" add_to_stock_xml = re.sub(r"[\t\s]+", "", add_to_stock_xml) xml_data = self.helper.dicttoxml(add_to_stock_dict) xml_data = re.sub(r"[\t\s]+", "", xml_data) self.assertEqual(xml_data, add_to_stock_xml) def test_string_to_float_or_int(self): self.assertEqual(self.helper.string_to_float_or_int("1.0"), 1) self.assertEqual(self.helper.string_to_float_or_int("1"), 1) self.assertEqual(self.helper.string_to_float_or_int("AAAAA"), "AAAAA") self.assertEqual(self.helper.string_to_float_or_int("11.3"), 11.3) self.assertEqual(self.helper.string_to_float_or_int(str(4 / 3)), 4 / 3) def test_calculate_average(self): table = [ ["Yxskaft", "SE", "NM", 1, 1.21], ["Frazze11", "SE", "NM", 3, 1.3], ["andli826", "SE", "NM", 2, 1.82], ] self.assertEqual(self.helper.calculate_average(table, 3, 4), 1.46) def test_calculate_median(self): table = [ ["Yxskaft", "SE", "NM", 1, 1.21], ["Frazze11", "SE", "NM", 3, 1.3], ["andli826", "SE", "NM", 2, 1.82], ] self.assertEqual(self.helper.calculate_median(table, 3, 4), 1.3) self.assertEqual(self.helper.calculate_average(table, 3, 4), 1.46) def test_calculate_lowest(self): table = [ ["Yxskaft", "SE", "NM", 1, 1.21], ["Frazze11", "SE", "NM", 3, 1.3], ["andli826", "SE", "NM", 2, 1.82], ] self.assertEqual(self.helper.get_lowest_price_from_table(table, 4), 1.21) def test_round_up_to_limit(self): self.assertEqual( self.helper.round_up_to_multiple_of_lower_limit(0.25, 0.99), 1) self.assertEqual( self.helper.round_up_to_multiple_of_lower_limit(0.25, 0), 0) self.assertEqual( self.helper.round_up_to_multiple_of_lower_limit(0.25, 0.1), 0.25) self.assertEqual( self.helper.round_up_to_multiple_of_lower_limit(0.1, 0.99), 1) self.assertEqual( self.helper.round_up_to_multiple_of_lower_limit(0.01, 0.011), 0.02) self.assertEqual( self.helper.round_up_to_multiple_of_lower_limit(0.01, 1), 1) self.assertEqual( self.helper.round_up_to_multiple_of_lower_limit(1, 0.1), 1) def test_round_down_to_limit(self): self.assertEqual( self.helper.round_down_to_multiple_of_lower_limit(0.25, 0.99), 0.75) self.assertEqual( self.helper.round_down_to_multiple_of_lower_limit(0.25, 1.01), 1) self.assertEqual( self.helper.round_down_to_multiple_of_lower_limit(0.25, 0.1), 0.25) self.assertEqual( self.helper.round_down_to_multiple_of_lower_limit(0.1, 0.99), 0.9) self.assertEqual( self.helper.round_down_to_multiple_of_lower_limit(0.01, 0.011), 0.01) self.assertEqual( self.helper.round_down_to_multiple_of_lower_limit(0.01, 1), 1) self.assertEqual( self.helper.round_down_to_multiple_of_lower_limit(1, 0.1), 1) self.assertEqual( self.helper.round_down_to_multiple_of_lower_limit(0.10, 8.44), 8.4) @patch("sys.stdout", new_callable=io.StringIO) @patch("builtins.input", side_effect=["y", "n", "p", "n", ""]) def test_prompt_bool(self, mock_input, mock_stdout): self.assertTrue(self.helper.prompt_bool("test_y")) self.assertFalse(self.helper.prompt_bool("test_n")) self.helper.prompt_bool("test_error") self.assertRegex(mock_stdout.getvalue(), r"\nPlease answer with y\/n\n") self.assertFalse(self.helper.prompt_bool("test_empty")) @patch("builtins.input", side_effect=["y"]) def test_prompt_string(self, mock_input): self.assertEqual(self.helper.prompt_string("test"), "y")