direct-cli 0.2.1__tar.gz → 0.2.2__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.
- {direct_cli-0.2.1/direct_cli.egg-info → direct_cli-0.2.2}/PKG-INFO +1 -1
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/ads.py +22 -2
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/utils.py +1 -1
- {direct_cli-0.2.1 → direct_cli-0.2.2/direct_cli.egg-info}/PKG-INFO +1 -1
- {direct_cli-0.2.1 → direct_cli-0.2.2}/pyproject.toml +1 -1
- {direct_cli-0.2.1 → direct_cli-0.2.2}/tests/test_dry_run.py +44 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/tests/test_integration.py +29 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/.env.example +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/MANIFEST.in +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/README.md +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/__init__.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/api.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/auth.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/cli.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/adextensions.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/adgroups.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/adimages.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/agencyclients.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/audiencetargets.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/bidmodifiers.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/bids.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/businesses.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/campaigns.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/changes.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/clients.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/creatives.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/dictionaries.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/dynamicads.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/feeds.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/keywordbids.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/keywords.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/reports.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/retargeting.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/sitelinks.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/smartadtargets.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/turbopages.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/commands/vcards.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli/output.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli.egg-info/SOURCES.txt +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli.egg-info/requires.txt +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/setup.cfg +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/setup.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/tests/test_auth_op.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/tests/test_cli.py +0 -0
- {direct_cli-0.2.1 → direct_cli-0.2.2}/tests/test_comprehensive.py +0 -0
|
@@ -24,7 +24,11 @@ def ads():
|
|
|
24
24
|
@click.option("--fetch-all", is_flag=True, help="Fetch all pages")
|
|
25
25
|
@click.option("--format", "output_format", default="json", help="Output format")
|
|
26
26
|
@click.option("--output", help="Output file")
|
|
27
|
-
@click.option("--fields", help="Comma-separated field names")
|
|
27
|
+
@click.option("--fields", help="Comma-separated top-level field names")
|
|
28
|
+
@click.option(
|
|
29
|
+
"--text-ad-fields", help="Comma-separated TextAd field names (e.g. Title,Text,Href)"
|
|
30
|
+
)
|
|
31
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
28
32
|
@click.pass_context
|
|
29
33
|
def get(
|
|
30
34
|
ctx,
|
|
@@ -37,6 +41,8 @@ def get(
|
|
|
37
41
|
output_format,
|
|
38
42
|
output,
|
|
39
43
|
fields,
|
|
44
|
+
text_ad_fields,
|
|
45
|
+
dry_run,
|
|
40
46
|
):
|
|
41
47
|
"""Get ads"""
|
|
42
48
|
try:
|
|
@@ -52,6 +58,12 @@ def get(
|
|
|
52
58
|
else ["Id", "CampaignId", "AdGroupId", "Status", "State", "Type"]
|
|
53
59
|
)
|
|
54
60
|
|
|
61
|
+
text_ad_field_names = (
|
|
62
|
+
text_ad_fields.split(",")
|
|
63
|
+
if text_ad_fields
|
|
64
|
+
else ["Title", "Title2", "Text", "Href"]
|
|
65
|
+
)
|
|
66
|
+
|
|
55
67
|
criteria = {}
|
|
56
68
|
if ids:
|
|
57
69
|
criteria["Ids"] = parse_ids(ids)
|
|
@@ -62,13 +74,21 @@ def get(
|
|
|
62
74
|
if status:
|
|
63
75
|
criteria["Statuses"] = [status]
|
|
64
76
|
|
|
65
|
-
params = {
|
|
77
|
+
params = {
|
|
78
|
+
"SelectionCriteria": criteria,
|
|
79
|
+
"FieldNames": field_names,
|
|
80
|
+
"TextAdFieldNames": text_ad_field_names,
|
|
81
|
+
}
|
|
66
82
|
|
|
67
83
|
if limit:
|
|
68
84
|
params["Page"] = {"Limit": limit}
|
|
69
85
|
|
|
70
86
|
body = {"method": "get", "params": params}
|
|
71
87
|
|
|
88
|
+
if dry_run:
|
|
89
|
+
format_output(body, "json", None)
|
|
90
|
+
return
|
|
91
|
+
|
|
72
92
|
result = client.ads().post(data=body)
|
|
73
93
|
|
|
74
94
|
if fetch_all:
|
|
@@ -89,7 +89,7 @@ COMMON_FIELDS = {
|
|
|
89
89
|
"ClientInfo",
|
|
90
90
|
],
|
|
91
91
|
"adgroups": ["Id", "Name", "CampaignId", "Status", "Type", "RegionIds"],
|
|
92
|
-
"ads": ["Id", "CampaignId", "AdGroupId", "Status", "State", "Type"],
|
|
92
|
+
"ads": ["Id", "CampaignId", "AdGroupId", "Status", "State", "Type", "TextAd"],
|
|
93
93
|
"keywords": [
|
|
94
94
|
"Id",
|
|
95
95
|
"Keyword",
|
|
@@ -115,6 +115,50 @@ def test_ads_update_extra_json_merges_into_payload():
|
|
|
115
115
|
assert ad["TextAd"] == {"Title": "Updated"}
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
def test_ads_get_default_fieldnames():
|
|
119
|
+
"""Default FieldNames includes basic top-level fields, plus TextAdFieldNames."""
|
|
120
|
+
body = _dry_run("ads", "get", "--campaign-ids", "12345")
|
|
121
|
+
assert body["method"] == "get"
|
|
122
|
+
assert body["params"]["FieldNames"] == [
|
|
123
|
+
"Id",
|
|
124
|
+
"CampaignId",
|
|
125
|
+
"AdGroupId",
|
|
126
|
+
"Status",
|
|
127
|
+
"State",
|
|
128
|
+
"Type",
|
|
129
|
+
]
|
|
130
|
+
assert body["params"]["TextAdFieldNames"] == ["Title", "Title2", "Text", "Href"]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_ads_get_with_fields_overrides_defaults():
|
|
134
|
+
"""--fields and --text-ad-fields override the defaults."""
|
|
135
|
+
body = _dry_run(
|
|
136
|
+
"ads",
|
|
137
|
+
"get",
|
|
138
|
+
"--campaign-ids",
|
|
139
|
+
"12345",
|
|
140
|
+
"--fields",
|
|
141
|
+
"Id,State",
|
|
142
|
+
"--text-ad-fields",
|
|
143
|
+
"Title",
|
|
144
|
+
)
|
|
145
|
+
assert body["params"]["FieldNames"] == ["Id", "State"]
|
|
146
|
+
assert body["params"]["TextAdFieldNames"] == ["Title"]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_ads_get_with_ids_and_status():
|
|
150
|
+
"""Multiple selection criteria are combined correctly."""
|
|
151
|
+
body = _dry_run(
|
|
152
|
+
"ads", "get", "--ids", "1,2,3", "--status", "ACCEPTED", "--limit", "10"
|
|
153
|
+
)
|
|
154
|
+
assert body["params"]["SelectionCriteria"] == {
|
|
155
|
+
"Ids": [1, 2, 3],
|
|
156
|
+
"Statuses": ["ACCEPTED"],
|
|
157
|
+
}
|
|
158
|
+
assert body["params"]["Page"] == {"Limit": 10}
|
|
159
|
+
assert "TextAdFieldNames" in body["params"]
|
|
160
|
+
|
|
161
|
+
|
|
118
162
|
# ----------------------------------------------------------------------
|
|
119
163
|
# adgroups
|
|
120
164
|
# ----------------------------------------------------------------------
|
|
@@ -157,6 +157,35 @@ class TestReadOnlyAds(unittest.TestCase):
|
|
|
157
157
|
)
|
|
158
158
|
assert_success(result, "ads get")
|
|
159
159
|
|
|
160
|
+
def test_get_ads_returns_textad(self):
|
|
161
|
+
"""TEXT_AD ads must include TextAd with Title and Text."""
|
|
162
|
+
if not self.campaign_id:
|
|
163
|
+
self.skipTest("No campaigns found in account")
|
|
164
|
+
result = invoke_get(
|
|
165
|
+
"ads",
|
|
166
|
+
"get",
|
|
167
|
+
"--campaign-ids",
|
|
168
|
+
str(self.campaign_id),
|
|
169
|
+
"--limit",
|
|
170
|
+
"50",
|
|
171
|
+
"--format",
|
|
172
|
+
"json",
|
|
173
|
+
)
|
|
174
|
+
assert_success(result, "ads get (TextAd check)")
|
|
175
|
+
data = json.loads(result.output)
|
|
176
|
+
text_ads = [ad for ad in data if ad.get("Type") == "TEXT_AD"]
|
|
177
|
+
if not text_ads:
|
|
178
|
+
self.skipTest("No TEXT_AD ads found in first 50 results")
|
|
179
|
+
for ad in text_ads:
|
|
180
|
+
self.assertIn(
|
|
181
|
+
"TextAd",
|
|
182
|
+
ad,
|
|
183
|
+
f"TEXT_AD {ad['Id']} missing TextAd — "
|
|
184
|
+
"TextAdFieldNames may not be sent",
|
|
185
|
+
)
|
|
186
|
+
self.assertIn("Title", ad["TextAd"])
|
|
187
|
+
self.assertIn("Text", ad["TextAd"])
|
|
188
|
+
|
|
160
189
|
|
|
161
190
|
@pytest.mark.integration
|
|
162
191
|
@skip_if_no_token
|
|
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
|