Posts tagged with python-3.x

I recently got my new Macbook Pro and installed 3.8.6 with pyenv and created a virtualenv for my project. When I try to run the tests for this project i get the following error about a module not found by a dependency (abridged). The the files are all there, ad_manager is a file right next to the __init__.py file that is importing it. This was not a problem on my old machine.

echo $PYTHONPATH is empty

python -c 'import sys; print(sys.path)' -> ['', '/Users/derek/.pyenv/versions/3.8.6/lib/python38.zip', '/Users/derek/.pyenv/versions/3.8.6/lib/python3.8', '/Users/derek/.pyenv/versions/3.8.6/lib/python3.8/lib-dynload', '/Users/derek/.pyenv/versions/VIRTUAL_ENV_3.8.6/lib/python3.8/site-packages']

Traceback (most recent call last):   File "/Users/derek/dev/PATH_TO_PROJECT/tests/../src/services/adwords_service.py", line 18, in <module>     from googleads import oauth2, adwords   File "/Users/derek/.pyenv/versions/VIRTUAL_ENV_3.8.6/lib/python3.8/site-packages/googleads/__init__.py", line 17, in <module>     from ad_manager import AdManagerClient ModuleNotFoundError: No module named 'ad_manager' 

I want to remove a few ads in one server request. What is the difference between:

  • batch-processing (only async?)
  • bulk mutates (only sync, shorter code?)

I have tried both ways and got errors:

  1. batch-processing

I've tried to follow this post about batch-processing to create an async batch job for removing multiple ads. It was sent to the server, but I didn't see the sent ad ids were deleted.

Do I miss anything?

