Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 9 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
* 29.2.0
- Google Ads API v23_1 release.
- Update Google Ads API v20, v21, and v22 to include EU political advertising changes.
- Add upload_video example.
- Add text guidelines to performance max example.

* 29.1.0
- Add configurable metadata header for ads api assistant.

* 29.1.0
- Add configurable metadata header for ads api assistant.

Expand Down
37 changes: 29 additions & 8 deletions examples/advanced_operations/add_performance_max_campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
shopping_ads/add_performance_max_retail_campaign.py
"""


import argparse
from datetime import datetime, timedelta
import sys
Expand Down Expand Up @@ -83,7 +82,6 @@
MutateOperationResponse,
)


# We specify temporary IDs that are specific to a single mutate request.
# Temporary IDs are always negative and unique within one mutate request.
#
Expand Down Expand Up @@ -318,8 +316,24 @@ def create_performance_max_campaign_operation(
)

# Optional fields
campaign.start_date_time = (datetime.now() + timedelta(1)).strftime("%Y%m%d 00:00:00")
campaign.end_date_time = (datetime.now() + timedelta(365)).strftime("%Y%m%d 23:59:59")
campaign.start_date_time = (datetime.now() + timedelta(1)).strftime(
"%Y%m%d 00:00:00"
)
campaign.end_date_time = (datetime.now() + timedelta(365)).strftime(
"%Y%m%d 23:59:59"
)

# [START add_performance_max_text_guidelines]
campaign.text_guidelines.term_exclusions = ["cheap", "free"]
messaging_restriction = campaign.MessagingRestriction()
messaging_restriction.restriction_text = "Don't mention competitor names"
messaging_restriction.restriction_type = (
client.enums.MessagingRestrictionTypeEnum.RESTRICTION_BASED_EXCLUSION
)
campaign.text_guidelines.messaging_restrictions.append(
messaging_restriction
)
# [END add_performance_max_text_guidelines]

# [START add_pmax_asset_automation_settings]
# Configures the optional opt-in/out status for asset automation settings.
Expand All @@ -328,11 +342,17 @@ def create_performance_max_campaign_operation(
client.enums.AssetAutomationTypeEnum.FINAL_URL_EXPANSION_TEXT_ASSET_AUTOMATION,
client.enums.AssetAutomationTypeEnum.TEXT_ASSET_AUTOMATION,
client.enums.AssetAutomationTypeEnum.GENERATE_ENHANCED_YOUTUBE_VIDEOS,
client.enums.AssetAutomationTypeEnum.GENERATE_IMAGE_ENHANCEMENT
client.enums.AssetAutomationTypeEnum.GENERATE_IMAGE_ENHANCEMENT,
]:
asset_automattion_setting: Campaign.AssetAutomationSetting = client.get_type("Campaign").AssetAutomationSetting()
asset_automattion_setting.asset_automation_type = asset_automation_type_enum
asset_automattion_setting.asset_automation_status = client.enums.AssetAutomationStatusEnum.OPTED_IN
asset_automattion_setting: Campaign.AssetAutomationSetting = (
client.get_type("Campaign").AssetAutomationSetting()
)
asset_automattion_setting.asset_automation_type = (
asset_automation_type_enum
)
asset_automattion_setting.asset_automation_status = (
client.enums.AssetAutomationStatusEnum.OPTED_IN
)
campaign.asset_automation_settings.append(asset_automattion_setting)
# [END add_pmax_asset_automation_settings]

Expand Down Expand Up @@ -712,6 +732,7 @@ def create_and_link_image_asset(
return operations
# [END add_performance_max_campaign_8]


# [START create_and_link_brand_assets]
def create_and_link_brand_assets(
client: GoogleAdsClient,
Expand Down
164 changes: 164 additions & 0 deletions examples/advanced_operations/upload_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/env python
# Copyright 2026s 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 upload videos to YouTube."""

import argparse
import itertools
import os
import sys
import logging
from typing import Iterator, Iterable, List, MutableSequence

import google.auth
from google.auth.credentials import Credentials
from google.auth import impersonated_credentials

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
from google.ads.googleads.v23.services.services.google_ads_service import (
GoogleAdsServiceClient,
)
from google.ads.googleads.v23.services.types.google_ads_service import (
SearchGoogleAdsStreamResponse,
GoogleAdsRow,
)

from google.ads.googleads.v23.services.services.you_tube_video_upload_service.client import (
YouTubeVideoUploadServiceClient,
)
from google.ads.googleads.v23.services.types import youtube_video_upload_service
from google.ads.googleads.v23.services.types.youtube_video_upload_service import (
CreateYouTubeVideoUploadRequest,
CreateYouTubeVideoUploadResponse,
UpdateYouTubeVideoUploadRequest,
UpdateYouTubeVideoUploadResponse,
RemoveYouTubeVideoUploadRequest,
RemoveYouTubeVideoUploadResponse,
)

from google.protobuf import field_mask_pb2
from google.ads.googleads.v23.resources.types import youtube_video_upload


