google-analytics-cli 0.1.0__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.
Files changed (49) hide show
  1. ga_cli/__init__.py +8 -0
  2. ga_cli/api/__init__.py +0 -0
  3. ga_cli/api/client.py +142 -0
  4. ga_cli/auth/__init__.py +35 -0
  5. ga_cli/auth/credentials.py +126 -0
  6. ga_cli/auth/oauth.py +322 -0
  7. ga_cli/auth/service_account.py +155 -0
  8. ga_cli/commands/__init__.py +0 -0
  9. ga_cli/commands/access_bindings.py +254 -0
  10. ga_cli/commands/access_reports.py +201 -0
  11. ga_cli/commands/account_summaries.py +68 -0
  12. ga_cli/commands/accounts.py +297 -0
  13. ga_cli/commands/agent_cmd.py +776 -0
  14. ga_cli/commands/annotations.py +264 -0
  15. ga_cli/commands/audiences.py +223 -0
  16. ga_cli/commands/auth_cmd.py +205 -0
  17. ga_cli/commands/bigquery_links.py +309 -0
  18. ga_cli/commands/calculated_metrics.py +312 -0
  19. ga_cli/commands/channel_groups.py +223 -0
  20. ga_cli/commands/completions_cmd.py +55 -0
  21. ga_cli/commands/config_cmd.py +113 -0
  22. ga_cli/commands/custom_dimensions.py +272 -0
  23. ga_cli/commands/custom_metrics.py +305 -0
  24. ga_cli/commands/data_retention.py +153 -0
  25. ga_cli/commands/data_streams.py +277 -0
  26. ga_cli/commands/event_create_rules.py +250 -0
  27. ga_cli/commands/event_edit_rules.py +292 -0
  28. ga_cli/commands/firebase_links.py +142 -0
  29. ga_cli/commands/google_ads_links.py +225 -0
  30. ga_cli/commands/key_events.py +269 -0
  31. ga_cli/commands/mp_secrets.py +265 -0
  32. ga_cli/commands/properties.py +330 -0
  33. ga_cli/commands/property_settings.py +287 -0
  34. ga_cli/commands/reports.py +726 -0
  35. ga_cli/commands/upgrade_cmd.py +148 -0
  36. ga_cli/config/__init__.py +0 -0
  37. ga_cli/config/constants.py +61 -0
  38. ga_cli/config/store.py +115 -0
  39. ga_cli/main.py +110 -0
  40. ga_cli/utils/__init__.py +20 -0
  41. ga_cli/utils/describe.py +129 -0
  42. ga_cli/utils/dry_run.py +40 -0
  43. ga_cli/utils/errors.py +150 -0
  44. ga_cli/utils/output.py +209 -0
  45. ga_cli/utils/pagination.py +93 -0
  46. google_analytics_cli-0.1.0.dist-info/METADATA +321 -0
  47. google_analytics_cli-0.1.0.dist-info/RECORD +49 -0
  48. google_analytics_cli-0.1.0.dist-info/WHEEL +4 -0
  49. google_analytics_cli-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,269 @@
