From a129dddd8c4d904da078799e95ea3c5b922edae2 Mon Sep 17 00:00:00 2001 From: Igor Raznatovic Date: Sat, 24 Feb 2024 17:07:48 +0100 Subject: [PATCH] Speed enhancement. Changed the code to send products in batches. --- abacus2woo.py | 118 ++++++++++++++++++++++++------------------------ all_from_woo.py | 9 ++-- imd.db | Bin 1736704 -> 1736704 bytes 3 files changed, 63 insertions(+), 64 deletions(-) diff --git a/abacus2woo.py b/abacus2woo.py index 91804b6..ebea9bf 100644 --- a/abacus2woo.py +++ b/abacus2woo.py @@ -3,9 +3,10 @@ import sqlite3 import sys import time import pyodbc +import json from woocommerce import API -# import all_from_woo +import all_from_woo conn = sqlite3.connect('imd.db') # intermediary db c = conn.cursor() @@ -17,15 +18,17 @@ c.execute('''CREATE TABLE IF NOT EXISTS products (id INTEGER NOT NULL PRIMARY KE # Connect to WooCommerce API wcapi = API( - url="https://www.rasterdoo.com/", + url="https://www.rasterdoo.com", consumer_key="ck_b3169b1723f6f39a965ed04ecfa77860fb89bbf5", consumer_secret="cs_a83f0217ed8d6191ab7de9df06a0c2b652f6bd57", wp_api=True, timeout=360, - version="wc/v2" + version="wc/v3" ) -print("Resting for for a bit...") -# time.sleep(50) +# Let's check if connection with WooCommerce is OK - list a sales report +# print(wcapi.get("reports/sales?date_min=2024-01-01&date_max=2024-02-04").json()) +# print("Resting for for a bit...") +# time.sleep(5) print("Connecting to MS SQL server") ''' @@ -81,34 +84,34 @@ print("Abacus row in works:"), row # snc = 0 for row in cursor: while row is not None: - # print("Abacus row entered for loop and it is not None") + # print("Abacus row entered for loop & it is not None") # input("Press Enter to continue...") # aid = row[0] aprice = float(row[1]) aname = row[2] - asifra = row[3] + asifra = str(row[3]) aqty = int(row[4]) agroup = row[5] - print("MS Sql group value:", agroup) + # print("MS Sql group value:", agroup) # This dictionary matches abacus product category id to equivalent id in woocommerce groupdict = { 1: 21472, 2: 21473, 4: 21474, 5: 21475, 10: 21476, 11: 21477, 13: 21477, 15: 21477, 19: 21478, 24: 21479, 30: 21480, 32: 21481, 33: 21482, 37: 21483, 38: 21484, 44: 21485, 45: 21486, 47: 21477, 48: 21477 } - print("Coverted to Woocommerce group it becomes wgroup:", groupdict[agroup]) + # print("Coverted to Woocommerce group it becomes wgroup:", groupdict[agroup]) if agroup in groupdict: wgroup = groupdict[agroup] - print("Group in abacusu is from dictionary:", agroup, " - while woocommerce now holds:", wgroup) # + # print("Group in abacusu is from dictionary:", agroup, " - while woocommerce now holds:", wgroup) # else: wgroup = 3557 - print("Group ", agroup, "does not exist in dictionary - we put it in woocommerce group -other-:", wgroup) # + # print("Group ", agroup, "does not exist in dictionary - we put it in woocommerce group -other-:", wgroup) # # Test id Woo product exists in SQLite table q = (asifra,) c.execute('SELECT * FROM products WHERE sifra=?', q) - print("Abacus data to be saved in SQLite:", aid, aprice, aname, asifra, aqty, wgroup) + # print("Abacus data to be saved in SQLite:", aid, aprice, aname, asifra, aqty, wgroup) # input("Press Enter to continue...") # - # We test SqlLite rows so that we update the table only with products that changed + # We test SqlLite rows so that we update the table only with product that changed trow = c.fetchone() if trow is not None: tnaziv = trow[3] @@ -118,30 +121,31 @@ for row in cursor: # print("There is an SQLite trow we need to check for an update: "), tnaziv, tprice, tqty, tgroup if aprice != tprice: snc = 1 - print("bad price") - if aqty != tqty: + print(aname, "has a bad price") + elif aqty != tqty: snc = 1 - print("bad qty") - if aname != tnaziv: + print(aname, "has a bad qty") + elif aname != tnaziv: snc = 1 - print("bad naziv") - if wgroup != tgroup: + print(aname, "has bad naziv: ", tnaziv) + elif wgroup != tgroup: snc = 1 - print(wgroup, "bad tgroup: ", tgroup) + print(wgroup, "has a bad tgroup: ", tgroup) + else: + snc = 0 if snc == 1: - # print("=======") + print(asifra, aname, snc) c.execute('''UPDATE products SET aid = ?, price = ?, qty =?, naziv = ?, snc = ?, grupa = ? WHERE sifra = ?''', (aid, aprice, aqty, aname, snc, wgroup, asifra)) conn.commit() c.execute('SELECT * FROM products WHERE sifra=?', q) trow = c.fetchone() - print(trow) - print("=======") + # print(trow) + # print("=======") # input("Press Enter to continue...") else: - print("If sqlite trow does not exist (is ", trow, "), then first create one in Woo", aid, asifra, aname, - aprice, aqty, wgroup) + print("If sqlite trow does not exist (is ", trow, "), then first create one in Woo", aid, asifra, aname, aprice, aqty, wgroup) # input("Press Enter to continue...") # aprice = str(aprice) data = { @@ -160,8 +164,6 @@ for row in cursor: ], } w = wcapi.post("products", data).json() - - # w = wcapi.get("products/categories").json() class ListStream: @@ -189,7 +191,7 @@ for row in cursor: (rogueone, aid, asifra, aname, aprice, aqty, 1, wgroup)) # if no product it will create print("Inserting rogueone in SQLlite data:", rogueone, aid, asifra, aname, aprice, aqty, 1, wgroup) conn.commit() - # rgpath = "\"products/" + str(rogueone) + "\"" + # rgpath = "\"product_batch/" + str(rogueone) + "\"" # print rgpath # rg = wcapi.get(rogueone).json() # print("The rogueone:", rg) @@ -211,49 +213,45 @@ for row in cursor: # print("Resting for for a bit...") # time.sleep(5) conn.close() +print("Wait before sending") +# time.sleep(10) # Update Woo with our SQLite table conn = sqlite3.connect('imd.db') # intermediary db c = conn.cursor() c.execute('''SELECT * FROM products WHERE snc = 1 ORDER BY naziv''') -trow = c.fetchone() - -print("Selected for update:", trow) -# cleantrow is not None: grupa = 0 + +# Make a product_list +product_list = list() +trow = c.fetchone() while trow is not None: - id = trow[0] + # count += 1 + # print("Selected for update:", trow) + tid = trow[0] tnaziv = trow[3] tprice = str(trow[4]) tqty = trow[5] tgroup = trow[7] - # print type(id), type(tnaziv), type(tprice), type(tqty), "Grupa:", type(tgroup), tgroup - # input("Press enter ...") - data = { - "id": id, - "manage_stock": "true", - "stock_quantity": tqty, - "name": tnaziv, - "status": "publish", - "regular_price": tprice, - "categories": [ - { - "id": tgroup - } - ], - } - # print data - uplink = "products/" + str(id) - # print uplink - up = wcapi.put((uplink), data).json() - print(up) - # input("Press enter ...") - if trow is None: - break - else: - trow = c.fetchone() - print("___________________________________________") - print("Selected next for update:", trow) + # product = [tid, tqty, tnaziv, tprice, tgroup] + product = {'id': tid, 'manage_stock': 'true', 'stock_quantity': tqty, 'name': tnaziv, 'status': 'publish', 'regular_price': tprice, 'categories': [{'id': tgroup}]} + # print(product) + product_list.append(product) + trow = c.fetchone() +print(product_list) +# Make batches of products and send them to WooCommerce +l = len(product_list) +print("Lenght: ", l) +batch_size = 100 + +for i in range(0, l, batch_size): + one_product_batch = product_list[i:i + batch_size] + data = {'update': one_product_batch} + # print(one_product_batch) + print("_________________________BATCH", i, "_________________________") + response = wcapi.post("products/batch", data).json() + print(response) + c.execute('UPDATE products SET snc = 0') conn.commit() diff --git a/all_from_woo.py b/all_from_woo.py index e92980e..deebcf5 100644 --- a/all_from_woo.py +++ b/all_from_woo.py @@ -19,7 +19,8 @@ def initSQLite(): consumer_key="ck_b3169b1723f6f39a965ed04ecfa77860fb89bbf5", consumer_secret="cs_a83f0217ed8d6191ab7de9df06a0c2b652f6bd57", wp_api=True, - version="wc/v2" + timeout=360, + version="wc/v3" ) r = wcapi.get("products") # get woo web page where products are h = int(r.headers['X-WP-TotalPages']) # in the header we see number of tot pages @@ -31,7 +32,7 @@ def initSQLite(): r = wcapi.get(goto_page) page_txt = r.json() # contents of one page ar stored in variable # We fill our SQLite table with Woo Products - for product in page_txt: # first we go through products in this page + for product in page_txt: # first we go through product in this page wsku = product.get('sku') wid = product.get('id') wname = product.get('name') @@ -41,9 +42,9 @@ def initSQLite(): wqty = 0 # FIX THIS! - It does not update - just inserts if new ... c.execute('INSERT INTO products VALUES (?,?,?,?,?,?,?,?)', - (wid, "", wsku, wname, wprice, wqty, 0)) # if no product it will create + (wid, "", wsku, wname, wprice, wqty, 0, "")) # if no product it will create c.execute('UPDATE products SET aid = ?, sifra = ?, naziv = ?, price = ?, qty = ?, snc = ? WHERE id = ?', - ("", wsku, wname, wprice, wqty, 0, wid, "")) + ("", wsku, wname, wprice, wqty, 0, wid)) print("Insert woocommerce data to SQLlite:", wid, "", wsku, wname, wprice, wqty, 0) conn.commit() diff --git a/imd.db b/imd.db index 938d12192c97860ba8c9df2fbb8aa1d53eff5269..5992b4cc5cad5d72397ce569f640b8904fa86e7c 100644 GIT binary patch delta 14856 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 delta 10674 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