def test_local(): wcapi = API(url='http://localhost:18080/wptest/', consumer_key='ck_b13285e31ed659227f2fdc1db856d1d3d0b06c21', consumer_secret='cs_3038b86805335faa887c6605364bcd1df9e57254', api="wp-json", version="wc/v2") target_product_id = 22 # id=22 is variable # id=23 is variation of 22 print "\n\n*******\n* GET *\n*******\n\n" response = wcapi.get('products') response = wcapi.get('products/%s' % target_product_id) # response = wcapi.get('products?page=2') # response = wcapi.put('products/99', {'product':{'title':'Woo Single #2a'}} ) # response = wcapi.put('products/99?id=http%3A%2F%2Fprinter', # {'product':{'title':'Woo Single #2a'}} ) print_response(response) print "\n\n*******\n* PUT *\n*******\n\n" data = {'product': {'custom_meta': {'attribute_pa_color': 'grey'}}} response = wcapi.put('products/%s' % target_product_id, data) print_response(response)
def test_technotea(): wcapi = API(url='http://technotea.com.au/', consumer_key='ck_204e2f68c5ca4d3be966b7e5da7a48cac1a75104', consumer_secret='cs_bcb080a472a2bbded7dc421c63cbe031a91241f5', api="wp-json", version="wc/v2") target_product_id = 24009 print "\n\n*******\n* GET *\n*******\n\n" response = wcapi.get('products/%s?fields=meta' % target_product_id) response = wcapi.get('products/categories?filter[q]=solution') categories = response.json() print "categories: %s" % pformat([(category['id'], category['name']) for category in categories]) # print_response(response) print "\n\n*******\n* GET 2 *\n*******\n\n" response = wcapi.get('products/%s' % target_product_id) product_categories = response.json().get('categories', []) print "categories: %s" % pformat(product_categories) print "categories: %s" % pformat([(category['id'], category['name']) for category in categories]) print_response(response) print "\n\n*******\n* PUT *\n*******\n\n" data = {'product': {'custom_meta': {'wootan_danger': 'D'}}} response = wcapi.put('products/%s' % target_product_id, data) print_response(response) print "\n\n*******\n* PUT 2 *\n*******\n\n" data = {'product': {'categories': [898]}} response = wcapi.put('products/%s' % target_product_id, data) response = wcapi.get('products/%s?fields=categories' % target_product_id) product_categories = response.json().get('product', {}).get('categories', []) print "categories: %s" % pformat(product_categories)
class Postman: DEFAULT_FEATURED_MEDIA = 151 DEFAULT_CATEGORY = 13 def __init__(self, url, username, password): self.__wp_api = API(url=url, api="wp-json", version='wp/v2', wp_user=username, wp_pass=password, basic_auth=True, user_auth=True, consumer_key="", consumer_secret="") assert self.__wp_api.get("categories").status_code == 200 def upload_media(self, file_path): assert os.path.exists(file_path), "img should exist at {}".format( file_path) data = open(file_path, 'rb').read() filename = os.path.basename(file_path) _, extension = os.path.splitext(filename) headers = { 'cache-control': 'no-cache', 'content-disposition': 'attachment; filename=%s' % filename, 'content-type': 'image/%s' % extension } response = self.__wp_api.post("media", data, headers=headers) print "Media upload response status:{}".format(response.status_code) return response.json() def create_post(self, post_dir, status): assert os.path.exists( post_dir), "Post Directory should exist at {}".format(post_dir) with open(os.path.join(post_dir, "metadata.json"), 'r') as f: metadata = json.loads(f.read()) post = { 'slug': metadata['information']['slug'], 'title': metadata['information']['title'] } if 'title-img' in metadata['information'] and metadata[ 'information']['title-img']: media = self.upload_media( os.path.join(post_dir, metadata['information']['title-img'])) post['featured_media'] = media['id'] else: post['featured_media'] = Postman.DEFAULT_FEATURED_MEDIA post['status'] = status post['categories'] = [Postman.DEFAULT_CATEGORY] assert metadata['information']['content-file'] with open( os.path.join( post_dir, metadata['information']['content-file'])) as c: post['content'] = c.read().decode('utf-8') headers = {'content-type': 'application/json'} response = self.__wp_api.post("posts", data=post, headers=headers) print "Create Post response status:{}".format(response.status_code) return response
# version="v3" # ) wcapi = API(url='http://technotea.com.au/', consumer_key='ck_204e2f68c5ca4d3be966b7e5da7a48cac1a75104', consumer_secret='cs_bcb080a472a2bbded7dc421c63cbe031a91241f5', api="wp-json", version="wc/v1") print "\n\n*******\n* GET *\n*******\n\n" # response = wcapi.get('products') # response = wcapi.get('products?page=2') # response = wcapi.put('products/99', {'product':{'title':'Woo Single #2a'}} ) # response = wcapi.put('products/99?id=http%3A%2F%2Fprinter', # {'product':{'title':'Woo Single #2a'}} ) response = wcapi.get('products/21391') print_response(response) quit() response = wcapi.get('products/categories') categories = response.json() print "categories: %s" % pformat([(category['id'], category['name']) for category in categories]) # print_response(response) # quit() response = wcapi.get('products/17834') product_categories = response.json().get('categories', []) print "categories: %s" % pformat(product_categories) # print "categories: %s" % pformat([(category['id'], category['name']) for
class WooCommerceShim(Database): """ Contains various methods for interacting with the WooCommerce API. These methods will do things such as adding new products, and removing sold products. """ def __init__(self, *args, **kwargs): super(WooCommerceShim, self).__init__(*args, **kwargs) # Setup logging self.log = logging.getLogger(__name__) self.log.setLevel(os.environ.get('log_level', 'INFO')) self.log.addHandler(LOG_HANDLER) self.api = WCAPI(url=os.environ.get('woo_url', False), consumer_key=os.environ.get('woo_key', False), consumer_secret=os.environ.get('woo_secret', False)) self.wp_api = WPAPI(url=os.environ.get('woo_url', False), api='wp-json', version='wp/v2', wp_user=os.environ.get('wordpress_user', False), wp_pass=os.environ.get('wordpress_app_password', False), basic_auth=True, user_auth=True, consumer_key=False, consumer_secret=False) mapping_path = os.environ.get( 'category_mapping', 'database/ebay-to-woo-commerce-category-map.json') try: with open(mapping_path, 'r') as mapping_file: self.category_mapping = json.load(mapping_file) except IOError: self.category_mapping = None def __does_image_exist_on_woocommerce(self, slug): """ Searches the Wordpress media library for any files that have a URL `slug` that matches the one provided Returns True if the file exists, and False otherwise This method is unreliable! Duplicates are basically guarenteed to happen... """ self.log.info('Checking if a file has a slug matching: %s' % (slug)) result = self.wp_api.get('/media?slug=%s' % (slug)).json() if len(result) == 0: return False return True def __divide_into_chunks(self, iterable, chunk_size=100): """ Used to make bulk requests via the API, which limits the amount of products to change at once to 100 `iterable` is something that can be iterated over, be it a list or a range. When a range, you must wrap the output of this method in `list()` `chunk_size` is optional, and defines how many products to change per request. The default of 100 is the maximum that the API will allow Returns the `iterable` containing as many items as are in the `chunk_size` """ for i in range(0, len(iterable), chunk_size): yield iterable[i:i + chunk_size] def __search_map(self, value, field): """ Uses List Comprehension to search for the `value` in the `field`. Normal usage would be similar to `self.__search_map(ebay_category_id, 'ebay_ids')` Returns an integer, which is the first matching Woo Commerce ID for the selected `value` (in the case that one ebay category is mapped to multiple woo commerce categories) When a matching category can't be found, this method will call itself to search for the "Uncategorized" `value` on the "wc-name" `field` """ try: mapped = [ key for key in self.category_mapping if value in key[field] ] return int(mapped[0]['wc-id']) except IndexError: # Couldn't find it, return the uncategorized id return self.__search_map('Uncategorized', 'wc-name') def does_product_exist(self, item_id): """ Determines if the product with the `item_id` has already been uploaded to WooCommerce, by checking for the truthyness of `post_id` """ data = self.db_get_product_data(item_id) if data.get('post_id') is not None: return True return False def get_mapped_category_id(self, ebay_category_id): """ Determines if the user provided a category mapping, and if so returns an integer, which is the Woo Commerce category id that is mapped to the `ebay_category_id` (or the Uncategorized category id if a mapping can not be found) In the case that the user has not provided a category mapping, this method returns None """ if self.category_mapping is not None: return self.__search_map(ebay_category_id, 'ebay_ids') return None def download_product_images_from_ebay(self, item_id): """ Downloads all of the images for a provided `item_id` and returns a dictionary containing the image name, mime type, and bytes-like object for the raw images The image URLs come from the database table `item_metadata`, which is populated when `self.__get_item_metadata()` runs """ count = 0 return_images = list() image_urls = self.db_get_product_image_urls(item_id) image_urls_count = len(image_urls) if image_urls_count > 0: self.log.info("Found %d image URLs for: %s" % (image_urls_count, item_id)) for image in image_urls: url = image.get('value', '') if image.get('post_id') is not None: self.log.warning( "We've already uploaded %s, skipping download" % (url)) continue self.log.info("Downloading %s" % (url)) req = requests.get(url) if req.content: mime_type = req.headers.get('Content-Type', '') slug = '%s-%d' % (item_id, count) extension = mime_type.split('/')[1] filename = '%s.%s' % (slug, extension) if 'image' not in mime_type: msg = "%d didn't get an image somehow. Content type was: %s" self.log.error(msg % (item_id, mime_type)) continue return_images.append( Image(slug=slug, ebay_url=url, name=filename, mime_type=mime_type, data=req.content)) # self.log.info("Image %s downloaded" % (filename)) if count < image_urls_count: self.log.debug( "Waiting a quarter second until next download") time.sleep(0.25) else: self.log.error( "No content returned. Is %s reachable in a browser?" % (url)) count += 1 else: self.log.warning("No Image URLs found for item: %s" % (item_id)) return return_images def upload_image_to_woocommerce(self, image, post_id): """ Uploads the provided `image` to wordpress, and returns the response `image` is a dictionary containing the following keys: `name` - This is the destination file name, including extension `type` - This is the MIMETYPE of the image, usually derived from the extension `data` - This is a bytes-like object representing the entire image. We get this from dowloading an image directly from Ebay's servers and temporarily storing it in memory `post_id` is the post in which to attach the image to. This is returned in the response from `self.create_product()` Returns either a string containing the URL the image can be found at, or False if the image fails to be uploaded """ self.log.info("Uploading %s to wordpress" % (image.name)) endpoint = '/media?post=%d' % (post_id) headers = { 'cache-control': 'no-cache', 'content-disposition': 'attachment; filename=%s' % (image.name), 'content-type': '%s' % (image.mime_type) } # Don't upload a duplicate image if it was uploaded in the past if self.__does_image_exist_on_woocommerce(image.slug): self.log.warning( "Image %s already exists on wordpress. Not uploading again" % (image.name)) return None, None # Upload the image response = self.wp_api.post(endpoint, image.data, headers=headers) try: image_id = response.json().get('id') url = response.json().get('guid', dict).get('raw') self.log.debug("Uploaded %s to %s" % (image.name, url)) return image_id, url except AttributeError: self.log.error('Could not upload %s' % image.name) return None, None def upload_product_images(self, item_id): """ With the provided `item_id`, the database is searched for the post id (set during `create_product`) When the post_id is found, it will be used to download the images for that product from ebay, and then upload the images """ post_id = self.db_woo_get_post_id(item_id) gallery = [] if post_id is not None: for image in self.download_product_images_from_ebay(item_id): image_id, url = self.upload_image_to_woocommerce( image, post_id) if image_id and url: self.db_metadata_uploaded(image_id, item_id) gallery.append({'id': image_id}) # Add the images to the gallery self.api.put('products/%d' % (post_id), {'images': gallery}).json() else: self.log.warning('The product %d has not yet been uploaded' % (item_id)) return self def create_product(self, item_id): """ Pulls the product related to the `item_id` out of the database and uploads it to WooCommerce Returns the result as JSON """ attributes = list() attributes_to_upload = list() self.log.info('Creating a WooCommerce product from ebay id: %s' % (item_id)) if self.does_product_exist(item_id): self.log.warning( 'Product with item id %d already exists, skipping' % (item_id)) return self product = self.db_get_product_data(item_id) attributes = self.db_get_all_product_metadata(item_id) # Strip out any pictures attributes = [ attribute for attribute in attributes if attribute['key'] != 'picture_url' ] # Format the attributes in a way that WooCommerce is expecting for index, attribute in enumerate(attributes): attributes_to_upload.append({ 'name': attribute['key'], 'options': [attribute['value']], 'visible': True, 'variation': True, 'position': index, }) upload_data = { 'name': product['title'], 'type': 'simple', 'status': 'publish', 'short_description': product['condition_description'], 'description': DEFAULT_DESCRIPTION, 'sku': product['sku'], # 'attributes': attributes_to_upload, # 'default_attributes': attributes_to_upload, } # Add the category id category_id = self.get_mapped_category_id(product.get( 'category_id', 0)) if category_id is not None: upload_data['categories'] = [{'id': category_id}] res = self.api.post('products', upload_data).json() self.log.debug(res) if res.get('id', False): self.db_product_uploaded(res['id'], item_id) else: # Invalid or duplicate sku if res.get('code') == 'product_invalid_sku': if res.get('data') and res['data'].get('resource_id'): new_post_id = res['data']['resource_id'] self.log.warning( 'The SKU for %s already exists for %s. Updating.' % (new_post_id, item_id)) self.db_product_uploaded(new_post_id, item_id) else: self.log.error('Unable to retrive product_id') self.log.debug(res) self.log.debug(upload_data) return self def delete_product_images(self, item_id): """ With the provided `item_id`, an API request will be made to WooCommerce to identify all images associted with it. Then, it will delete each of those images. """ # This feature has not been implemented. # Delete media through wordpress directly pass def delete_product(self, item_id): """ With the provided `item_id`, an API request will be made to WooCommerce to force delete the item The `item_id` is supplied by the queue, which gets them from `db.db_get_inactive_uploaded_item_ids()` When an item is force deleted, it will not appear in the "Trash" Returns the response as a dictionary or None if there is no post id """ post_id = self.db_woo_get_post_id(item_id) if post_id is not None: self.log.info('Deleting %d from WooCommerce' % (item_id)) try: response = self.api.delete('products/%d' % (post_id), params={ 'force': True }).json() except TypeError: self.log.error("Got unexpected response type: %s" % (str(response))) return None self.delete_product_images(post_id) status_code = response.get('data', dict).get('staus', 500) if status_code == 404: self.log.warning("Product was already deleted") elif status_code < 300 and status_code > 199: self.log.info('Product deleted') else: self.log.debug(response) return response return None def delete_all_products_in_range(self, id_range, chunk_size=100): """ With a provided `id_range`, which is expected to be a `range` or `list` type, multiple bulk requests will be made to the Woo Commerce API to delete those items. When `id_range` is of type(range), your ending ID needs to be the last ID to delete + 1 Returns None """ self.log.info('Deleting products from %d to %d' % (id_range[0], id_range[-1])) # The API says that it supports chunks up to 100 items, but in testing # it would always time out, even if it successfully deleted the items # with any chunk size greater than or equal to 50 for chunk in self.__divide_into_chunks(id_range, chunk_size): post_ids = list(chunk) data = {'delete': post_ids} self.api.post('products/batch', data) self.log.info('Deleted ids %s' % (post_ids)) for post_id in post_ids: self.delete_product_images(post_id) def try_command(self, command, data): """ Wrapper for running methods. Verifies that we support the method, raising a NameError if not and then runs the method specified in the `command` argument in a try, except statement `command` is a string that is inside `__available_commands` `data` is dependent on the type of command that is being ran. In most instances, it is an integer containing the ebay ItemID. With the `delete_all_products` command, it is either a range or a list containing the post ids for existing products """ __available_commands = [ 'create_product', 'delete_product', 'upload_images', 'delete_all_products', ] err_msg = "Command %s is unrecognized. Supported commands are: %s" % ( command, ', '.join(__available_commands)) if command not in __available_commands: self.log.exception(err_msg) raise NameError(err_msg) try: if command == 'create_product': return self.create_product(data) elif command == 'delete_product': return self.delete_product(data) elif command == 'upload_images': return self.upload_product_images(data) elif command == 'delete_all_products': return self.delete_all_products_in_range(data) else: self.log.exception(err_msg) raise NameError(err_msg) # The several kinds of timeout exceptions that are normally returned by the API except (timeout, ReadTimeoutError, requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout): self.log.warning( 'The Previous request Timed Out. Waiting 5s before retrying') time.sleep(5) self.try_command(command, data)
) # wcapi = API( # url='http://technotea.com.au/', # consumer_key='ck_204e2f68c5ca4d3be966b7e5da7a48cac1a75104', # consumer_secret='cs_bcb080a472a2bbded7dc421c63cbe031a91241f5', # api="wc-api", # version="v3" # ) print "\n\n*******\n* GET *\n*******\n\n" ### tests for wordpress local test # response = wcapi.get('products') # response = wcapi.get('products/22') # id=22 is variable response = wcapi.get('products/23') # id=23 is variation of 22 # response = wcapi.get('products?page=2') # response = wcapi.put('products/99', {'product':{'title':'Woo Single #2a'}} ) # response = wcapi.put('products/99?id=http%3A%2F%2Fprinter', {'product':{'title':'Woo Single #2a'}} ) ### tests for technotea # response = wcapi.get('products/21391?fields=meta') # response = wcapi.get('products/categories?filter[q]=solution') # categories = response.json().get('product_categories') # print "categories: %s" % pformat([(category['id'], category['name']) for category in categories]) print_response(response) # response = wcapi.get('products/17834')
def main(): wcapi = API(url='http://localhost:18080/wptest/', consumer_key='ck_b13285e31ed659227f2fdc1db856d1d3d0b06c21', consumer_secret='cs_3038b86805335faa887c6605364bcd1df9e57254', api="wc-api", version="v3") # wcapi = API( # url='http://technotea.com.au/', # consumer_key='ck_204e2f68c5ca4d3be966b7e5da7a48cac1a75104', # consumer_secret='cs_bcb080a472a2bbded7dc421c63cbe031a91241f5', # api="wc-api", # version="v3" # ) target_product_id = 22 # on woocommerce test data, # id=22 is variable # id=23 is variation of 22 print "\n\n*******\n* GET *\n*******\n\n" # tests for wordpress local test # response = wcapi.get('products') response = wcapi.get('products/%s' % target_product_id) # response = wcapi.get('products?page=2') # response = wcapi.put('products/99', {'product':{'title':'Woo Single #2a'}} ) # response = wcapi.put('products/99?id=http%3A%2F%2Fprinter', # {'product':{'title':'Woo Single #2a'}} ) # tests for technotea # response = wcapi.get('products/21391?fields=meta') # response = wcapi.get('products/categories?filter[q]=solution') # categories = response.json().get('product_categories') # print "categories: %s" % pformat([(category['id'], category['name']) for # category in categories]) print_response(response) # response = wcapi.get('products/17834') # product_categories = response.json().get('product',{}).get('categories',[]) # print "categories: %s" % pformat(product_categories) # print "categories: %s" % pformat([(category['id'], category['name']) for # category in categories]) print "\n\n*******\n* PUT *\n*******\n\n" # Tests for woocommerce local staging data = {'product': {'custom_meta': {'attribute_pa_color': 'grey'}}} response = wcapi.put('products/%s' % target_product_id, data) print_response(response) # Tests for technotea # data = {'product':{'custom_meta':{'wootan_danger':'D'}}} # response = wcapi.put('products/21391', data) # print_response(response) print "\n\n*******\n* GET 2 *\n*******\n\n" # Tests for woocommerce local staging response = wcapi.get('products/%s' % target_product_id) print_response(response)
def upload_video(post): post_id = create_post(post) upload(post_id, post.video_url, ".mp4", "video/mp4", post.date_utc) def upload_sidecar(post): post_id = create_post(post) for node in post.get_sidecar_nodes(): if node.is_video: upload(post_id, node.video_url, ".mp4", "video/mp4", post.date_utc) else: upload(post_id, node.display_url, ".jpg", "image/jpeg", post.date_utc) categories = wpapi.get("categories") assert categories.status_code == 200, "Unable to fetch categories from WordPress" for c in categories.json(): if c['name'] == WORDPRESS_CATEGORY: category_id = c['id'] print("Found %s category ID: %i" % (WORDPRESS_CATEGORY, category_id)) break assert category_id != -1, "Unable to find requested WordPress category" posts = wpapi.get("posts?per_page=1&categories=" + str(category_id)) assert posts.status_code == 200, "Unable to fetch posts from WordPress" latest_post_date = datetime.fromisoformat(posts.json()[0]['date_gmt'] + "+00:00") print("Most recent post in category: %s" % (latest_post_date))
import os from dotenv import load_dotenv from wordpress import API load_dotenv() consumer_key = os.getenv('GF_CK') consumer_secret = os.getenv('GF_CS') url = os.getenv('GF_URL') gfapi = API( url=url, api="wp-json", version='gf/v2', consumer_key=consumer_key, consumer_secret=consumer_secret, ) entradas = gfapi.get('entries') print(entradas.json()['total_count']) print(entradas.json()['entries'])
# ) wcapi = API( url='http://technotea.com.au/', consumer_key='ck_204e2f68c5ca4d3be966b7e5da7a48cac1a75104', consumer_secret='cs_bcb080a472a2bbded7dc421c63cbe031a91241f5', api="wp-json", version="wc/v1" ) print "\n\n*******\n* GET *\n*******\n\n" # response = wcapi.get('products') # response = wcapi.get('products?page=2') # response = wcapi.put('products/99', {'product':{'title':'Woo Single #2a'}} ) # response = wcapi.put('products/99?id=http%3A%2F%2Fprinter', {'product':{'title':'Woo Single #2a'}} ) response = wcapi.get('products/21391') print_response(response) quit() response = wcapi.get('products/categories') categories = response.json() print "categories: %s" % pformat([(category['id'], category['name']) for category in categories]) # print_response(response) # quit() response = wcapi.get('products/17834') product_categories = response.json().get('categories',[]) print "categories: %s" % pformat(product_categories) # print "categories: %s" % pformat([(category['id'], category['name']) for category in categories])
class WpClient(ApiMixin): """Wraps around the wordpress API and provides extra useful methods""" kwarg_validations = { 'consumer_key': [ValidationUtils.not_none], 'consumer_secret': [ValidationUtils.not_none], 'url': [ValidationUtils.is_url], # 'wp_user':[ValidationUtils.not_none], # 'wp_pass':[ValidationUtils.not_none], # 'callback':[ValidationUtils.not_none], } page_nesting = True def __init__(self, *args, **kwargs): self.validate_kwargs(**kwargs) api_args = { 'version': 'v3', 'api': 'wc-api', 'basic_auth': True, # 'query_string_auth': False, 'query_string_auth': True, } for key in [ 'consumer_key', 'consumer_secret', 'url', 'version', 'api', 'basic_auth', 'query_string_auth' ]: if key in kwargs: api_args[key] = kwargs.get(key) self.api = WPAPI(**api_args) class WpApiPageIterator(WPAPI): """Creates an iterator based on a paginated wc api call""" def __init__(self, api, endpoint): self.api = api self.last_response = self.api.get(endpoint) def __iter__(self): return self 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 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 update_product(self, _id, data): assert isinstance(_id, int), "id must be int" assert isinstance(data, dict), "data must be dict" return self.api.put("products/%d" % _id, data).json()