class ServiceWrapper:     """Wraps GoogleAdsService API request"""     # public properties ...     def __init__(self, client, customer_id):         self._client = client         self._ga_service = client.get_service("GoogleAdsService")         self._ad_group_ad_service = client.get_service("AdGroupAdService")         self._batch_job_service = client.get_service("BatchJobService")         self._customer_id = customer_id         self._batch_job_operation = self._create_batch_job_operation(client)         self._batch_job_resource_name = self._create_batch_job(self._batch_job_service, customer_id,                                                                self._batch_job_operation)     def _create_batch_job_operation(self, client):         """Created a BatchJobOperation and sets an empty BatchJob instance to         the "create" property in order to tell the Google Ads API that we're         creating a new BatchJob.         Args:             client: an initialized GoogleAdsClient instance.         Returns: a BatchJobOperation with a BatchJob instance set in the "create"             property.         """         batch_job_operation = client.get_type("BatchJobOperation")         batch_job = client.get_type("BatchJob")         client.copy_from(batch_job_operation.create, batch_job)         return batch_job_operation     def _create_batch_job(self, batch_job_service, customer_id, batch_job_operation):         """Creates a batch job for the specified customer ID.         Args:             batch_job_service: an instance of the BatchJobService message class.             customer_id: a str of a customer ID.             batch_job_operation: a BatchJobOperation instance set to "create"         Returns: a str of a resource name for a batch job.         """         try:             response = batch_job_service.mutate_batch_job(                 customer_id=customer_id, operation=batch_job_operation             )             resource_name = response.result.resource_name             print(f'Created a batch job with resource name "{resource_name}"')             return resource_name         except GoogleAdsException as exception:             handle_googleads_exception(exception)     def add_all_batch_job_operations(self, batch_job_service, operations, resource_name):         """Adds all mutate operations to the batch job.         As this is the first time for this batch job, we pass null as a sequence         token. The response will contain the next sequence token that we can use         to upload more operations in the future.         Args:             batch_job_service: an instance of the BatchJobService message class.             operations: a list of a mutate operations.             resource_name: a str of a resource name for a batch job.         """         try:             response = batch_job_service.add_batch_job_operations(                 resource_name=resource_name,                 sequence_token=None,                 mutate_operations=operations,             )             print(                 f"{response.total_operations} mutate operations have been "                 "added so far."             )             # You can use this next sequence token for calling             # add_batch_job_operations() next time.             print(                 "Next sequence token for adding next operations is "                 f"{response.next_sequence_token}"             )         except GoogleAdsException as exception:             handle_googleads_exception(exception) def remove_disapproved_ads_for_account(account):     """Remove all disapproved ads for a given customer id"""     ad_removal_operations = []         for row in rows:                     ad_removal_operations.append(                         build_removal_operation(customer_id, ad_json["ad_group_id"],          if len(ad_removal_operations) > 0:         remove_ads(ad_removal_operations)         #serviceWrapper.mutate(customer_id, [mutate_operation1, mutate_operation2]) def build_removal_operation(customer_id, ad_group_id, ad_id):     """Removes the specified ad"""     resource_name = serviceWrapper.ad_group_ad_service.ad_group_ad_path(         customer_id, ad_group_id, ad_id     )     ad_group_ad_operation = serviceWrapper.client.get_type("AdGroupAdOperation")     ad_group_ad_operation.remove = resource_name     return ad_group_ad_operation async def remove_ads(removal_operations):     """Removes the specified ad"""     serviceWrapper.add_all_batch_job_operations(serviceWrapper.batch_job_service, removal_operations,                                                 serviceWrapper.batch_job_resource_name)     operations_response = _run_batch_job(serviceWrapper.batch_job_service, serviceWrapper.batch_job_resource_name)     # Create an asyncio.Event instance to control execution during the     # asyncronous steps in _poll_batch_job. Note that this is not important     # for polling asyncronously, it simply helps with execution control so we     # can run _fetch_and_print_results after the asyncronous operations have     # completed.     _done_event = asyncio.Event()     _poll_batch_job(operations_response, _done_event)     # Execution will stop here and wait for the asyncronous steps in     # _poll_batch_job to complete before proceeding.     await _done_event.wait()     _fetch_and_print_results(serviceWrapper.client, serviceWrapper.batch_job_service,                              serviceWrapper.batch_job_resource_name) def _run_batch_job(batch_job_service, resource_name):     """Runs the batch job for executing all uploaded mutate operations.     Args:         batch_job_service: an instance of the BatchJobService message class.         resource_name: a str of a resource name for a batch job.     Returns: a google.api_core.operation.Operation instance.     """     try:         response = batch_job_service.run_batch_job(resource_name=resource_name)         print(             f'Batch job with resource name "{resource_name}" has been '             "executed."         )         return response     except GoogleAdsException as exception:         handle_googleads_exception(exception) def _poll_batch_job(operations_response, event):     """Polls the server until the batch job execution finishes.     Sets the initial poll delay time and the total time to wait before time-out.     Args:         operations_response: a google.api_core.operation.Operation instance.         event: an instance of asyncio.Event to invoke once the operations have             completed, alerting the awaiting calling code that it can proceed.     """     loop = asyncio.get_event_loop()     def _done_callback(future):         # The operations_response object will call callbacks from a daemon         # thread so we must use a threadsafe method of setting the event here         # otherwise it will not trigger the awaiting code.         loop.call_soon_threadsafe(event.set)     # operations_response represents a Long-Running Operation or LRO. The class     # provides an interface for polling the API to check when the operation is     # complete. Below we use the asynchronous interface, but there's also a     # synchronous interface that uses the Operation.result method.     # See: https://googleapis.dev/python/google-api-core/latest/operation.html     operations_response.add_done_callback(_done_callback) def _fetch_and_print_results(client, batch_job_service, resource_name):     """Prints all the results from running the batch job.     Args:         client: an initialized GoogleAdsClient instance.         batch_job_service: an instance of the BatchJobService message class.         resource_name: a str of a resource name for a batch job.     """     print(         f'Batch job with resource name "{resource_name}" has finished. '         "Now, printing its results..."     )     list_results_request = client.get_type("ListBatchJobResultsRequest")     list_results_request.resource_name = resource_name     list_results_request.page_size = BULK_REMOVE_PAGE_SIZE     # Gets all the results from running batch job and prints their information.     batch_job_results = batch_job_service.list_batch_job_results(         request=list_results_request     )     for batch_job_result in batch_job_results:         status = batch_job_result.status.message         status = status if status else "N/A"         result = batch_job_result.mutate_operation_response         result = result or "N/A"         print(             f"Batch job #{batch_job_result.operation_index} "             f'has a status "{status}" and response type "{result}"'         )         # [END add_complete_campaigns_using_batch_job_4] 
  1. Bulk Mutates

If I choose to follow this post about Bulk Mutates, and create a sync batch, I get an undefined symbol :Mutate how can I fix this? Or make this code work?

class ServiceWrapper:     """Wraps GoogleAdsService API request"""     # public properties ...     def __init__(self, client, customer_id):         self._client = client         self._ga_service = client.get_service("GoogleAdsService")         self._ad_group_ad_service = client.get_service("AdGroupAdService")         self._batch_job_service = client.get_service("BatchJobService")         self._customer_id = customer_id         self._batch_job_operation = self._create_batch_job_operation(client)         self._batch_job_resource_name = self._create_batch_job(self._batch_job_service, customer_id,                                                                self._batch_job_operation)          def build_removal_operation_sync(customer_id, ad_group_id, ad_id):      mutate_operation1 = serviceWrapper.client.operation(:Mutate)      """Removes the specified ad"""      resource_name = serviceWrapper.ad_group_ad_service.ad_group_ad_path(          customer_id, ad_group_id, ad_id      )      ad_group_ad_operation = serviceWrapper.client.get_type("AdGroupAdOperation")      ad_group_ad_operation.remove = resource_name      mutate_operation1.ad_group_ad_operation = campaign_operation 

