adloop 0.16.0__tar.gz → 0.16.1__tar.gz
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.
- {adloop-0.16.0 → adloop-0.16.1}/PKG-INFO +1 -1
- {adloop-0.16.0 → adloop-0.16.1}/pyproject.toml +1 -1
- {adloop-0.16.0 → adloop-0.16.1}/pyproject.toml.orig +1 -1
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/validate_only.py +41 -14
- {adloop-0.16.0 → adloop-0.16.1}/README.md +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/__main__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/_mcp_patches.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/client.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/conversion_actions.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/currency.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/enums.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/forecast.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/gaql.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/pmax.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/read.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ads/write.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/auth.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/cli.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/config.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/crossref.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/diagnostics.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ga4/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ga4/client.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ga4/reports.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ga4/tracking.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/ga4/write.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/gsc/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/gsc/client.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/gsc/reports.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/gtm/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/gtm/client.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/gtm/read.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/merchant/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/merchant/client.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/merchant/read.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/pagespeed.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/reddit/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/reddit/auth.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/reddit/client.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/reddit/read.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/reddit/schedule.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/reddit/write.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules/adloop.md +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules/commands/analyze-performance.md +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules/commands/budget-plan.md +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules/commands/create-ad.md +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules/commands/create-campaign.md +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules/commands/diagnose-tracking.md +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules/commands/optimize-campaign.md +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/rules_install.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/runtime.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/safety/__init__.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/safety/audit.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/safety/guards.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/safety/preview.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/server.py +0 -0
- {adloop-0.16.0 → adloop-0.16.1}/src/adloop/tracking.py +0 -0
|
@@ -10,6 +10,11 @@ Validate-only responses carry no results, so each validated call answers the
|
|
|
10
10
|
apply code with placeholder resource names. A later call that references one
|
|
11
11
|
(a plan whose second step uses what the first would have created) cannot be
|
|
12
12
|
validated meaningfully; it is skipped and counted instead of sent.
|
|
13
|
+
|
|
14
|
+
The wrapper fails closed: only path helpers, reads, ``mutate*`` and
|
|
15
|
+
``upload*`` calls (both sent validate-only) reach the real service. Any other
|
|
16
|
+
method raises, so a write path added later cannot slip past a dry run and
|
|
17
|
+
change an account for real.
|
|
13
18
|
"""
|
|
14
19
|
|
|
15
20
|
from __future__ import annotations
|
|
@@ -18,7 +23,8 @@ from types import SimpleNamespace
|
|
|
18
23
|
|
|
19
24
|
PLACEHOLDER = "adloop-validate-only"
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
# Methods that never change an account and may run for real in a dry run.
|
|
27
|
+
_READ_METHODS = frozenset({"search", "search_stream"})
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
class ValidateOnlyFailure(Exception):
|
|
@@ -52,9 +58,14 @@ class _ValidateOnlyService:
|
|
|
52
58
|
self._service = service
|
|
53
59
|
|
|
54
60
|
def __getattr__(self, attr: str) -> object:
|
|
61
|
+
if attr in _READ_METHODS or attr.endswith("_path") or attr.startswith("parse_"):
|
|
62
|
+
return getattr(self._service, attr)
|
|
63
|
+
if not (attr.startswith("mutate") or attr.startswith("upload")):
|
|
64
|
+
raise ValidateOnlyFailure(
|
|
65
|
+
f"{self._name}.{attr} has no validate-only mode, so a dry run "
|
|
66
|
+
"cannot check it without changing the account. Refusing."
|
|
67
|
+
)
|
|
55
68
|
target = getattr(self._service, attr)
|
|
56
|
-
if not attr.startswith("mutate"):
|
|
57
|
-
return target
|
|
58
69
|
|
|
59
70
|
def validate(request: object = None, **kwargs: object) -> object:
|
|
60
71
|
if request is None:
|
|
@@ -78,35 +89,51 @@ class _ValidateOnlyService:
|
|
|
78
89
|
def _build_request(self, method: str, kwargs: dict) -> object:
|
|
79
90
|
request = self._owner._client.get_type(_request_type(self._name, method))
|
|
80
91
|
for key, value in kwargs.items():
|
|
81
|
-
if key in
|
|
92
|
+
if key in _LIST_FIELDS:
|
|
82
93
|
getattr(request, key).extend(value)
|
|
83
94
|
else:
|
|
84
95
|
setattr(request, key, value)
|
|
85
96
|
return request
|
|
86
97
|
|
|
87
98
|
|
|
99
|
+
_LIST_FIELDS = ("operations", "mutate_operations", "conversions")
|
|
100
|
+
|
|
101
|
+
|
|
88
102
|
def _request_type(service_name: str, method: str) -> str:
|
|
89
|
-
"""``mutate_ad_group_criteria`` -> ``MutateAdGroupCriteriaRequest
|
|
103
|
+
"""``mutate_ad_group_criteria`` -> ``MutateAdGroupCriteriaRequest``,
|
|
104
|
+
``upload_click_conversions`` -> ``UploadClickConversionsRequest``."""
|
|
90
105
|
if method == "mutate":
|
|
91
106
|
return "Mutate" + service_name.removesuffix("Service") + "Request"
|
|
92
|
-
|
|
93
|
-
return
|
|
107
|
+
verb, _, rest = method.partition("_")
|
|
108
|
+
return verb.capitalize() + "".join(word.capitalize() for word in rest.split("_")) + "Request"
|
|
94
109
|
|
|
95
110
|
|
|
96
111
|
def _operations_of(request: object) -> object:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
for field in _LIST_FIELDS:
|
|
113
|
+
if hasattr(request, field):
|
|
114
|
+
return getattr(request, field)
|
|
115
|
+
return []
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class _PlaceholderResult:
|
|
119
|
+
"""A MutateOperationResponse stand-in: every ``*_result`` field
|
|
120
|
+
(``asset_result``, ``campaign_asset_result``, ...) carries the placeholder,
|
|
121
|
+
whichever one the apply code reads."""
|
|
122
|
+
|
|
123
|
+
def __init__(self, name: str) -> None:
|
|
124
|
+
self._name = name
|
|
125
|
+
|
|
126
|
+
def __getattr__(self, attr: str) -> object:
|
|
127
|
+
if attr.endswith("_result"):
|
|
128
|
+
return SimpleNamespace(resource_name=self._name)
|
|
129
|
+
raise AttributeError(attr)
|
|
100
130
|
|
|
101
131
|
|
|
102
132
|
def _placeholder_response(customer_id: str, count: int, googleads_mutate: bool) -> object:
|
|
103
133
|
names = [f"customers/{customer_id}/{PLACEHOLDER}/{i}" for i in range(count)]
|
|
104
134
|
if googleads_mutate:
|
|
105
135
|
return SimpleNamespace(
|
|
106
|
-
mutate_operation_responses=[
|
|
107
|
-
SimpleNamespace(**{_RESPONSE_FIELD_FOR_SERVICE: SimpleNamespace(resource_name=n)})
|
|
108
|
-
for n in names
|
|
109
|
-
],
|
|
136
|
+
mutate_operation_responses=[_PlaceholderResult(n) for n in names],
|
|
110
137
|
partial_failure_error=None,
|
|
111
138
|
)
|
|
112
139
|
return SimpleNamespace(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|