def main(client: GoogleAdsClient, customer_id: str, video_file_path: str) -> None:
"""The main method that uploads a video and retrieves its state.

Args:
client: an initialized GoogleAdsClient instance.
customer_id: a client customer ID.
video_file_path: the absolute path to a video file on your machine.
"""

# [START upload_video_1]
yt_service: YouTubeVideoUploadServiceClient = client.get_service(
"YouTubeVideoUploadService"
)

create_upload_request: CreateYouTubeVideoUploadRequest = (
youtube_video_upload_service.CreateYouTubeVideoUploadRequest()
)
create_upload_request.customer_id = customer_id
create_upload_request.you_tube_video_upload.video_title = "Test Video"
create_upload_request.you_tube_video_upload.video_description = (
"Test Video Description"
)
create_upload_request.you_tube_video_upload.video_privacy = (
client.enums.YouTubeVideoPrivacyEnum.UNLISTED
)

video_upload_resource_name: str
with open(video_file_path, "rb") as stream:
response: CreateYouTubeVideoUploadResponse = (
yt_service.create_you_tube_video_upload(
stream=stream,
request=create_upload_request,
retry=None,
)
)
print(f"Created YouTube video upload: {response.resource_name}")
# [END upload_video_1]

# [START upload_video_3]
# Retrieve the metadata of the newly uploaded video.
query: str = f"""
SELECT
you_tube_video_upload.resource_name,
you_tube_video_upload.video_id,
you_tube_video_upload.state
FROM you_tube_video_upload
WHERE you_tube_video_upload.resource_name = '{video_upload_resource_name}'"""

ga_service: GoogleAdsServiceClient = client.get_service("GoogleAdsService")
stream: Iterator[SearchGoogleAdsStreamResponse] = ga_service.search_stream(
customer_id=customer_id, query=query
)

for row in itertools.chain.from_iterable(batch.results for batch in stream):
video = row.you_tube_video_upload
print(
f"Video with ID {row.you_tube_video_upload.video_id} was found in state {row.you_tube_video_upload.state}."
)
# [END upload_video_3]


if __name__ == "__main__":
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.",
)
parser.add_argument(
"-v",
"--video_file_path",
type=str,
required=True,
help="The path to a video file to upload to YouTube.",
)
args: argparse.Namespace = parser.parse_args()

# GoogleAdsClient will read the google-ads.yaml configuration file in the
# home directory if none is specified.
googleads_client: GoogleAdsClient = GoogleAdsClient.load_from_storage(version="v23")
try:
main(
googleads_client,
customer_id=args.customer_id,
video_file_path=args.video_file_path,
)
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:'
)
if hasattr(ex.error, "_response"):
raw_res = getattr(ex.error, "_response")
print(f"\n--- Full Response (Reflected from Proxy) ---")
print(f"Status Code: {raw_res.status_code}")
print(f"Headers: {raw_res.headers}")
print(f"Body: {raw_res.text}")
print(f"-------------------------------------------\n")

for error in ex.failure.errors:
print(f'\tError with message "{error.message}".')
if hasattr(error, "location") and 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)
2 changes: 1 addition & 1 deletion google/ads/googleads/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import google.ads.googleads.errors
import google.ads.googleads.util

VERSION = "29.1.0"
VERSION = "29.2.0"

# Checks if the current runtime is Python 3.9.
if sys.version_info.major == 3 and sys.version_info.minor <= 9:
Expand Down
24 changes: 24 additions & 0 deletions google/ads/googleads/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,18 @@ def get_service(
channel=channel, client_info=_CLIENT_INFO
)

if name == "YouTubeVideoUploadService":
# YouTubeVideoUploadService uses REST, so we cannot pass the credentials inside the gRPC
# channel; we need to pass them explicitly.
return service_client_class(
transport=service_transport,
credentials=self.credentials,
developer_token=self.developer_token,
login_customer_id=self.login_customer_id,
linked_customer_id=self.linked_customer_id,
use_cloud_org_for_api_access=self.use_cloud_org_for_api_access
)

return service_client_class(transport=service_transport)

channel: grpc.Channel = service_transport_class.create_channel(
Expand Down Expand Up @@ -504,6 +516,18 @@ def get_service(
channel=channel, client_info=_CLIENT_INFO
)

if name == "YouTubeVideoUploadService":
# YouTubeVideoUploadService uses REST, so we cannot pass the credentials inside the gRPC
# channel; we need to pass them explicitly.
return service_client_class(
transport=service_transport,
credentials=self.credentials,
developer_token=self.developer_token,
login_customer_id=self.login_customer_id,
linked_customer_id=self.linked_customer_id,
use_cloud_org_for_api_access=self.use_cloud_org_for_api_access
)

return service_client_class(transport=service_transport)

def get_type(self, name: str, version: str = _DEFAULT_VERSION) -> Union[ProtoPlusMessageType, ProtobufMessageType]:
Expand Down
Loading
Loading