def test_coerce_unicode(self): coerce_none = SanitationUtils.coerce_unicode(None) self.assertEqual(coerce_none, u'') self.assertIsInstance(coerce_none, unicode) coerce_bytestr = SanitationUtils.coerce_unicode('asdf\xe7') self.assertEqual(u'asdf\ufffd', coerce_bytestr) self.assertIsInstance(coerce_bytestr, unicode)
def get_products(self, params=None): if params is None: params = self.default_fields request_params = OrderedDict() incl_fields = params.get('core_fields', []) extra_fields = [] if params.get('meta_fields'): request_params['filter[meta]'] = 'true' extra_fields.append('product_meta') if params.get('core_fields'): request_params['fields'] = ','.join(incl_fields + extra_fields) if params.get('filter_params'): for key, val in params['filter_params'].items(): request_params['filter[%s]' % key] = val for key in ['per_page', 'search', 'slug', 'sku']: if params.get(key): request_params[key] = SanitationUtils.coerce_ascii(params[key]) endpoint = 'products' if params.get('id'): _id = params['id'] assert isinstance(_id, int), "id must be an int" endpoint += '/%d' % _id if request_params: endpoint += '?' + '&'.join( [key + '=' + val for key, val in request_params.items()]) # print self._API__get_url(endpoint) # print endpoint products = [] if params.get('id'): response = self.get(endpoint) product = response.json().get('product') products.append(product) return products for page in self.WcApiPageIterator(self, endpoint): if page.get('products'): for page_product in page.get('products'): product = {} for field in params.get('core_fields', []): if field in page_product: product[field] = page_product[field] if page_product.get('product_meta'): page_product_meta = page_product['product_meta'] for meta_field in params.get('meta_fields', []): if page_product_meta.get(meta_field): product[ 'meta.' + meta_field] = page_product_meta[meta_field] products.append(product) elif params.get('id'): print page products.append(page) break return products
def next(self): if int(self.last_response.status_code) not in [200]: raise UserWarning("request failed with %s: %s -> %s" % (self.last_response.status_code, repr(self.last_response.request.url), repr(self.last_response.content))) last_response_json = self.last_response.json() last_response_headers = self.last_response.headers # print "headers", last_response_headers links_str = last_response_headers.get('link', '') for link in SanitationUtils.findall_wc_links(links_str): if link.get('rel') == 'next' and link.get('url'): self.last_response = self.api.get(link['url']) return last_response_json raise StopIteration()
def test_findall_wc_links(self): results = SanitationUtils.findall_wc_links( '<http://www.annachandler.com/wc-api/v3/products?filter[meta]=true' + '&oauth_consumer_key=ck_ed0a5ae4658a895dbdf6e4009d81a850e1ec3347' + '&oauth_timestamp=1464927585&oauth_nonce=f8bbaf5c3795d1e7dd76112f5290465301e2a7d6' + '&oauth_signature_method=HMAC-SHA256' + '&oauth_signature=WsQCgHfubKIHHRkKS3879NwzQEImMPcHEtxh8aiBg7A=&page=2>; rel="next", ' '<http://www.annachandler.com/wc-api/v3/products?filter[meta]=true' + '&oauth_consumer_key=ck_ed0a5ae4658a895dbdf6e4009d81a850e1ec3347' + '&oauth_timestamp=1464927585&oauth_nonce=f8bbaf5c3795d1e7dd76112f5290465301e2a7d6' + '&oauth_signature_method=HMAC-SHA256&' + 'oauth_signature=WsQCgHfubKIHHRkKS3879NwzQEImMPcHEtxh8aiBg7A=&page=31>; rel="last"' ) print results
def next(self): assert self.last_response.status_code not in [404] last_response_json = self.last_response.json() print "last_response:", self.last_response, self.last_response.text if int(self.last_response.status_code) not in [200]: raise UserWarning( "request failed with %s: %s" % (self.last_response.status_code, self.last_response.text)) last_response_headers = self.last_response.headers links_str = last_response_headers.get('link', '') for link in SanitationUtils.findall_wc_links(links_str): if link.get('rel') == 'next' and link.get('url'): self.last_response = self.api.get( self.api.__get_endpoint(link['url'])) return last_response_json raise StopIteration()
def get_products(self, params=None): if params is None: params = {} request_params = OrderedDict() for key in ['per_page', 'search', 'slug', 'sku']: if params.get(key): request_params[key] = SanitationUtils.coerce_ascii(params[key]) endpoint = 'products' if params.get('id'): _id = params['id'] assert isinstance(_id, int), "id must be an int" endpoint += '/%d' % _id if request_params: endpoint += '?' + '&'.join( [key + '=' + val for key, val in request_params.items()]) print "endpoint is", endpoint products = [] if params.get('id'): response = self.api.get(endpoint) product = response.json().get('product') products.append(product) return products for page in self.WpApiPageIterator(self.api, endpoint): # print "page:", page if page.get('products'): for page_product in page.get('products'): # print "page_product: ", page_product product = {} for field in params.get('core_fields', []): if field in page_product: product[field] = page_product[field] if page_product.get('product_meta'): page_product_meta = page_product['product_meta'] for meta_field in params.get('meta_fields', []): if page_product_meta.get(meta_field): product[ 'meta.' + meta_field] = page_product_meta[meta_field] products.append(product) return products
def test_maybe_wrap_full_match(self): self.assertEqual(SanitationUtils.maybe_wrap_full_match('asdf'), '^asdf$') self.assertEqual(SanitationUtils.maybe_wrap_full_match('^asf$'), '^asf$')
def main(): dir_module = os.path.dirname(sys.argv[0]) if dir_module: os.chdir(dir_module) #default arguments xero_config_file = 'xero_api.yaml' wc_config_file = 'wc_api_test.yaml' xero_report_file = 'xero_products.csv' wc_report_file = 'wc_products.csv' new_wc_report_file = 'new_wc_products.csv' parser = argparse.ArgumentParser( description='Merge products between Xero and WC') group = parser.add_mutually_exclusive_group() group.add_argument("-v", "--verbosity", action="count", default=2, help="increase output verbosity") group.add_argument("-q", "--quiet", action="store_true") group = parser.add_mutually_exclusive_group() group.add_argument('--download-xero', help='download the xero data', action="store_true", default=None) group.add_argument('--skip-download-xero', help='use the local xero file instead\ of downloading the xero data', action="store_false", dest='download_xero') group = parser.add_mutually_exclusive_group() group.add_argument('--download-wc', help='download the wc data', action="store_true", default=None) group.add_argument('--skip-download-wc', help='use the local wc file instead\ of downloading the wc data', action="store_false", dest='download_wc') parser.add_argument( '--wc-import-file', help='location of wc import file, used if can\'t download products') group = parser.add_mutually_exclusive_group() group.add_argument('--update-wc', help='update the wc database', action="store_true", default=None) group.add_argument('--report-wc-and-quit', help='report wc data and quit', action="store_true", default=None) group.add_argument('--report-xero-and-quit', help='report wc data and quit', action="store_true", default=None) group.add_argument('--skip-update-wc', help='don\'t update the wc database', action="store_false", dest='update_wc') parser.add_argument('--xero-config-file', help='location of xero config file') parser.add_argument('--wc-config-file', help='location of wc config file') args = parser.parse_args() if args: print("verbosity: %s" % args.verbosity) if args.verbosity > 0: DebugUtils.CURRENT_VERBOSITY = args.verbosity elif args.quiet: DebugUtils.CURRENT_VERBOSITY = 0 if args.xero_config_file is not None: xero_config_file = args.xero_config_file if args.wc_config_file is not None: wc_config_file = args.wc_config_file DebugUtils.register_message('args: %s' % pformat(vars(args))) dir_conf = 'conf' path_conf_wc = os.path.join(dir_conf, wc_config_file) with open(path_conf_wc) as file_conf_wc: conf_wc = yaml.load(file_conf_wc) for key in ['consumer_key', 'consumer_secret', 'url']: assert key in conf_wc, key path_conf_xero = os.path.join(dir_conf, xero_config_file) with open(path_conf_xero) as file_conf_xero: conf_xero = yaml.load(file_conf_xero) for key in ['consumer_key', 'consumer_secret', 'key_file']: assert key in conf_xero, key wc_client = WpClient(**conf_wc) xero_client = XeroClient(**conf_xero) # fill wc_products wc_products = [] wc_container_class = WCAPIProduct if args.download_wc: wc_products_data = wc_client.get_products({ 'core_fields': [ wc_container_class.id_key, wc_container_class.managing_stock_key, wc_container_class.stock_level_key, wc_container_class.stock_status_key, wc_container_class.second_sku_key, wc_container_class.title_key, # 'status', ], 'meta_fields': ['MYOB SKU'], 'per_page': conf_wc.get('per_page', 300) }) wc_products = [wc_container_class(data) for data in wc_products_data] if wc_products: print "%d products downloaded from WC" % len(wc_products) else: print "it looks like there was a problem downloading products from the website." print "you can export the products from the website manually, and try again." quit() elif args.wc_import_file: wc_container_class = WCCSVProduct with open(args.wc_import_file, 'r') as csv_file: reader = csv.DictReader(csv_file) for row in reader: wc_products.append(wc_container_class(row)) if wc_products: DebugUtils.register_message("reporting WC") with open(wc_report_file, 'w+') as report_file: csv_writer = csv.writer(report_file) csv_writer.writerow(wc_container_class.report_attrs) for wc_product in wc_products: csv_writer.writerow([ SanitationUtils.coerce_ascii(getattr(wc_product, attr)) \ for attr in wc_container_class.report_attrs ]) for wc_product in wc_products: wc_sku = wc_product.get(wc_container_class.sku_key) wc_second_sku = wc_product.get(wc_container_class.second_sku_key) wc_managing_stock = wc_product.get( wc_container_class.managing_stock_key) wc_stock_level = wc_product.get(wc_container_class.stock_level_key) if not wc_managing_stock and wc_stock_level < 5: DebugUtils.register_warning('not tracked: %s' % wc_sku) if wc_sku and wc_sku != wc_second_sku: DebugUtils.register_warning( 'primary SKU (%s) != secondary SKU (%s) ' % (wc_sku, wc_second_sku)) if args.report_wc_and_quit: DebugUtils.register_message("quitting after report") quit() if args.download_xero: DebugUtils.register_message("downloading xero") try: xero_products_data = xero_client.get_products() except Exception as exc: print "failed to get xero products:\n%s" % ( traceback.format_exc(exc)) xero_products = [XeroProduct(data) for data in xero_products_data] if xero_products: print "%d products downloaded from Xero" % len(xero_products) else: print "it looks like there was a problem downloading products from Xero." print "check your config and try again." read() quit() else: xero_products_data = [{ 'Code': 'a', 'Name': 'A', 'QuantityOnHand': 2.0, 'IsTrackedAsInventory': True }, { 'Code': 'c', 'QuantityOnHand': 0.0, 'IsTrackedAsInventory': True }] xero_products = [XeroProduct(data) for data in xero_products_data] if xero_products: DebugUtils.register_message("reporting Xero") with open(xero_report_file, 'w+') as report_file: csv_writer = csv.writer(report_file) csv_writer.writerow(XeroProduct.report_attrs) for xero_product in xero_products: csv_writer.writerow([ SanitationUtils.coerce_ascii(getattr(xero_product, attr)) \ for attr in XeroProduct.report_attrs ]) if args.report_xero_and_quit: quit() DebugUtils.register_message("xero_products: %s" % (len(xero_products))) #group xero products by sku xero_sku_groups = {} for xero_product in xero_products: xero_name = xero_product.title xero_sku = xero_product.sku xero_stock = xero_product.stock_level xero_id = xero_product.pid # xero_managing_stock = xero_product.managing_stock DebugUtils.register_message(" ".join( SanitationUtils.coerce_ascii(x) for x in \ ["processing xero", xero_id, xero_name, xero_sku, xero_stock] )) if xero_sku: xero_sku_groups[xero_sku] = [ prod for prod in \ xero_sku_groups.get(xero_sku, []) + [xero_product] \ if prod ] #group wc products by sku wc_sku_groups = {} for wc_product in wc_products: wc_name = wc_product.title wc_sku = wc_product.sku wc_stock = wc_product.stock_level wc_id = wc_product.pid DebugUtils.register_message(" ".join( SanitationUtils.coerce_ascii(x) for x in \ ["processing wc", wc_id, wc_name, wc_sku, wc_stock] )) if wc_sku: wc_sku_groups[wc_sku] = [ prod for prod in \ wc_sku_groups.get(wc_sku, []) + [wc_product] \ if prod ] #match products on sku matches = [] for sku, wc_product_group in wc_sku_groups.items(): if xero_sku_groups.get(sku): matches.append((wc_product_group, xero_sku_groups[sku])) good_matches = [] bad_matches = [] # skip_matches = [] bad_data = [] delta_matches = [] delta_data = [] new_wc_products = [] new_data = [] for match in matches: matched_wc_products = match[0] matched_xero_products = match[1] if len(matched_wc_products) != 1 or len(matched_xero_products) != 1: bad_matches.append(match) # print "bad match:", match DebugUtils.register_message( ' '.join((SanitationUtils.coerce_unicode(x) for x in \ ["not 1:1 match", matched_wc_products, matched_xero_products])) ) bad_row = [ "; ".join([ SanitationUtils.coerce_ascii(product) for product in products ]) for products in [matched_wc_products, matched_xero_products] ] bad_data.append(bad_row) continue wc_product = matched_wc_products[0] xero_product = matched_xero_products[0] if wc_product.managing_stock != xero_product.managing_stock: delta_matches.append(match) continue try: for product in wc_product, xero_product: assert product.stock_level is not None wc_stock = wc_product.stock_level xero_stock = xero_product.stock_level except (TypeError, ValueError, AssertionError): bad_matches.append(match) DebugUtils.register_message( ' '.join((SanitationUtils.coerce_unicode(x) for x in \ ["cant cast to int", repr(wc_product.get('stock_quantity')), \ repr(xero_product.get('QuantityOnHand'))])) ) continue if wc_stock != xero_stock: delta_matches.append(match) continue good_matches.append(match) for delta_match in delta_matches: wc_product = delta_match[0][0] xero_product = delta_match[1][0] # wc_name = wc_product.title # xero_name = xero_product.title wc_level = wc_product.stock_level xero_level = xero_product.stock_level delta_row = [ SanitationUtils.coerce_ascii(cell) for cell in\ [ wc_product, wc_level, wc_product.managing_stock, xero_product, xero_level, xero_product.managing_stock, "%+3d" % (xero_level - wc_level) ] ] delta_data.append(delta_row) new_wc_product = copy(wc_product) new_wc_product.stock_level = xero_product.stock_level new_wc_product.managing_stock = xero_product.managing_stock new_wc_products.append(new_wc_product) new_data.append([ SanitationUtils.coerce_ascii(x) for x in \ [new_wc_product, new_wc_product.stock_level, new_wc_product.stock_status, new_wc_product.managing_stock] ]) # DebugUtils.register_message( # ' '.join((SanitationUtils.coerce_unicode(x) for x in \ # ["changing", wc_name, xero_name, wc_stock, xero_stock])) # ) DebugUtils.register_message("good: %3d" % len(good_matches)) DebugUtils.register_message("bad: %3d" % len(bad_matches)) DebugUtils.register_message("delta: %3d" % len(delta_matches)) # report bad if bad_data: DebugUtils.register_message( "CONFLICTS:\n%s" % tabulate(bad_data, headers=('WC Products', 'Xero Products'))) # report deltas if delta_matches: DebugUtils.register_message( "CHANGES:\n%s" % tabulate(delta_data, headers=('WC Product', 'stock', 'managing', 'Xero Product', 'stock', 'managing', 'delta'))) if new_wc_products: DebugUtils.register_message("reporting WC to %s" % new_wc_report_file) with open(new_wc_report_file, 'w+') as report_file: csv_writer = csv.writer(report_file) csv_writer.writerow(WCProduct.report_attrs) for wc_product in new_wc_products: csv_writer.writerow([ SanitationUtils.coerce_ascii(getattr(wc_product, attr)) \ for attr in WCProduct.report_attrs ]) if args.update_wc: if new_wc_products: update_progress_counter = ProgressCounter(len(new_wc_products)) DebugUtils.register_message("Updating WC") for count, product in enumerate(new_wc_products): update_progress_counter.maybe_print_update(count) data = { "product": OrderedDict([ (WCAPIProduct.stock_level_key, product.stock_level), (WCAPIProduct.stock_status_key, product.stock_status), (WCAPIProduct.managing_stock_key, product.managing_stock) ]) } response = None try: response = wc_client.update_product(product.pid, data) except ReadTimeout as e: DebugUtils.register_warning( "request timed out, trying again after a short break") sleep(10) try: response = wc_client.update_product(product.pid, data) except ReadTimeout as e: DebugUtils.register_error(( "!!! API keeps timing out, " "please try again later or manually upload the CSV file" )) quit() finally: DebugUtils.register_message("API responeded :%s" % response) else: print "no updates need to be made" print "Sync complete."
def main(): dir_module = os.path.dirname(sys.argv[0]) if dir_module: os.chdir(dir_module) #default arguments xero_config_file = 'xero_api.yaml' wc_config_file = 'wc_api_test.yaml' xero_report_file = 'xero_products.csv' wc_report_file = 'wc_products.csv' new_wc_report_file = 'new_wc_products.csv' parser = argparse.ArgumentParser(description='Merge products between Xero and WC') group = parser.add_mutually_exclusive_group() group.add_argument("-v", "--verbosity", action="count", default=2, help="increase output verbosity") group.add_argument("-q", "--quiet", action="store_true") group = parser.add_mutually_exclusive_group() group.add_argument('--download-xero', help='download the xero data', action="store_true", default=None) group.add_argument('--skip-download-xero', help='use the local xero file instead\ of downloading the xero data', action="store_false", dest='download_xero') group = parser.add_mutually_exclusive_group() group.add_argument('--download-wc', help='download the wc data', action="store_true", default=None) group.add_argument('--skip-download-wc', help='use the local wc file instead\ of downloading the wc data', action="store_false", dest='download_wc') parser.add_argument('--wc-import-file', help='location of wc import file, used if can\'t download products') group = parser.add_mutually_exclusive_group() group.add_argument('--update-wc', help='update the wc database', action="store_true", default=None) group.add_argument('--report-wc-and-quit', help='report wc data and quit', action="store_true", default=None) group.add_argument('--report-xero-and-quit', help='report wc data and quit', action="store_true", default=None) group.add_argument('--skip-update-wc', help='don\'t update the wc database', action="store_false", dest='update_wc') parser.add_argument('--xero-config-file', help='location of xero config file') parser.add_argument('--wc-config-file', help='location of wc config file') args = parser.parse_args() if args: print("verbosity: %s" % args.verbosity) if args.verbosity > 0: DebugUtils.CURRENT_VERBOSITY = args.verbosity elif args.quiet: DebugUtils.CURRENT_VERBOSITY = 0 if args.xero_config_file is not None: xero_config_file = args.xero_config_file if args.wc_config_file is not None: wc_config_file = args.wc_config_file DebugUtils.register_message('args: %s' % pformat(vars(args))) dir_conf = 'conf' path_conf_wc = os.path.join(dir_conf, wc_config_file) with open(path_conf_wc) as file_conf_wc: conf_wc = yaml.load(file_conf_wc) for key in ['consumer_key', 'consumer_secret', 'url']: assert key in conf_wc, key path_conf_xero = os.path.join(dir_conf, xero_config_file) with open(path_conf_xero) as file_conf_xero: conf_xero = yaml.load(file_conf_xero) for key in ['consumer_key', 'consumer_secret', 'key_file']: assert key in conf_xero, key wc_client = WpClient( **conf_wc ) xero_client = XeroClient( **conf_xero ) # fill wc_products wc_products = [] wc_container_class = WCAPIProduct if args.download_wc: wc_products_data = wc_client.get_products({ 'core_fields': [ wc_container_class.id_key, wc_container_class.managing_stock_key, wc_container_class.stock_level_key, wc_container_class.stock_status_key, wc_container_class.second_sku_key, wc_container_class.title_key, # 'status', ], 'meta_fields': ['MYOB SKU'], 'per_page': conf_wc.get('per_page', 300) }) wc_products = [wc_container_class(data) for data in wc_products_data] if wc_products: print "%d products downloaded from WC" % len(wc_products) else: print "it looks like there was a problem downloading products from the website." print "you can export the products from the website manually, and try again." quit() elif args.wc_import_file: wc_container_class = WCCSVProduct with open(args.wc_import_file, 'r') as csv_file: reader = csv.DictReader(csv_file) for row in reader: wc_products.append(wc_container_class(row)) if wc_products: DebugUtils.register_message("reporting WC") with open(wc_report_file, 'w+') as report_file: csv_writer = csv.writer(report_file) csv_writer.writerow(wc_container_class.report_attrs) for wc_product in wc_products: csv_writer.writerow([ SanitationUtils.coerce_ascii(getattr(wc_product, attr)) \ for attr in wc_container_class.report_attrs ]) for wc_product in wc_products: wc_sku = wc_product.get(wc_container_class.sku_key) wc_second_sku = wc_product.get(wc_container_class.second_sku_key) wc_managing_stock = wc_product.get(wc_container_class.managing_stock_key) wc_stock_level = wc_product.get(wc_container_class.stock_level_key) if not wc_managing_stock and wc_stock_level < 5: DebugUtils.register_warning('not tracked: %s' % wc_sku) if wc_sku and wc_sku != wc_second_sku: DebugUtils.register_warning('primary SKU (%s) != secondary SKU (%s) ' % (wc_sku, wc_second_sku)) if args.report_wc_and_quit: DebugUtils.register_message("quitting after report") quit() if args.download_xero: DebugUtils.register_message("downloading xero") try: xero_products_data = xero_client.get_products() except Exception as exc: print "failed to get xero products:\n%s" % ( traceback.format_exc(exc) ) xero_products = [XeroProduct(data) for data in xero_products_data] if xero_products: print "%d products downloaded from Xero" % len(xero_products) else: print "it looks like there was a problem downloading products from Xero." print "check your config and try again." read() quit() else: xero_products_data = [ {'Code':'a', 'Name':'A', 'QuantityOnHand':2.0, 'IsTrackedAsInventory':True}, {'Code':'c', 'QuantityOnHand':0.0, 'IsTrackedAsInventory':True} ] xero_products = [XeroProduct(data) for data in xero_products_data] if xero_products: DebugUtils.register_message("reporting Xero") with open(xero_report_file, 'w+') as report_file: csv_writer = csv.writer(report_file) csv_writer.writerow(XeroProduct.report_attrs) for xero_product in xero_products: csv_writer.writerow([ SanitationUtils.coerce_ascii(getattr(xero_product, attr)) \ for attr in XeroProduct.report_attrs ]) if args.report_xero_and_quit: quit() DebugUtils.register_message("xero_products: %s" % (len(xero_products))) #group xero products by sku xero_sku_groups = {} for xero_product in xero_products: xero_name = xero_product.title xero_sku = xero_product.sku xero_stock = xero_product.stock_level xero_id = xero_product.pid # xero_managing_stock = xero_product.managing_stock DebugUtils.register_message(" ".join( SanitationUtils.coerce_ascii(x) for x in \ ["processing xero", xero_id, xero_name, xero_sku, xero_stock] )) if xero_sku: xero_sku_groups[xero_sku] = [ prod for prod in \ xero_sku_groups.get(xero_sku, []) + [xero_product] \ if prod ] #group wc products by sku wc_sku_groups = {} for wc_product in wc_products: wc_name = wc_product.title wc_sku = wc_product.sku wc_stock = wc_product.stock_level wc_id = wc_product.pid DebugUtils.register_message(" ".join( SanitationUtils.coerce_ascii(x) for x in \ ["processing wc", wc_id, wc_name, wc_sku, wc_stock] )) if wc_sku: wc_sku_groups[wc_sku] = [ prod for prod in \ wc_sku_groups.get(wc_sku, []) + [wc_product] \ if prod ] #match products on sku matches = [] for sku, wc_product_group in wc_sku_groups.items(): if xero_sku_groups.get(sku): matches.append((wc_product_group, xero_sku_groups[sku])) good_matches = [] bad_matches = [] # skip_matches = [] bad_data = [] delta_matches = [] delta_data = [] new_wc_products = [] new_data = [] for match in matches: matched_wc_products = match[0] matched_xero_products = match[1] if len(matched_wc_products) != 1 or len(matched_xero_products) != 1: bad_matches.append(match) # print "bad match:", match DebugUtils.register_message( ' '.join((SanitationUtils.coerce_unicode(x) for x in \ ["not 1:1 match", matched_wc_products, matched_xero_products])) ) bad_row = [ "; ".join([ SanitationUtils.coerce_ascii(product) for product in products ]) for products in [matched_wc_products, matched_xero_products] ] bad_data.append(bad_row) continue wc_product = matched_wc_products[0] xero_product = matched_xero_products[0] if wc_product.managing_stock != xero_product.managing_stock: delta_matches.append(match) continue try: for product in wc_product, xero_product: assert product.stock_level is not None wc_stock = wc_product.stock_level xero_stock = xero_product.stock_level except (TypeError, ValueError, AssertionError): bad_matches.append(match) DebugUtils.register_message( ' '.join((SanitationUtils.coerce_unicode(x) for x in \ ["cant cast to int", repr(wc_product.get('stock_quantity')), \ repr(xero_product.get('QuantityOnHand'))])) ) continue if wc_stock != xero_stock: delta_matches.append(match) continue good_matches.append(match) for delta_match in delta_matches: wc_product = delta_match[0][0] xero_product = delta_match[1][0] # wc_name = wc_product.title # xero_name = xero_product.title wc_level = wc_product.stock_level xero_level = xero_product.stock_level delta_row = [ SanitationUtils.coerce_ascii(cell) for cell in\ [ wc_product, wc_level, wc_product.managing_stock, xero_product, xero_level, xero_product.managing_stock, "%+3d" % (xero_level - wc_level) ] ] delta_data.append(delta_row) new_wc_product = copy(wc_product) new_wc_product.stock_level = xero_product.stock_level new_wc_product.managing_stock = xero_product.managing_stock new_wc_products.append(new_wc_product) new_data.append([ SanitationUtils.coerce_ascii(x) for x in \ [new_wc_product, new_wc_product.stock_level, new_wc_product.stock_status, new_wc_product.managing_stock] ]) # DebugUtils.register_message( # ' '.join((SanitationUtils.coerce_unicode(x) for x in \ # ["changing", wc_name, xero_name, wc_stock, xero_stock])) # ) DebugUtils.register_message("good: %3d" % len(good_matches)) DebugUtils.register_message("bad: %3d" % len(bad_matches)) DebugUtils.register_message("delta: %3d" % len(delta_matches)) # report bad if bad_data: DebugUtils.register_message("CONFLICTS:\n%s" % tabulate( bad_data, headers=('WC Products', 'Xero Products') )) # report deltas if delta_matches: DebugUtils.register_message("CHANGES:\n%s" % tabulate( delta_data, headers=( 'WC Product', 'stock', 'managing', 'Xero Product', 'stock', 'managing', 'delta' ) )) if new_wc_products: DebugUtils.register_message("reporting WC to %s" % new_wc_report_file) with open(new_wc_report_file, 'w+') as report_file: csv_writer = csv.writer(report_file) csv_writer.writerow(WCProduct.report_attrs) for wc_product in new_wc_products: csv_writer.writerow([ SanitationUtils.coerce_ascii(getattr(wc_product, attr)) \ for attr in WCProduct.report_attrs ]) if args.update_wc: if new_wc_products: update_progress_counter = ProgressCounter(len(new_wc_products)) DebugUtils.register_message("Updating WC") for count, product in enumerate(new_wc_products): update_progress_counter.maybe_print_update(count) data = {"product": OrderedDict([ (WCAPIProduct.stock_level_key, product.stock_level), (WCAPIProduct.stock_status_key, product.stock_status), (WCAPIProduct.managing_stock_key, product.managing_stock) ])} response = None try: response = wc_client.update_product(product.pid, data) except ReadTimeout as e: DebugUtils.register_warning( "request timed out, trying again after a short break" ) sleep(10) try: response = wc_client.update_product(product.pid, data) except ReadTimeout as e: DebugUtils.register_error( ("!!! API keeps timing out, " "please try again later or manually upload the CSV file") ) quit() finally: DebugUtils.register_message("API responeded :%s" % response) else: print "no updates need to be made" print "Sync complete."