I'm trying to get reports for my google ads and display them on a dashboard for easier viewing and monitoring.

I've gone through the whole process to authenticate my account. I got all the keys needed. Everything works up until the query is run. See code below copied from Google ads API examples

#!/usr/bin/env python # Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #     https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """This example illustrates how to get all campaigns. To add campaigns, run add_campaigns.py. """ import argparse import sys from google.ads.google_ads.client import GoogleAdsClient from google.ads.google_ads.errors import GoogleAdsException def main(client, customer_id):     ga_service = client.get_service("GoogleAdsService", version="v6")     query = """         SELECT campaign.id, campaign.name         FROM campaign         ORDER BY campaign.id"""     # Issues a search request using streaming.     response = ga_service.search_stream(customer_id, query=query)     try:         for batch in response:             for row in batch.results:                 print(                     f"Campaign with ID {row.campaign.id} and name "                     f'"{row.campaign.name}" was found.'                 )     except GoogleAdsException as ex:         print(             f'Request with ID "{ex.request_id}" failed with status '             f'"{ex.error.code().name}" and includes the following errors:'         )         for error in ex.failure.errors:             print(f'\tError with message "{error.message}".')             if error.location:                 for field_path_element in error.location.field_path_elements:                     print(f"\t\tOn field: {field_path_element.field_name}")         sys.exit(1) if __name__ == "__main__":     # GoogleAdsClient will read the google-ads.yaml configuration file in the     # home directory if none is specified.     google_ads_client = GoogleAdsClient.load_from_storage()     parser = argparse.ArgumentParser(         description="Lists all campaigns for specified customer."     )     # The following argument(s) should be provided to run the example.     parser.add_argument(         "-c",         "--customer_id",         type=str,         required=True,         help="The Google Ads customer ID.",     )     args = parser.parse_args()     main(google_ads_client, args.customer_id) 

Nothing gets printed to my console. When I print out print(response) i get <google.api_core.grpc_helpers._StreamingResponseIterator object at 0x7fb7dcaaf3d0>

I get no errors, tracebacks nothing. This is what my console looks like (hiding customer_id):

I've set up the whole Google Ads API according to this manual here: https://developers.google.com/google-ads/api/docs/client-libs/python/oauth-desktop?hl=en

I got my refresh token and put it in my google-ads.yaml file which looks exactly like this: https://github.com/googleads/google-ads-python/blob/master/google-ads.yaml. I also put my client_id, developer_token and client_secret at the corresponding spots in the file.

Everything worked out and I could for example ask for all campaigns: https://developers.google.com/google-ads/api/docs/samples/get-campaigns#python. Also I could use other services of the API.

Each time I use the API I access it via the client using the following code:

from google.ads.google_ads.client import GoogleAdsClient client = GoogleAdsClient.load_from_storage(path_to_google_ads_yaml_file) 

As I already said it worked.

A few days later I tried to connect again and I got the following error:

 “error”: “invalid_grant”, “error_description”: “Token has been expired or revoked.” 

So I did the process of getting a new refresh token again (https://developers.google.com/google-ads/api/docs/client-libs/python/oauth-desktop?hl=en) such that everything worked out again.

One day later I got the same error again. So my question is: What is the purpose of the refresh token? I thought it allows me to access the API without ever having to ask for a new token or refresh token!?

Now, I again tried to connect to the API once more (although it didn't work last time) and now I got an even worse and new error:

'invalid_grant: Bad Request', '{\n  "error": "invalid_grant",\n  "error_description": "Bad Request"\n}' 

So things are getting even more complicated...

I am trying to pull report from google ad manager using API

  report_job = {   'reportQuery': {       'dimensions': ['AD_EXCHANGE_DATE', 'AD_EXCHANGE_COUNTRY_NAME'],       'columns': ['AD_EXCHANGE_AD_REQUESTS', 'AD_EXCHANGE_IMPRESSIONS',                   'AD_EXCHANGE_ESTIMATED_REVENUE','AD_EXCHANGE_AD_ECPM'],       'dateRangeType': 'LAST_WEEK',       'timeZoneType': 'AD_EXCHANGE',  # Run in pacific time       'adxReportCurrency': 'USD'   }   }

I suspect the downloaded report has very wired ECPM and Revenue values. as below

Please help if i am doing something wrong, as the ECPM has to be very low and the revenue as well.