def upload_image_asset(client, googleads_client, account, asset_name, path, adgroups): """Upload image asset and assign to ad groups if given.""" asset_service = Service_Class.get_asset_service(client) with open(path, 'rb') as image_handle: image_data = image_handle.read() # Construct media and upload image asset. image_asset = { 'xsi_type': 'ImageAsset', 'assetName': asset_name, 'imageData': image_data, } operation = {'operator': 'ADD', 'operand': image_asset} asset = asset_service.mutate([operation])['value'][0] new_asset = { 'id': asset['assetId'], 'name': asset['assetName'], 'type': 'IMAGE', 'image_url': asset['fullSizeInfo']['imageUrl'] } return _assign_new_asset_to_adgroups(client, googleads_client, account, new_asset, adgroups)
def upload_html5_asset(client, googleads_client, account, asset_name, path, adgroups): """Upload html5 asset and assign to ad groups if given.""" asset_service = Service_Class.get_asset_service(client) with open(path, 'rb') as html_handle: html_data = html_handle.read() media_bundle_asset = { 'xsi_type': 'MediaBundleAsset', 'assetName': asset_name, 'mediaBundleData': html_data } operation = {'operator': 'ADD', 'operand': media_bundle_asset} asset = asset_service.mutate([operation])['value'][0] new_asset = { 'id': asset['assetId'], 'name': asset['assetName'], 'type': 'MEDIA_BUNDLE', } return _assign_new_asset_to_adgroups(client, googleads_client, account, new_asset, adgroups)
def upload_yt_video_asset(client, googleads_client, account, asset_name, url, adgroups): """Upload YT video asset and assign to ad groups if given.""" asset_service = Service_Class.get_asset_service(client) url_data = urllib.parse.urlparse(url) if url_data.netloc == 'youtu.be': video_id = url_data.path.lstrip('/') else: query = urllib.parse.parse_qs(url_data.query) video_id = query['v'][0] vid_asset = { 'xsi_type': 'YouTubeVideoAsset', 'assetName': asset_name, 'youTubeVideoId': video_id } operation = {'operator': 'ADD', 'operand': vid_asset} asset = asset_service.mutate([operation])['value'][0] new_asset = { 'id': asset['assetId'], 'name': asset['assetName'], 'type': 'YOUTUBE_VIDEO', 'video_id': video_id, 'link': url, 'image_url': yt_thumbnail_url % (video_id) } return _assign_new_asset_to_adgroups(client, googleads_client, account, new_asset, adgroups)
def _get_ad_id(client, adgroup): """gets the ad id from the adgroup id.""" adgroupad_service = Service_Class.get_ad_group_ad_service(client) selector = { 'fields': ['Id'], 'predicates': [{ 'field': 'AdGroupId', 'operator': 'EQUALS', 'values': [adgroup] }], } page = adgroupad_service.get(selector) return page['entries'][0]['ad']['id']
def mutate_ad(client, account, adgroup, asset, action, text_type_to_assign='descriptions'): """Add or remove asset to a given adgroup's ad.""" if action not in actions: raise ValueError('action not supported') client.SetClientCustomerId(account) asset_type_map = { 'TEXT': 'TextAsset', 'IMAGE': 'ImageAsset', 'YOUTUBE_VIDEO': 'YouTubeVideoAsset', 'MEDIA_BUNDLE': 'MediaBundleAsset' } property_map = { 'TEXT': text_type_to_assign, 'IMAGE': 'images', 'YOUTUBE_VIDEO': 'videos', 'MEDIA_BUNDLE': 'html5MediaBundles' } asset_type = asset_type_map[asset['type']] ad_property = property_map[asset['type']] # ad id is required to add/remove assets from the adgroup ad_id = _get_ad_id(client, adgroup) selector = { 'fields': [ 'Id', 'UniversalAppAdDescriptions', 'UniversalAppAdHeadlines', 'UniversalAppAdImages', 'UniversalAppAdYouTubeVideos', 'UniversalAppAdHtml5MediaBundles', ], 'predicates': [{ 'field': 'Id', 'operator': 'EQUALS', 'values': [ad_id], }], } ad_service = Service_Class.get_ad_service(client) ad = ad_service.get(selector)['entries'][0] # Case 1: adding an asset to the ad if action == 'ADD': attached_asset = [{ 'asset': { 'xsi_type': asset_type, 'assetId': asset['id'], } }] # YT video assets require the video ID to assign to ad if asset_type == 'YouTubeVideoAsset': attached_asset[0]['asset']['youTubeVideoId'] = asset['video_id'] # Text assets require the text itself to assign to ad if asset_type == 'TextAsset': attached_asset[0]['asset']['assetText'] = asset['asset_text'] ad[ad_property] += attached_asset # Case 2: Removing an asset from the ad elif action == 'REMOVE': for item in ad[ad_property]: if item['asset']['assetId'] == asset['id']: ad[ad_property].remove(item) operations = [{ 'operator': 'SET', 'operand': ad, }] ad_service.mutate(operations) Service_Class.reset_cid(client)
def upload_asset(): """upload new asset to account and assign to specified adgroups. asset_type needs to be IMAGE,YOUTUBE_VIDEO,MEDIA_BUNDLE, descriptions, headlines """ data = request.get_json(force=True) if data.get('account') is None or data.get('asset_type') is None: return _build_response(msg='invalid arguments', status=400) # uniform file names asset_name = data.get('asset_name') if asset_name and data.get('asset_type') == 'IMAGE': asset_name = asset_name.replace(' ', '_') for char in string.punctuation: if char not in ['_', '-', '.']: asset_name = asset_name.replace(char, '') try: result = upload(client, googleads_client, data.get('account'), data.get('asset_type'), asset_name, asset_text=data.get('asset_text'), path=UPLOAD_FOLDER / asset_name, url=data.get('url'), adgroups=data.get('adgroups')) except Exception as e: logging.exception(e) Service_Class.reset_cid(client) # Asset not uploaded print(str(e)) return _build_response(msg=json.dumps({ 'msg': 'Could not upload asset', 'error_message': error_mapping(str(e)), 'err': str(e) }), status=400) Service_Class.reset_cid(client) # No adgroup assignment was requested, asset uploaded successfully if result['status'] == -1: return _build_response(msg=json.dumps({ 'msg': 'Asset Uploaded', 'asset': result['asset'] }), status=200) # successfully uploaded and assigend to all ad groups if result['status'] == 0: return _build_response(msg=json.dumps(result), status=200) # successfully assigend only to some ad groups if result['status'] == 1: return _build_response(msg=json.dumps(result), status=206) # text asset couldn't assign to any ad group - therefore also not uploaded if result['status'] == 3: return _build_response(msg=json.dumps({ 'msg': 'Text asset could not be assigned to any adgroup', 'failures': result['unsuccessfull'] }), status=500) # asset uploaded but didn't assign to any ad groups elif result['status'] == 2: return _build_response(msg=json.dumps({ 'msg': 'could not assign asset', 'asset': result['asset'] }), status=500)
def _text_asset_mutate(data, asset_id, asset_struct): """Handles text asset mutations""" asset_handlers = [] index = 0 # to re-write back to location for entry in asset_struct: if entry['id'] == asset_id: asset_handlers.append({'asset': entry, 'index': index}) index += 1 # if only one of headlines/descriptions entries # exists in asset_struct, create the second one. # if the asset isn't assigned to any adgroup, create both entries # create headline entry only if text's len <= 30 if len(asset_handlers) < 2: new_asset = { 'id': data[0]['asset']['id'], 'type': 'TEXT', 'asset_text': data[0]['asset']['asset_text'], 'adgroups': [] } append = False if len(data[0]['asset']['asset_text']) <= 30: headline_len = True else: headline_len = False if len(asset_handlers) == 1: existing_type = asset_handlers[0]['asset']['text_type'] if existing_type == 'headlines': new_asset['text_type'] = 'descriptions' append = True elif headline_len: new_asset['text_type'] = 'headlines' append = True if append: asset_handlers.append({'asset': new_asset, 'index': None}) elif len(asset_handlers) == 0: new_asset['text_type'] = 'descriptions' asset_handlers.append({'asset': new_asset, 'index': None}) if headline_len: new_asset_second = copy.copy(new_asset) new_asset_second['adgroups'] = [] new_asset_second['text_type'] = 'headlines' asset_handlers.append({ 'asset': new_asset_second, 'index': None }) successeful_assign = [] failed_assign = [] for item in data: account = item['account'] adgroup = item['adgroup'] action = item['action'] asset = item['asset'] text_type_to_assign = item['asset']['text_type_to_assign'] try: mutation = mutate_ad(client, account, adgroup, asset, action, text_type_to_assign) except Exception as e: failed_assign.append({ 'adgroup': adgroup, 'error_message': error_mapping(str(e)), 'err': str(e) }) mutation = 'failed' logging.error('could not execute mutation on adgroup: ' + str(adgroup) + str(e)) if mutation is None: for obj in asset_handlers: if obj['asset']['text_type'] == text_type_to_assign: obj['asset'] = _asset_ag_update(obj['asset'], adgroup, action) successeful_assign.append(adgroup) Service_Class.reset_cid(client) for obj in asset_handlers: if obj['index']: asset_struct[obj['index']] = obj['asset'] else: asset_struct.append(obj['asset']) with open(asset_to_ag_json_path, 'w') as f: json.dump(asset_struct, f, indent=2) if failed_assign and successeful_assign: status = 206 elif successeful_assign: status = 200 else: status = 500 logging.info('mutate response: msg={} , status={}'.format( str(asset_handlers), index)) # switch to this return and tell Mariam the changed return type. return _build_response(msg=json.dumps([{ 'asset': asset_handlers, 'failures': failed_assign }]), status=status)
def mutate(): """Assign or remove an asset from adgroups. gets a json file with a list of asset, account, adgourp and action. preforms all of the actions one by one. returns a list withthe new asset objects with the changed adgroups list. if its a text asset, returns a list with both 'headlines' and 'descriptions' entries. also changes the asset_to_ag.json file. """ data = request.get_json(force=True) logging.info('Recived mutate request: ' + str(data)) asset_id = data[0]['asset']['id'] asset_type = data[0]['asset']['type'] with open(asset_to_ag_json_path, 'r') as f: asset_struct = json.load(f) # special func for text assets, as they have 2 entries in asset_to_ag.json if asset_type == 'TEXT': return _text_asset_mutate(data, asset_id, asset_struct) asset_handler = {} index = 0 # to re-write back to location for entry in asset_struct: if entry['id'] == asset_id: asset_handler = entry break index += 1 if not asset_handler: asset_handler = data[0]['asset'] asset_handler['adgroups'] = [] index = None failed_assign = [] successeful_assign = [] for item in data: account = item['account'] adgroup = item['adgroup'] action = item['action'] asset = item['asset'] try: mutation = mutate_ad(client, account, adgroup, asset, action) except Exception as e: failed_assign.append({ 'adgroup': adgroup, 'error_message': error_mapping(str(e)), 'err': str(e) }) mutation = 'failed' logging.error('could not execute mutation on adgroup: ' + str(adgroup)) if mutation is None: successeful_assign.append(adgroup) asset_handler = _asset_ag_update(asset_handler, adgroup, action) Service_Class.reset_cid(client) if index: asset_struct[index] = asset_handler else: asset_struct.append(asset_handler) with open(asset_to_ag_json_path, 'w') as f: json.dump(asset_struct, f, indent=2) if failed_assign and successeful_assign: status = 206 elif successeful_assign: status = 200 else: status = 500 logging.info('mutate response: msg={} , status={}'.format( asset_handler, index)) return _build_response(msg=json.dumps([{ 'asset': asset_handler, 'index': index, 'failures': failed_assign }]), status=status)
config_file = yaml.load(f, Loader=yaml.FullLoader) except FileNotFoundError: config_file = {'config_valid': 0} if config_file['config_valid']: setup.set_api_configs() client = adwords.AdWordsClient.LoadFromStorage(CONFIG_PATH / 'googleads.yaml') googleads_client = GoogleAdsClient.load_from_storage(CONFIG_PATH / 'google-ads.yaml') try: structure.create_mcc_struct(googleads_client, account_struct_json_path, asset_to_ag_json_path) except Exception as e: logging.exception('Error when trying to create struct') Service_Class.reset_cid(client) @server.route('/') def upload_frontend(): return render_template('index.html') @server.route('/config/', methods=['GET']) def get_configs(): """return all config parameters""" try: with open(CONFIG_FILE_PATH, 'r') as fi: config = yaml.load(fi, Loader=yaml.FullLoader) except FileNotFoundError: config = {
except FileNotFoundError: config_file = {'config_valid': 0} if config_file['config_valid']: try: setup.download_file_from_gcs('account_struct.json', account_struct_json_path) setup.download_file_from_gcs('asset_to_ag.json', asset_to_ag_json_path) if CLOUD_VERSION and Path(account_struct_json_path).exists(): get_global_googleads_client() # to initialize googleads.yaml file logging.info('Skipping structure creation on startup, since this is the cloud version') else: structure.create_mcc_struct(get_global_googleads_client(), account_struct_json_path, asset_to_ag_json_path) except Exception as e: logging.exception('Error when trying to create struct') Service_Class.reset_cid(get_global_adwords_client()) @app.route('/') def upload_frontend(): return render_template('index.html') @app.route('/config/', methods=['GET']) def get_configs(): """return all config parameters""" try: setup.download_file_from_gcs(CONFIG_FILE_PATH_GS, CONFIG_FILE_PATH) with open(CONFIG_FILE_PATH, 'r') as fi: config = yaml.load(fi, Loader=yaml.FullLoader) except FileNotFoundError: