google-ads-cli 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,150 @@
1
+ """`gads auth login|status` -- einmalige OAuth2-Einrichtung fuer nicht-interaktive Nutzung."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from googleadscli import client_factory, config, formatting
8
+ from googleadscli.commands._common import run_guarded
9
+
10
+ app = typer.Typer(no_args_is_help=True, help="OAuth2-Einrichtung und Zugangspruefung")
11
+
12
+ SCOPES = ["https://www.googleapis.com/auth/adwords"]
13
+
14
+
15
+ @app.command("login")
16
+ def login(
17
+ ctx: typer.Context,
18
+ client_id: str = typer.Option(..., "--client-id", envvar="GOOGLE_ADS_CLIENT_ID"),
19
+ client_secret: str = typer.Option(..., "--client-secret", envvar="GOOGLE_ADS_CLIENT_SECRET"),
20
+ developer_token: str = typer.Option(
21
+ None,
22
+ "--developer-token",
23
+ envvar="GOOGLE_ADS_DEVELOPER_TOKEN",
24
+ help="Optional: nicht noetig bei Cloud-managed Access ohne klassischen Developer Token",
25
+ ),
26
+ login_customer_id: str = typer.Option(
27
+ None, "--login-customer-id", help="Optionale MCC-CID, die standardmaessig als login-customer-id gesetzt wird"
28
+ ),
29
+ no_browser: bool = typer.Option(
30
+ False, "--no-browser", help="Auth-URL ausgeben statt Browser zu oeffnen (fuer SSH/Remote-Sessions)"
31
+ ),
32
+ ) -> None:
33
+ """Fuehrt einmalig den interaktiven OAuth2-Consent-Flow aus und speichert den Refresh Token."""
34
+
35
+ def _run() -> None:
36
+ from google_auth_oauthlib.flow import InstalledAppFlow
37
+
38
+ client_config = {
39
+ "installed": {
40
+ "client_id": client_id,
41
+ "client_secret": client_secret,
42
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
43
+ "token_uri": "https://oauth2.googleapis.com/token",
44
+ "redirect_uris": ["http://localhost"],
45
+ }
46
+ }
47
+ flow = InstalledAppFlow.from_client_config(client_config, scopes=SCOPES)
48
+ credentials = flow.run_local_server(open_browser=not no_browser)
49
+
50
+ data = {
51
+ "client_id": client_id,
52
+ "client_secret": client_secret,
53
+ "refresh_token": credentials.refresh_token,
54
+ }
55
+ if developer_token:
56
+ data["developer_token"] = developer_token
57
+ if login_customer_id:
58
+ data["login_customer_id"] = login_customer_id.replace("-", "")
59
+
60
+ path = config.write_config_file(data)
61
+ formatting.render({"status": "ok", "config_path": str(path)}, fmt=ctx.obj["format"])
62
+
63
+ run_guarded(ctx, _run)
64
+
65
+
66
+ @app.command("use-service-account")
67
+ def use_service_account(
68
+ ctx: typer.Context,
69
+ json_key_file_path: str = typer.Option(
70
+ ...,
71
+ "--json-key-file-path",
72
+ help="Pfad zur Service-Account-JSON-Schluesseldatei aus der Google Cloud Console",
73
+ ),
74
+ developer_token: str = typer.Option(
75
+ None,
76
+ "--developer-token",
77
+ envvar="GOOGLE_ADS_DEVELOPER_TOKEN",
78
+ help="Optional: nicht noetig bei Cloud-managed Access ohne klassischen Developer Token",
79
+ ),
80
+ impersonated_email: str = typer.Option(
81
+ None,
82
+ "--impersonated-email",
83
+ help="Nur fuer Domain-Wide-Delegation: zu impersonierender Workspace-Nutzer. "
84
+ "Nicht noetig, wenn die client_email der Schluesseldatei direkt als Nutzer auf dem "
85
+ "Google-Ads-Konto/MCC hinterlegt wurde.",
86
+ ),
87
+ login_customer_id: str = typer.Option(None, "--login-customer-id", help="Optionale MCC-CID"),
88
+ ) -> None:
89
+ """Richtet Service-Account-basierte Authentifizierung ein (kein interaktiver Consent noetig).
90
+
91
+ Voraussetzung: die client_email aus der JSON-Schluesseldatei muss auf dem
92
+ Google-Ads-Konto (oder MCC) als Nutzer mit passendem Zugriffslevel hinterlegt sein
93
+ (Tools & Einstellungen > Zugriff und Sicherheit > Nutzer), es sei denn, es wird
94
+ stattdessen per --impersonated-email eine Domain-Wide-Delegation genutzt.
95
+ """
96
+
97
+ def _run() -> None:
98
+ import json as _json
99
+ from pathlib import Path
100
+
101
+ key_path = Path(json_key_file_path).expanduser().resolve()
102
+ if not key_path.exists():
103
+ raise config.ConfigError(f"Service-Account-Schluesseldatei nicht gefunden: {key_path}")
104
+ key_data = _json.loads(key_path.read_text(encoding="utf-8"))
105
+ if key_data.get("type") != "service_account":
106
+ raise config.ConfigError(
107
+ f"Datei {key_path} ist kein Service-Account-Schluessel (type={key_data.get('type')!r})."
108
+ )
109
+
110
+ data: dict = {"json_key_file_path": str(key_path)}
111
+ if developer_token:
112
+ data["developer_token"] = developer_token
113
+ if impersonated_email:
114
+ data["impersonated_email"] = impersonated_email
115
+ if login_customer_id:
116
+ data["login_customer_id"] = login_customer_id.replace("-", "")
117
+
118
+ path = config.write_config_file(data)
119
+ formatting.render(
120
+ {
121
+ "status": "ok",
122
+ "config_path": str(path),
123
+ "service_account_email": key_data.get("client_email"),
124
+ "hint": "Stelle sicher, dass diese client_email als Nutzer auf dem Google-Ads-Konto "
125
+ "hinterlegt ist, falls --impersonated-email nicht gesetzt wurde.",
126
+ },
127
+ fmt=ctx.obj["format"],
128
+ )
129
+
130
+ run_guarded(ctx, _run)
131
+
132
+
133
+ @app.command("status")
134
+ def status(ctx: typer.Context) -> None:
135
+ """Prueft die aktuelle Konfiguration gegen die echte API (list_accessible_customers)."""
136
+
137
+ def _run() -> None:
138
+ client = client_factory.build_client(
139
+ config_path=ctx.obj["config_path"],
140
+ cli_overrides=ctx.obj["cli_overrides"],
141
+ version=ctx.obj["version"],
142
+ )
143
+ service = client.get_service("CustomerService", version=ctx.obj["version"])
144
+ response = service.list_accessible_customers()
145
+ formatting.render(
146
+ {"status": "ok", "accessible_customers": list(response.resource_names)},
147
+ fmt=ctx.obj["format"],
148
+ )
149
+
150
+ run_guarded(ctx, _run)
@@ -0,0 +1,76 @@
1
+ """`gads call <Service> <Methode>` -- generischer Fallback fuer jede API-Methode.
2
+
3
+ Deckt alles ab, was kein regulaerer `mutate_*`-Aufruf ist: BatchJobService,
4
+ ConversionUploadService, OfflineUserDataJobService, KeywordPlanService,
5
+ ReachPlanService, GoogleAdsFieldService, CustomerService, Long-Running-
6
+ Operations, etc.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import typer
12
+
13
+ from googleadscli import formatting, proto_bridge
14
+ from googleadscli.commands import _common
15
+ from googleadscli.utils import load_json_arg
16
+
17
+
18
+ def register(app: typer.Typer) -> None:
19
+ @app.command("call")
20
+ def call_cmd(
21
+ ctx: typer.Context,
22
+ service: str = typer.Argument(..., help="Service-Name, z.B. 'BatchJobService'"),
23
+ method: str = typer.Argument(..., help="Methodenname (snake_case), z.B. 'mutate' oder 'run_batch_job'"),
24
+ request_json: str = typer.Option(
25
+ "{}", "--request-json", "-r", help="JSON-Objekt als Request-Payload; mit '@pfad.json' aus Datei laden"
26
+ ),
27
+ no_wait: bool = typer.Option(
28
+ False, "--no-wait", help="Bei Long-Running-Operations nicht auf das Ergebnis warten"
29
+ ),
30
+ timeout: float = typer.Option(
31
+ None, "--timeout", help="Timeout in Sekunden beim Warten auf eine Long-Running-Operation"
32
+ ),
33
+ ) -> None:
34
+ """Ruft eine beliebige Service-Methode generisch auf (voller API-Fallback)."""
35
+
36
+ def _run() -> None:
37
+ payload = load_json_arg(request_json)
38
+ if payload is not None and not isinstance(payload, dict):
39
+ raise ValueError("--request-json muss ein JSON-Objekt sein.")
40
+
41
+ client = _common.build_client(ctx)
42
+ result = proto_bridge.invoke_call(
43
+ client,
44
+ service,
45
+ method,
46
+ payload,
47
+ wait_for_operation=not no_wait,
48
+ operation_timeout=timeout,
49
+ version=ctx.obj["version"],
50
+ )
51
+ formatting.render(result, fmt=ctx.obj["format"])
52
+
53
+ _common.run_guarded(ctx, _run)
54
+
55
+ @app.command("list-services")
56
+ def list_services_cmd(ctx: typer.Context) -> None:
57
+ """Listet alle verfuegbaren Service-Namen der aktiven API-Version."""
58
+
59
+ def _run() -> None:
60
+ names = proto_bridge.list_service_names(version=ctx.obj["version"])
61
+ formatting.render(names, fmt=ctx.obj["format"])
62
+
63
+ _common.run_guarded(ctx, _run)
64
+
65
+ @app.command("list-methods")
66
+ def list_methods_cmd(
67
+ ctx: typer.Context, service: str = typer.Argument(..., help="Service-Name, z.B. 'CampaignService'")
68
+ ) -> None:
69
+ """Listet alle aufrufbaren Methoden eines Service auf."""
70
+
71
+ def _run() -> None:
72
+ client = _common.build_client(ctx)
73
+ methods = proto_bridge.list_service_methods(client, service, version=ctx.obj["version"])
74
+ formatting.render(methods, fmt=ctx.obj["format"])
75
+
76
+ _common.run_guarded(ctx, _run)
@@ -0,0 +1,94 @@
1
+ """`gads fields <resource>` -- Feld-Metadaten der Google Ads API nachschlagen.
2
+
3
+ Duenner Komfort-Wrapper um GoogleAdsFieldService.SearchGoogleAdsFields (eine
4
+ GAQL-aehnliche Mini-Query-Sprache gegen den globalen Feld-Katalog der API,
5
+ kein Kundenkonto noetig). Beantwortet Fragen wie "welche Felder bietet
6
+ ad_group?" ohne dass man die Mini-Query-Syntax von Hand bauen muss.
7
+
8
+ Hinweis: Diese Metadaten sagen nur, was *selectable*/*filterable*/*sortable*
9
+ per GAQL ist -- nicht, was per `mutate` *schreibbar* oder mit welchem
10
+ advertising_channel_type kompatibel ist. Das laesst sich nur durch Lesen des
11
+ Resource-Protos (siehe README) oder durch tatsaechliches Ausprobieren
12
+ (--dry-run) herausfinden.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import typer
18
+
19
+ from googleadscli import formatting, proto_bridge
20
+ from googleadscli.commands._common import build_client, run_guarded
21
+
22
+ app = typer.Typer(no_args_is_help=True, help="Feld-Metadaten der API nachschlagen (kein Kundenkonto noetig)")
23
+
24
+ _DEFAULT_SELECT = "name, category, data_type, selectable, filterable, sortable, is_repeated, enum_values"
25
+
26
+
27
+ @app.command("list")
28
+ def list_fields(
29
+ ctx: typer.Context,
30
+ resource: str = typer.Argument(
31
+ ..., help="Ressourcen- oder Feldname bzw. -praefix, z.B. 'ad_group' oder 'campaign.network_settings'"
32
+ ),
33
+ category: str = typer.Option(
34
+ None,
35
+ "--category",
36
+ help="Nur diese Kategorie: RESOURCE|ATTRIBUTE|SEGMENT|METRIC (Default: alle)",
37
+ ),
38
+ exact: bool = typer.Option(
39
+ False, "--exact", help="Nur exakten Feldnamen nachschlagen statt Praefix-Suche"
40
+ ),
41
+ ) -> None:
42
+ """Listet Feld-Metadaten (selectable/filterable/sortable/enum_values/...) fuer ein Praefix."""
43
+
44
+ def _run() -> None:
45
+ if exact:
46
+ where = f'name = "{resource}"'
47
+ else:
48
+ # Die Mini-Query-Sprache von GoogleAdsFieldService kennt kein OR,
49
+ # daher listet dies nur die Kindfelder (Praefix); der exakte
50
+ # Ressourcen-Eintrag selbst laesst sich per 'gads fields show' holen.
51
+ where = f'name LIKE "{resource}.%"'
52
+ if category:
53
+ where += f' AND category = "{category.upper()}"'
54
+
55
+ query = f"SELECT {_DEFAULT_SELECT} WHERE {where}"
56
+
57
+ client = build_client(ctx)
58
+ rows = proto_bridge.invoke_call(
59
+ client,
60
+ "GoogleAdsFieldService",
61
+ "search_google_ads_fields",
62
+ {"query": query},
63
+ version=ctx.obj["version"],
64
+ )
65
+ formatting.render(rows, fmt=ctx.obj["format"])
66
+
67
+ run_guarded(ctx, _run)
68
+
69
+
70
+ @app.command("show")
71
+ def show_field(
72
+ ctx: typer.Context,
73
+ name: str = typer.Argument(..., help="Exakter Feld- oder Ressourcenname, z.B. 'ad_group.status'"),
74
+ ) -> None:
75
+ """Zeigt die vollstaendigen Metadaten (inkl. selectable_with) fuer genau einen Feld-/Ressourcennamen."""
76
+
77
+ def _run() -> None:
78
+ client = build_client(ctx)
79
+ rows = proto_bridge.invoke_call(
80
+ client,
81
+ "GoogleAdsFieldService",
82
+ "search_google_ads_fields",
83
+ {
84
+ "query": (
85
+ "SELECT name, category, data_type, selectable, filterable, sortable, "
86
+ "is_repeated, enum_values, selectable_with, attribute_resources, metrics, segments "
87
+ f'WHERE name = "{name}"'
88
+ )
89
+ },
90
+ version=ctx.obj["version"],
91
+ )
92
+ formatting.render(rows[0] if rows else {}, fmt=ctx.obj["format"])
93
+
94
+ run_guarded(ctx, _run)
@@ -0,0 +1,18 @@
1
+ """Komfortbefehle fuer haeufige Workflows -- duenne Wrapper ueber `mutate`/`call`.
2
+
3
+ Vollstaendigkeit ist bereits durch die generischen Basisbefehle (query/mutate/
4
+ call) gegeben; diese Befehle dienen nur der Ergonomie.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import typer
10
+
11
+ from googleadscli.commands.highlevel import ad, ad_group, budget, campaign, keyword
12
+
13
+ app = typer.Typer(no_args_is_help=True, help="Komfortbefehle fuer haeufige Workflows")
14
+ app.add_typer(budget.app, name="budget")
15
+ app.add_typer(campaign.app, name="campaign")
16
+ app.add_typer(ad_group.app, name="ad-group")
17
+ app.add_typer(keyword.app, name="keyword")
18
+ app.add_typer(ad.app, name="ad")
@@ -0,0 +1,49 @@
1
+ """`gads hl ad create-responsive-search-ad` -- Responsive Search Ad anlegen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from googleadscli import formatting, proto_bridge
8
+ from googleadscli.commands import _common
9
+ from googleadscli.utils import normalize_customer_id
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Anzeigen anlegen/verwalten")
12
+
13
+
14
+ @app.command("create-responsive-search-ad")
15
+ def create_responsive_search_ad(
16
+ ctx: typer.Context,
17
+ customer_id: str = typer.Option(..., "--customer-id", "-c"),
18
+ ad_group_resource_name: str = typer.Option(..., "--ad-group"),
19
+ final_url: str = typer.Option(..., "--final-url"),
20
+ headline: list[str] = typer.Option(..., "--headline", help="Mehrfach angebbar, min. 3 empfohlen"),
21
+ description: list[str] = typer.Option(..., "--description", help="Mehrfach angebbar, min. 2 empfohlen"),
22
+ status: str = typer.Option("PAUSED", "--status"),
23
+ dry_run: bool = typer.Option(False, "--dry-run"),
24
+ ) -> None:
25
+ def _run() -> None:
26
+ client = _common.build_client(ctx)
27
+ create_payload = {
28
+ "ad_group": ad_group_resource_name,
29
+ "status": status,
30
+ "ad": {
31
+ "final_urls": [final_url],
32
+ "responsive_search_ad": {
33
+ "headlines": [{"text": h} for h in headline],
34
+ "descriptions": [{"text": d} for d in description],
35
+ },
36
+ },
37
+ }
38
+ operations = [{"create": create_payload}]
39
+ result = proto_bridge.run_mutate(
40
+ client,
41
+ "ad_group_ad",
42
+ normalize_customer_id(customer_id),
43
+ operations,
44
+ validate_only=dry_run,
45
+ version=ctx.obj["version"],
46
+ )
47
+ formatting.render(result, fmt=ctx.obj["format"])
48
+
49
+ _common.run_guarded(ctx, _run)
@@ -0,0 +1,46 @@
1
+ """`gads hl ad-group create` -- Ad Group anlegen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from googleadscli import formatting, proto_bridge
8
+ from googleadscli.commands import _common
9
+ from googleadscli.utils import normalize_customer_id
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Ad Groups anlegen/verwalten")
12
+
13
+
14
+ @app.command("create")
15
+ def create(
16
+ ctx: typer.Context,
17
+ customer_id: str = typer.Option(..., "--customer-id", "-c"),
18
+ name: str = typer.Option(..., "--name"),
19
+ campaign_resource_name: str = typer.Option(..., "--campaign", help="Resource-Name der Kampagne"),
20
+ status: str = typer.Option("PAUSED", "--status"),
21
+ ad_group_type: str = typer.Option("SEARCH_STANDARD", "--type", help="AdGroupTypeEnum-Wert"),
22
+ dry_run: bool = typer.Option(False, "--dry-run"),
23
+ ) -> None:
24
+ def _run() -> None:
25
+ client = _common.build_client(ctx)
26
+ operations = [
27
+ {
28
+ "create": {
29
+ "name": name,
30
+ "campaign": campaign_resource_name,
31
+ "status": status,
32
+ "type": ad_group_type,
33
+ }
34
+ }
35
+ ]
36
+ result = proto_bridge.run_mutate(
37
+ client,
38
+ "ad_group",
39
+ normalize_customer_id(customer_id),
40
+ operations,
41
+ validate_only=dry_run,
42
+ version=ctx.obj["version"],
43
+ )
44
+ formatting.render(result, fmt=ctx.obj["format"])
45
+
46
+ _common.run_guarded(ctx, _run)
@@ -0,0 +1,46 @@
1
+ """`gads hl budget create` -- CampaignBudget anlegen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from googleadscli import formatting, proto_bridge
8
+ from googleadscli.commands import _common
9
+ from googleadscli.utils import normalize_customer_id
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="CampaignBudget anlegen/verwalten")
12
+
13
+
14
+ @app.command("create")
15
+ def create(
16
+ ctx: typer.Context,
17
+ customer_id: str = typer.Option(..., "--customer-id", "-c"),
18
+ name: str = typer.Option(..., "--name"),
19
+ amount_micros: int = typer.Option(..., "--amount-micros", help="Tagesbudget in Micros (1 EUR = 1_000_000)"),
20
+ delivery_method: str = typer.Option("STANDARD", "--delivery-method", help="STANDARD|ACCELERATED"),
21
+ explicitly_shared: bool = typer.Option(False, "--explicitly-shared"),
22
+ dry_run: bool = typer.Option(False, "--dry-run"),
23
+ ) -> None:
24
+ def _run() -> None:
25
+ client = _common.build_client(ctx)
26
+ operations = [
27
+ {
28
+ "create": {
29
+ "name": name,
30
+ "amount_micros": amount_micros,
31
+ "delivery_method": delivery_method,
32
+ "explicitly_shared": explicitly_shared,
33
+ }
34
+ }
35
+ ]
36
+ result = proto_bridge.run_mutate(
37
+ client,
38
+ "campaign_budget",
39
+ normalize_customer_id(customer_id),
40
+ operations,
41
+ validate_only=dry_run,
42
+ version=ctx.obj["version"],
43
+ )
44
+ formatting.render(result, fmt=ctx.obj["format"])
45
+
46
+ _common.run_guarded(ctx, _run)
@@ -0,0 +1,106 @@
1
+ """`gads hl campaign create|pause|enable|remove` -- Kampagnen verwalten."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from googleadscli import formatting, proto_bridge
8
+ from googleadscli.commands import _common
9
+ from googleadscli.utils import normalize_customer_id
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Kampagnen anlegen/verwalten")
12
+
13
+
14
+ @app.command("create")
15
+ def create(
16
+ ctx: typer.Context,
17
+ customer_id: str = typer.Option(..., "--customer-id", "-c"),
18
+ name: str = typer.Option(..., "--name"),
19
+ budget_resource_name: str = typer.Option(..., "--budget", help="Resource-Name des CampaignBudget"),
20
+ advertising_channel_type: str = typer.Option("SEARCH", "--channel-type", help="z.B. SEARCH, DISPLAY, PERFORMANCE_MAX"),
21
+ status: str = typer.Option("PAUSED", "--status", help="Standard PAUSED, um versehentliche Ausgaben zu vermeiden"),
22
+ dry_run: bool = typer.Option(False, "--dry-run"),
23
+ ) -> None:
24
+ def _run() -> None:
25
+ client = _common.build_client(ctx)
26
+ operations = [
27
+ {
28
+ "create": {
29
+ "name": name,
30
+ "campaign_budget": budget_resource_name,
31
+ "advertising_channel_type": advertising_channel_type,
32
+ "status": status,
33
+ }
34
+ }
35
+ ]
36
+ result = proto_bridge.run_mutate(
37
+ client,
38
+ "campaign",
39
+ normalize_customer_id(customer_id),
40
+ operations,
41
+ validate_only=dry_run,
42
+ version=ctx.obj["version"],
43
+ )
44
+ formatting.render(result, fmt=ctx.obj["format"])
45
+
46
+ _common.run_guarded(ctx, _run)
47
+
48
+
49
+ def _set_status(ctx: typer.Context, customer_id: str, resource_name: str, status: str, dry_run: bool) -> None:
50
+ def _run() -> None:
51
+ client = _common.build_client(ctx)
52
+ operations = [{"update": {"resource_name": resource_name, "status": status}}]
53
+ result = proto_bridge.run_mutate(
54
+ client,
55
+ "campaign",
56
+ normalize_customer_id(customer_id),
57
+ operations,
58
+ validate_only=dry_run,
59
+ version=ctx.obj["version"],
60
+ )
61
+ formatting.render(result, fmt=ctx.obj["format"])
62
+
63
+ _common.run_guarded(ctx, _run)
64
+
65
+
66
+ @app.command("pause")
67
+ def pause(
68
+ ctx: typer.Context,
69
+ customer_id: str = typer.Option(..., "--customer-id", "-c"),
70
+ resource_name: str = typer.Argument(..., help="Resource-Name der Kampagne"),
71
+ dry_run: bool = typer.Option(False, "--dry-run"),
72
+ ) -> None:
73
+ _set_status(ctx, customer_id, resource_name, "PAUSED", dry_run)
74
+
75
+
76
+ @app.command("enable")
77
+ def enable(
78
+ ctx: typer.Context,
79
+ customer_id: str = typer.Option(..., "--customer-id", "-c"),
80
+ resource_name: str = typer.Argument(..., help="Resource-Name der Kampagne"),
81
+ dry_run: bool = typer.Option(False, "--dry-run"),
82
+ ) -> None:
83
+ _set_status(ctx, customer_id, resource_name, "ENABLED", dry_run)
84
+
85
+
86
+ @app.command("remove")
87
+ def remove(
88
+ ctx: typer.Context,
89
+ customer_id: str = typer.Option(..., "--customer-id", "-c"),
90
+ resource_name: str = typer.Argument(..., help="Resource-Name der Kampagne"),
91
+ dry_run: bool = typer.Option(False, "--dry-run"),
92
+ ) -> None:
93
+ def _run() -> None:
94
+ client = _common.build_client(ctx)
95
+ operations = [{"remove": resource_name}]
96
+ result = proto_bridge.run_mutate(
97
+ client,
98
+ "campaign",
99
+ normalize_customer_id(customer_id),
100
+ operations,
101
+ validate_only=dry_run,
102
+ version=ctx.obj["version"],
103
+ )
104
+ formatting.render(result, fmt=ctx.obj["format"])
105
+
106
+ _common.run_guarded(ctx, _run)
@@ -0,0 +1,46 @@
1
+ """`gads hl keyword add` -- Keyword als AdGroupCriterion hinzufuegen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from googleadscli import formatting, proto_bridge
8
+ from googleadscli.commands import _common
9
+ from googleadscli.utils import normalize_customer_id
10
+
11
+ app = typer.Typer(no_args_is_help=True, help="Keywords hinzufuegen/verwalten")
12
+
13
+
14
+ @app.command("add")
15
+ def add(
16
+ ctx: typer.Context,
17
+ customer_id: str = typer.Option(..., "--customer-id", "-c"),
18
+ ad_group_resource_name: str = typer.Option(..., "--ad-group", help="Resource-Name der Ad Group"),
19
+ text: str = typer.Option(..., "--text", help="Keyword-Text"),
20
+ match_type: str = typer.Option("BROAD", "--match-type", help="EXACT|PHRASE|BROAD"),
21
+ status: str = typer.Option("ENABLED", "--status"),
22
+ cpc_bid_micros: int = typer.Option(None, "--cpc-bid-micros"),
23
+ dry_run: bool = typer.Option(False, "--dry-run"),
24
+ ) -> None:
25
+ def _run() -> None:
26
+ client = _common.build_client(ctx)
27
+ create_payload: dict = {
28
+ "ad_group": ad_group_resource_name,
29
+ "status": status,
30
+ "keyword": {"text": text, "match_type": match_type},
31
+ }
32
+ if cpc_bid_micros is not None:
33
+ create_payload["cpc_bid_micros"] = cpc_bid_micros
34
+
35
+ operations = [{"create": create_payload}]
36
+ result = proto_bridge.run_mutate(
37
+ client,
38
+ "ad_group_criterion",
39
+ normalize_customer_id(customer_id),
40
+ operations,
41
+ validate_only=dry_run,
42
+ version=ctx.obj["version"],
43
+ )
44
+ formatting.render(result, fmt=ctx.obj["format"])
45
+
46
+ _common.run_guarded(ctx, _run)