1
+ """Key event management commands."""
2
+
3
+ from typing import Optional
4
+
5
+ import questionary
6
+ import typer
7
+
8
+ from ..api.client import get_admin_client
9
+ from ..config.store import get_effective_value
10
+ from ..utils import (
11
+ handle_dry_run,
12
+ handle_error,
13
+ info,
14
+ output,
15
+ require_options,
16
+ resolve_output_format,
17
+ success,
18
+ )
19
+ from ..utils.pagination import paginate_all
20
+
21
+ key_events_app = typer.Typer(
22
+ name="key-events",
23
+ help="Manage key events (conversions)",
24
+ no_args_is_help=True,
25
+ )
26
+
27
+ _VALID_COUNTING_METHODS = ("ONCE_PER_EVENT", "ONCE_PER_SESSION")
28
+
29
+
30
+ @key_events_app.command("list")
31
+ def list_cmd(
32
+ property_id: Optional[str] = typer.Option(
33
+ None, "--property-id", "-p", help="Property ID (numeric)"
34
+ ),
35
+ output_format: Optional[str] = typer.Option(
36
+ None, "--output", "-o", help="Output format (json, table, compact)"
37
+ ),
38
+ ):
39
+ """List key events for a property."""
40
+ try:
41
+ effective_property = get_effective_value(property_id, "default_property_id")
42
+ require_options({"property_id": effective_property}, ["property_id"])
43
+ effective_format = resolve_output_format(output_format)
44
+
45
+ admin = get_admin_client()
46
+ events = paginate_all(
47
+ lambda **kw: admin.properties()
48
+ .keyEvents()
49
+ .list(parent=f"properties/{effective_property}", **kw)
50
+ .execute(),
51
+ "keyEvents",
52
+ pageSize=200,
53
+ )
54
+
55
+ output(
56
+ events,
57
+ effective_format,
58
+ columns=[
59
+ "name",
60
+ "eventName",
61
+ "createTime",
62
+ "deletable",
63
+ "custom",
64
+ "countingMethod",
65
+ ],
66
+ headers=[
67
+ "Resource Name",
68
+ "Event Name",
69
+ "Create Time",
70
+ "Deletable",
71
+ "Custom",
72
+ "Counting Method",
73
+ ],
74
+ )
75
+ except Exception as e:
76
+ handle_error(e)
77
+
78
+
79
+ @key_events_app.command("get")
80
+ def get_cmd(
81
+ property_id: Optional[str] = typer.Option(
82
+ None, "--property-id", "-p", help="Property ID (numeric)"
83
+ ),
84
+ key_event_id: str = typer.Option(
85
+ ..., "--key-event-id", "-k", help="Key event ID"
86
+ ),
87
+ output_format: Optional[str] = typer.Option(
88
+ None, "--output", "-o", help="Output format (json, table, compact)"
89
+ ),
90
+ ):
91
+ """Get details for a key event."""
92
+ try:
93
+ effective_property = get_effective_value(property_id, "default_property_id")
94
+ require_options({"property_id": effective_property}, ["property_id"])
95
+ effective_format = resolve_output_format(output_format)
96
+
97
+ admin = get_admin_client()
98
+ event = (
99
+ admin.properties()
100
+ .keyEvents()
101
+ .get(name=f"properties/{effective_property}/keyEvents/{key_event_id}")
102
+ .execute()
103
+ )
104
+ output(event, effective_format)
105
+ except Exception as e:
106
+ handle_error(e)
107
+
108
+
109
+ @key_events_app.command("create")
110
+ def create_cmd(
111
+ property_id: Optional[str] = typer.Option(
112
+ None, "--property-id", "-p", help="Property ID (numeric)"
113
+ ),
114
+ event_name: str = typer.Option(
115
+ ..., "--event-name", "-e", help="Event name to mark as key event"
116
+ ),
117
+ counting_method: str = typer.Option(
118
+ "ONCE_PER_EVENT",
119
+ "--counting-method",
120
+ help="Counting method: ONCE_PER_EVENT or ONCE_PER_SESSION",
121
+ ),
122
+ dry_run: bool = typer.Option(
123
+ False, "--dry-run", help="Preview the request without executing"
124
+ ),
125
+ output_format: Optional[str] = typer.Option(
126
+ None, "--output", "-o", help="Output format (json, table, compact)"
127
+ ),
128
+ ):
129
+ """Create a key event."""
130
+ try:
131
+ effective_property = get_effective_value(property_id, "default_property_id")
132
+ require_options({"property_id": effective_property}, ["property_id"])
133
+ effective_format = resolve_output_format(output_format)
134
+
135
+ method_upper = counting_method.upper()
136
+ if method_upper not in _VALID_COUNTING_METHODS:
137
+ raise typer.BadParameter(
138
+ f"Invalid counting method '{counting_method}'. "
139
+ f"Must be one of: {', '.join(_VALID_COUNTING_METHODS)}"
140
+ )
141
+
142
+ body = {
143
+ "eventName": event_name,
144
+ "countingMethod": method_upper,
145
+ }
146
+ if dry_run:
147
+ handle_dry_run("create", "POST", f"properties/{effective_property}", body)
148
+
149
+ admin = get_admin_client()
150
+ event = (
151
+ admin.properties()
152
+ .keyEvents()
153
+ .create(parent=f"properties/{effective_property}", body=body)
154
+ .execute()
155
+ )
156
+ output(event, effective_format)
157
+ except (typer.BadParameter, typer.Exit):
158
+ raise
159
+ except Exception as e:
160
+ handle_error(e)
161
+
162
+
163
+ @key_events_app.command("update")
164
+ def update_cmd(
165
+ property_id: Optional[str] = typer.Option(
166
+ None, "--property-id", "-p", help="Property ID (numeric)"
167
+ ),
168
+ key_event_id: str = typer.Option(
169
+ ..., "--key-event-id", "-k", help="Key event ID"
170
+ ),
171
+ counting_method: Optional[str] = typer.Option(
172
+ None, "--counting-method", help="New counting method"
173
+ ),
174
+ dry_run: bool = typer.Option(
175
+ False, "--dry-run", help="Preview the request without executing"
176
+ ),
177
+ output_format: Optional[str] = typer.Option(
178
+ None, "--output", "-o", help="Output format (json, table, compact)"
179
+ ),
180
+ ):
181
+ """Update a key event."""
182
+ try:
183
+ effective_property = get_effective_value(property_id, "default_property_id")
184
+ require_options({"property_id": effective_property}, ["property_id"])
185
+ effective_format = resolve_output_format(output_format)
186
+
187
+ body = {}
188
+ mask_fields = []
189
+ if counting_method is not None:
190
+ method_upper = counting_method.upper()
191
+ if method_upper not in _VALID_COUNTING_METHODS:
192
+ raise typer.BadParameter(
193
+ f"Invalid counting method '{counting_method}'. "
194
+ f"Must be one of: {', '.join(_VALID_COUNTING_METHODS)}"
195
+ )
196
+ body["countingMethod"] = method_upper
197
+ mask_fields.append("countingMethod")
198
+
199
+ if not mask_fields:
200
+ raise typer.BadParameter(
201
+ "At least one field must be specified: --counting-method"
202
+ )
203
+
204
+ resource_name = f"properties/{effective_property}/keyEvents/{key_event_id}"
205
+ if dry_run:
206
+ handle_dry_run(
207
+ "update", "PATCH", resource_name,
208
+ body, update_mask=",".join(mask_fields),
209
+ )
210
+
211
+ admin = get_admin_client()
212
+ event = (
213
+ admin.properties()
214
+ .keyEvents()
215
+ .patch(
216
+ name=resource_name,
217
+ body=body,
218
+ updateMask=",".join(mask_fields),
219
+ )
220
+ .execute()
221
+ )
222
+ output(event, effective_format)
223
+ except (typer.BadParameter, typer.Exit):
224
+ raise
225
+ except Exception as e:
226
+ handle_error(e)
227
+
228
+
229
+ @key_events_app.command("delete")
230
+ def delete_cmd(
231
+ property_id: Optional[str] = typer.Option(
232
+ None, "--property-id", "-p", help="Property ID (numeric)"
233
+ ),
234
+ key_event_id: str = typer.Option(
235
+ ..., "--key-event-id", "-k", help="Key event ID"
236
+ ),
237
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
238
+ dry_run: bool = typer.Option(
239
+ False, "--dry-run", help="Preview the request without executing"
240
+ ),
241
+ ):
242
+ """Delete a key event."""
243
+ try:
244
+ effective_property = get_effective_value(property_id, "default_property_id")
245
+ require_options({"property_id": effective_property}, ["property_id"])
246
+
247
+ if dry_run:
248
+ handle_dry_run(
249
+ "delete", "DELETE",
250
+ f"properties/{effective_property}/keyEvents/{key_event_id}",
251
+ None,
252
+ )
253
+
254
+ if not yes:
255
+ confirmed = questionary.confirm(
256
+ f"Delete key event {key_event_id}? This cannot be undone."
257
+ ).ask()
258
+ if not confirmed:
259
+ info("Cancelled.")
260
+ raise typer.Exit()
261
+
262
+ admin = get_admin_client()
263
+ resource_name = f"properties/{effective_property}/keyEvents/{key_event_id}"
264
+ admin.properties().keyEvents().delete(name=resource_name).execute()
265
+ success(f"Key event {key_event_id} deleted.")
266
+ except typer.Exit:
267
+ raise
268
+ except Exception as e:
269
+ handle_error(e)
@@ -0,0 +1,265 @@
1
+ """Measurement Protocol secret management commands."""
2
+
3
+ from typing import Optional
4
+
5
+ import questionary
6
+ import typer
7
+
8
+ from ..api.client import get_admin_client
9
+ from ..config.store import get_effective_value
10
+ from ..utils import (
11
+ handle_dry_run,
12
+ handle_error,
13
+ info,
14
+ output,
15
+ require_options,
16
+ resolve_output_format,
17
+ success,
18
+ )
19
+ from ..utils.pagination import paginate_all
20
+
21
+ mp_secrets_app = typer.Typer(
22
+ name="mp-secrets",
23
+ help="Manage Measurement Protocol secrets",
24
+ no_args_is_help=True,
25
+ )
26
+
27
+
28
+ @mp_secrets_app.command("list")
29
+ def list_cmd(
30
+ property_id: Optional[str] = typer.Option(
31
+ None, "--property-id", "-p", help="Property ID (numeric)"
32
+ ),
33
+ stream_id: str = typer.Option(
34
+ ..., "--stream-id", "-s", help="Data stream ID"
35
+ ),
36
+ output_format: Optional[str] = typer.Option(
37
+ None, "--output", "-o", help="Output format (json, table, compact)"
38
+ ),
39
+ ):
40
+ """List Measurement Protocol secrets for a data stream."""
41
+ try:
42
+ effective_property = get_effective_value(property_id, "default_property_id")
43
+ require_options({"property_id": effective_property}, ["property_id"])
44
+ effective_format = resolve_output_format(output_format)
45
+
46
+ admin = get_admin_client()
47
+ parent = f"properties/{effective_property}/dataStreams/{stream_id}"
48
+ secrets = paginate_all(
49
+ lambda **kw: admin.properties()
50
+ .dataStreams()
51
+ .measurementProtocolSecrets()
52
+ .list(parent=parent, **kw)
53
+ .execute(),
54
+ "measurementProtocolSecrets",
55
+ pageSize=200,
56
+ )
57
+
58
+ output(
59
+ secrets,
60
+ effective_format,
61
+ columns=["name", "displayName", "secretValue"],
62
+ headers=["Resource Name", "Display Name", "Secret Value"],
63
+ )
64
+ except Exception as e:
65
+ handle_error(e)
66
+
67
+
68
+ @mp_secrets_app.command("get")
69
+ def get_cmd(
70
+ property_id: Optional[str] = typer.Option(
71
+ None, "--property-id", "-p", help="Property ID (numeric)"
72
+ ),
73
+ stream_id: str = typer.Option(
74
+ ..., "--stream-id", "-s", help="Data stream ID"
75
+ ),
76
+ secret_id: str = typer.Option(
77
+ ..., "--secret-id", help="Measurement Protocol secret ID"
78
+ ),
79
+ output_format: Optional[str] = typer.Option(
80
+ None, "--output", "-o", help="Output format (json, table, compact)"
81
+ ),
82
+ ):
83
+ """Get details for a Measurement Protocol secret."""
84
+ try:
85
+ effective_property = get_effective_value(property_id, "default_property_id")
86
+ require_options({"property_id": effective_property}, ["property_id"])
87
+ effective_format = resolve_output_format(output_format)
88
+
89
+ admin = get_admin_client()
90
+ resource_name = (
91
+ f"properties/{effective_property}/dataStreams/{stream_id}"
92
+ f"/measurementProtocolSecrets/{secret_id}"
93
+ )
94
+ secret = (
95
+ admin.properties()
96
+ .dataStreams()
97
+ .measurementProtocolSecrets()
98
+ .get(name=resource_name)
99
+ .execute()
100
+ )
101
+ output(secret, effective_format)
102
+ except Exception as e:
103
+ handle_error(e)
104
+
105
+
106
+ @mp_secrets_app.command("create")
107
+ def create_cmd(
108
+ property_id: Optional[str] = typer.Option(
109
+ None, "--property-id", "-p", help="Property ID (numeric)"
110
+ ),
111
+ stream_id: str = typer.Option(
112
+ ..., "--stream-id", "-s", help="Data stream ID"
113
+ ),
114
+ display_name: str = typer.Option(
115
+ ..., "--display-name", help="Display name for the secret"
116
+ ),
117
+ dry_run: bool = typer.Option(
118
+ False, "--dry-run", help="Preview the request without executing"
119
+ ),
120
+ output_format: Optional[str] = typer.Option(
121
+ None, "--output", "-o", help="Output format (json, table, compact)"
122
+ ),
123
+ ):
124
+ """Create a Measurement Protocol secret."""
125
+ try:
126
+ effective_property = get_effective_value(property_id, "default_property_id")
127
+ require_options({"property_id": effective_property}, ["property_id"])
128
+ effective_format = resolve_output_format(output_format)
129
+
130
+ parent = f"properties/{effective_property}/dataStreams/{stream_id}"
131
+ body = {"displayName": display_name}
132
+ if dry_run:
133
+ handle_dry_run("create", "POST", parent, body)
134
+
135
+ admin = get_admin_client()
136
+ secret = (
137
+ admin.properties()
138
+ .dataStreams()
139
+ .measurementProtocolSecrets()
140
+ .create(parent=parent, body=body)
141
+ .execute()
142
+ )
143
+ output(secret, effective_format)
144
+ except typer.Exit:
145
+ raise
146
+ except Exception as e:
147
+ handle_error(e)
148
+
149
+
150
+ @mp_secrets_app.command("update")
151
+ def update_cmd(
152
+ property_id: Optional[str] = typer.Option(
153
+ None, "--property-id", "-p", help="Property ID (numeric)"
154
+ ),
155
+ stream_id: str = typer.Option(
156
+ ..., "--stream-id", "-s", help="Data stream ID"
157
+ ),
158
+ secret_id: str = typer.Option(
159
+ ..., "--secret-id", help="Measurement Protocol secret ID"
160
+ ),
161
+ display_name: Optional[str] = typer.Option(
162
+ None, "--display-name", help="New display name"
163
+ ),
164
+ dry_run: bool = typer.Option(
165
+ False, "--dry-run", help="Preview the request without executing"
166
+ ),
167
+ output_format: Optional[str] = typer.Option(
168
+ None, "--output", "-o", help="Output format (json, table, compact)"
169
+ ),
170
+ ):
171
+ """Update a Measurement Protocol secret."""
172
+ try:
173
+ effective_property = get_effective_value(property_id, "default_property_id")
174
+ require_options({"property_id": effective_property}, ["property_id"])
175
+ effective_format = resolve_output_format(output_format)
176
+
177
+ body = {}
178
+ mask_fields = []
179
+ if display_name is not None:
180
+ body["displayName"] = display_name
181
+ mask_fields.append("displayName")
182
+
183
+ if not mask_fields:
184
+ raise typer.BadParameter(
185
+ "At least one field must be specified: --display-name"
186
+ )
187
+
188
+ resource_name = (
189
+ f"properties/{effective_property}/dataStreams/{stream_id}"
190
+ f"/measurementProtocolSecrets/{secret_id}"
191
+ )
192
+ if dry_run:
193
+ handle_dry_run(
194
+ "update", "PATCH", resource_name,
195
+ body, update_mask=",".join(mask_fields),
196
+ )
197
+
198
+ admin = get_admin_client()
199
+ secret = (
200
+ admin.properties()
201
+ .dataStreams()
202
+ .measurementProtocolSecrets()
203
+ .patch(
204
+ name=resource_name,
205
+ body=body,
206
+ updateMask=",".join(mask_fields),
207
+ )
208
+ .execute()
209
+ )
210
+ output(secret, effective_format)
211
+ except (typer.BadParameter, typer.Exit):
212
+ raise
213
+ except Exception as e:
214
+ handle_error(e)
215
+
216
+
217
+ @mp_secrets_app.command("delete")
218
+ def delete_cmd(
219
+ property_id: Optional[str] = typer.Option(
220
+ None, "--property-id", "-p", help="Property ID (numeric)"
221
+ ),
222
+ stream_id: str = typer.Option(
223
+ ..., "--stream-id", "-s", help="Data stream ID"
224
+ ),
225
+ secret_id: str = typer.Option(
226
+ ..., "--secret-id", help="Measurement Protocol secret ID"
227
+ ),
228
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
229
+ dry_run: bool = typer.Option(
230
+ False, "--dry-run", help="Preview the request without executing"
231
+ ),
232
+ ):
233
+ """Delete a Measurement Protocol secret."""
234
+ try:
235
+ effective_property = get_effective_value(property_id, "default_property_id")
236
+ require_options({"property_id": effective_property}, ["property_id"])
237
+
238
+ if dry_run:
239
+ resource_name = (
240
+ f"properties/{effective_property}/dataStreams/{stream_id}"
241
+ f"/measurementProtocolSecrets/{secret_id}"
242
+ )
243
+ handle_dry_run("delete", "DELETE", resource_name, None)
244
+
245
+ if not yes:
246
+ confirmed = questionary.confirm(
247
+ f"Delete Measurement Protocol secret {secret_id}? This cannot be undone."
248
+ ).ask()
249
+ if not confirmed:
250
+ info("Cancelled.")
251
+ raise typer.Exit()
252
+
253
+ admin = get_admin_client()
254
+ resource_name = (
255
+ f"properties/{effective_property}/dataStreams/{stream_id}"
256
+ f"/measurementProtocolSecrets/{secret_id}"
257
+ )
258
+ admin.properties().dataStreams().measurementProtocolSecrets().delete(
259
+ name=resource_name
260
+ ).execute()
261
+ success(f"Measurement Protocol secret {secret_id} deleted.")
262
+ except typer.Exit:
263
+ raise
264
+ except Exception as e:
265
+ handle_error(e)