direct-cli 0.2.5__tar.gz → 0.2.6__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.
Files changed (126) hide show
  1. direct_cli-0.2.6/.github/workflows/api-coverage.yml +153 -0
  2. {direct_cli-0.2.5 → direct_cli-0.2.6}/PKG-INFO +32 -1
  3. {direct_cli-0.2.5 → direct_cli-0.2.6}/README.md +28 -0
  4. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/api.py +18 -5
  5. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/cli.py +2 -0
  6. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/adextensions.py +11 -6
  7. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/adgroups.py +11 -6
  8. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/adimages.py +11 -6
  9. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/ads.py +51 -21
  10. direct_cli-0.2.5/direct_cli/commands/agencyclients.py → direct_cli-0.2.6/direct_cli/commands/advideos.py +37 -33
  11. direct_cli-0.2.6/direct_cli/commands/agencyclients.py +217 -0
  12. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/audiencetargets.py +84 -26
  13. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/bidmodifiers.py +26 -12
  14. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/bids.py +78 -5
  15. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/campaigns.py +55 -30
  16. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/creatives.py +29 -3
  17. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/dictionaries.py +30 -0
  18. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/dynamicads.py +89 -23
  19. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/feeds.py +8 -3
  20. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/keywordbids.py +39 -5
  21. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/keywords.py +55 -30
  22. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/keywordsresearch.py +0 -30
  23. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/negativekeywordsharedsets.py +8 -3
  24. direct_cli-0.2.6/direct_cli/commands/reports.py +387 -0
  25. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/retargeting.py +46 -15
  26. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/sitelinks.py +8 -3
  27. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/smartadtargets.py +129 -32
  28. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/vcards.py +8 -3
  29. direct_cli-0.2.6/direct_cli/reports_coverage.py +242 -0
  30. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/utils.py +1 -0
  31. direct_cli-0.2.6/direct_cli/wsdl_coverage.py +347 -0
  32. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli.egg-info/PKG-INFO +32 -1
  33. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli.egg-info/SOURCES.txt +45 -1
  34. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli.egg-info/requires.txt +3 -0
  35. direct_cli-0.2.6/docs/superpowers/plans/2026-04-12-issue-32-completion.md +548 -0
  36. {direct_cli-0.2.5 → direct_cli-0.2.6}/pyproject.toml +5 -1
  37. direct_cli-0.2.6/scripts/build_api_coverage_report.py +109 -0
  38. direct_cli-0.2.6/scripts/check_reports_drift.py +136 -0
  39. direct_cli-0.2.6/scripts/check_wsdl_drift.py +62 -0
  40. direct_cli-0.2.6/scripts/refresh_reports_cache.py +44 -0
  41. direct_cli-0.2.6/scripts/refresh_wsdl_cache.py +39 -0
  42. direct_cli-0.2.6/tests/reports_cache/raw/fields-list.html +3316 -0
  43. direct_cli-0.2.6/tests/reports_cache/raw/headers.html +88 -0
  44. direct_cli-0.2.6/tests/reports_cache/raw/spec.html +119 -0
  45. direct_cli-0.2.6/tests/reports_cache/raw/type.html +279 -0
  46. direct_cli-0.2.6/tests/reports_cache/spec.json +583 -0
  47. direct_cli-0.2.6/tests/test_api_coverage.py +1176 -0
  48. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/test_comprehensive.py +1 -0
  49. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/test_dry_run.py +341 -27
  50. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/test_integration.py +61 -0
  51. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/test_integration_write.py +3 -10
  52. direct_cli-0.2.6/tests/test_reports_drift.py +102 -0
  53. direct_cli-0.2.6/tests/wsdl_cache/adextensions.xml +189 -0
  54. direct_cli-0.2.6/tests/wsdl_cache/adgroups.xml +551 -0
  55. direct_cli-0.2.6/tests/wsdl_cache/adimages.xml +270 -0
  56. direct_cli-0.2.6/tests/wsdl_cache/ads.xml +1622 -0
  57. direct_cli-0.2.6/tests/wsdl_cache/advideos.xml +153 -0
  58. direct_cli-0.2.6/tests/wsdl_cache/agencyclients.xml +310 -0
  59. direct_cli-0.2.6/tests/wsdl_cache/audiencetargets.xml +339 -0
  60. direct_cli-0.2.6/tests/wsdl_cache/bidmodifiers.xml +536 -0
  61. direct_cli-0.2.6/tests/wsdl_cache/bids.xml +262 -0
  62. direct_cli-0.2.6/tests/wsdl_cache/businesses.xml +106 -0
  63. direct_cli-0.2.6/tests/wsdl_cache/campaigns.xml +2809 -0
  64. direct_cli-0.2.6/tests/wsdl_cache/changes.xml +190 -0
  65. direct_cli-0.2.6/tests/wsdl_cache/clients.xml +132 -0
  66. direct_cli-0.2.6/tests/wsdl_cache/creatives.xml +239 -0
  67. direct_cli-0.2.6/tests/wsdl_cache/dictionaries.xml +327 -0
  68. direct_cli-0.2.6/tests/wsdl_cache/dynamictextadtargets.xml +338 -0
  69. direct_cli-0.2.6/tests/wsdl_cache/feeds.xml +339 -0
  70. direct_cli-0.2.6/tests/wsdl_cache/keywordbids.xml +291 -0
  71. direct_cli-0.2.6/tests/wsdl_cache/keywords.xml +362 -0
  72. direct_cli-0.2.6/tests/wsdl_cache/keywordsresearch.xml +174 -0
  73. direct_cli-0.2.6/tests/wsdl_cache/leads.xml +107 -0
  74. direct_cli-0.2.6/tests/wsdl_cache/negativekeywordsharedsets.xml +232 -0
  75. direct_cli-0.2.6/tests/wsdl_cache/retargetinglists.xml +283 -0
  76. direct_cli-0.2.6/tests/wsdl_cache/sitelinks.xml +189 -0
  77. direct_cli-0.2.6/tests/wsdl_cache/smartadtargets.xml +395 -0
  78. direct_cli-0.2.6/tests/wsdl_cache/turbopages.xml +103 -0
  79. direct_cli-0.2.6/tests/wsdl_cache/vcards.xml +238 -0
  80. direct_cli-0.2.5/direct_cli/commands/reports.py +0 -133
  81. {direct_cli-0.2.5 → direct_cli-0.2.6}/.env.example +0 -0
  82. {direct_cli-0.2.5 → direct_cli-0.2.6}/.github/copilot-instructions.md +0 -0
  83. {direct_cli-0.2.5 → direct_cli-0.2.6}/.github/workflows/claude-code-review.yml +0 -0
  84. {direct_cli-0.2.5 → direct_cli-0.2.6}/.github/workflows/claude.yml +0 -0
  85. {direct_cli-0.2.5 → direct_cli-0.2.6}/.gitignore +0 -0
  86. {direct_cli-0.2.5 → direct_cli-0.2.6}/AGENTS.md +0 -0
  87. {direct_cli-0.2.5 → direct_cli-0.2.6}/CLAUDE.md +0 -0
  88. {direct_cli-0.2.5 → direct_cli-0.2.6}/MANIFEST.in +0 -0
  89. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/__init__.py +0 -0
  90. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/auth.py +0 -0
  91. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/__init__.py +0 -0
  92. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/businesses.py +0 -0
  93. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/changes.py +0 -0
  94. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/clients.py +0 -0
  95. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/leads.py +0 -0
  96. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/commands/turbopages.py +0 -0
  97. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli/output.py +0 -0
  98. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli.egg-info/dependency_links.txt +0 -0
  99. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli.egg-info/entry_points.txt +0 -0
  100. {direct_cli-0.2.5 → direct_cli-0.2.6}/direct_cli.egg-info/top_level.txt +0 -0
  101. {direct_cli-0.2.5 → direct_cli-0.2.6}/scripts/release_pypi.sh +0 -0
  102. {direct_cli-0.2.5 → direct_cli-0.2.6}/setup.cfg +0 -0
  103. {direct_cli-0.2.5 → direct_cli-0.2.6}/setup.py +0 -0
  104. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/__init__.py +0 -0
  105. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  106. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  107. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  108. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  109. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  110. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  111. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  112. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  113. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  114. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  115. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  116. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  117. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  118. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  119. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  120. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  121. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  122. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  123. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/conftest.py +0 -0
  124. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/test_auth_bw.py +0 -0
  125. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/test_auth_op.py +0 -0
  126. {direct_cli-0.2.5 → direct_cli-0.2.6}/tests/test_cli.py +0 -0
@@ -0,0 +1,153 @@
1
+ name: API Coverage
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "0 6 * * 1"
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ test-and-report:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python-version: ["3.9", "3.11", "3.13"]
18
+ steps:
19
+ - name: Checkout repository
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+
27
+ - name: Install package with dev dependencies
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -e ".[dev]"
31
+
32
+ - name: Run fast coverage suites
33
+ run: |
34
+ pytest -q tests/test_api_coverage.py tests/test_dry_run.py tests/test_cli.py tests/test_comprehensive.py tests/test_reports_drift.py
35
+
36
+ - name: Build API coverage report
37
+ run: |
38
+ python scripts/build_api_coverage_report.py > api_coverage_report.json
39
+
40
+ - name: Publish API coverage summary
41
+ run: |
42
+ python - <<'PY' >> "$GITHUB_STEP_SUMMARY"
43
+ import json
44
+ from pathlib import Path
45
+
46
+ report = json.loads(Path("api_coverage_report.json").read_text())
47
+ summary = report["summary"]
48
+ print("## API coverage summary")
49
+ print()
50
+ print(f"- Services checked: {summary['services_checked']}")
51
+ print(f"- Missing methods: {summary['missing_service_methods']}")
52
+ print(f"- Unexpected methods: {summary['unexpected_service_methods']}")
53
+ print(f"- Strict parity OK: {summary['strict_parity_ok']}")
54
+ print(f"- Alias groups: {len(report['aliases'])}")
55
+ print(f"- Non-WSDL services: {len(report['non_wsdl_services'])}")
56
+ print(f"- CLI helpers: {len(report['cli_helpers'])}")
57
+ PY
58
+
59
+ - name: Upload API coverage report
60
+ uses: actions/upload-artifact@v4
61
+ with:
62
+ name: api-coverage-report-py${{ matrix.python-version }}
63
+ path: api_coverage_report.json
64
+
65
+ monitor-live-wsdl:
66
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
67
+ runs-on: ubuntu-latest
68
+ steps:
69
+ - name: Checkout repository
70
+ uses: actions/checkout@v4
71
+
72
+ - name: Set up Python
73
+ uses: actions/setup-python@v5
74
+ with:
75
+ python-version: "3.11"
76
+
77
+ - name: Install package with dev dependencies
78
+ run: |
79
+ python -m pip install --upgrade pip
80
+ pip install -e ".[dev]"
81
+
82
+ - name: Check live WSDL drift
83
+ run: |
84
+ python scripts/check_wsdl_drift.py > wsdl_drift_report.json
85
+
86
+ - name: Publish WSDL drift summary
87
+ if: always()
88
+ run: |
89
+ python - <<'PY' >> "$GITHUB_STEP_SUMMARY"
90
+ import json
91
+ from pathlib import Path
92
+
93
+ report = json.loads(Path("wsdl_drift_report.json").read_text())
94
+ print("## WSDL drift summary")
95
+ print()
96
+ print(f"- Services checked: {report['services_checked']}")
97
+ print(f"- Missing cache files: {report['missing_cache_count']}")
98
+ print(f"- Drifted services: {report['drift_count']}")
99
+ for item in report["drift"][:10]:
100
+ print(f"- Drift: {item['service']}")
101
+ PY
102
+
103
+ - name: Upload WSDL drift report
104
+ if: always()
105
+ uses: actions/upload-artifact@v4
106
+ with:
107
+ name: wsdl-drift-report
108
+ path: wsdl_drift_report.json
109
+
110
+ monitor-live-reports:
111
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
112
+ runs-on: ubuntu-latest
113
+ steps:
114
+ - name: Checkout repository
115
+ uses: actions/checkout@v4
116
+
117
+ - name: Set up Python
118
+ uses: actions/setup-python@v5
119
+ with:
120
+ python-version: "3.11"
121
+
122
+ - name: Install package with dev dependencies
123
+ run: |
124
+ python -m pip install --upgrade pip
125
+ pip install -e ".[dev]"
126
+
127
+ - name: Check live reports drift
128
+ run: |
129
+ python scripts/check_reports_drift.py > reports_drift_report.json
130
+
131
+ - name: Publish reports drift summary
132
+ if: always()
133
+ run: |
134
+ python - <<'PY' >> "$GITHUB_STEP_SUMMARY"
135
+ import json
136
+ from pathlib import Path
137
+
138
+ report = json.loads(Path("reports_drift_report.json").read_text())
139
+ print("## Reports drift summary")
140
+ print()
141
+ print(f"- Sources checked: {report.get('sources_checked', 0)}")
142
+ print(f"- Missing cache files: {report.get('missing_cache_count', 0)}")
143
+ print(f"- Drifted sections: {report.get('drift_count', 0)}")
144
+ for item in report.get("drift", [])[:10]:
145
+ print(f"- Drift: {item['section']}")
146
+ PY
147
+
148
+ - name: Upload reports drift report
149
+ if: always()
150
+ uses: actions/upload-artifact@v4
151
+ with:
152
+ name: reports-drift-report
153
+ path: reports_drift_report.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: direct-cli
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Command-line interface for Yandex Direct API
5
5
  Author: axisrow
6
6
  License: MIT
@@ -31,6 +31,9 @@ Requires-Dist: pytest-recording>=0.13; extra == "dev"
31
31
  Requires-Dist: vcrpy>=6.0; extra == "dev"
32
32
  Requires-Dist: black>=22.0; extra == "dev"
33
33
  Requires-Dist: flake8>=4.0; extra == "dev"
34
+ Requires-Dist: requests>=2.0; extra == "dev"
35
+ Requires-Dist: beautifulsoup4>=4.12; extra == "dev"
36
+ Requires-Dist: lxml>=4.9; extra == "dev"
34
37
 
35
38
  # Direct CLI
36
39
 
@@ -231,6 +234,34 @@ pytest -m integration -v # read-only integration tests (needs token)
231
234
  pytest -m integration_write -v # write cassette replay (no token needed)
232
235
  ```
233
236
 
237
+ ### API Coverage And Drift Monitoring
238
+
239
+ The project now distinguishes four surfaces:
240
+
241
+ | Surface | Coverage strategy |
242
+ |---|---|
243
+ | Canonical WSDL-backed SOAP services | `tests/test_api_coverage.py` verifies strict service/method parity and dry-run request-schema coverage or explicit exclusions |
244
+ | Non-WSDL services (`reports`) | Explicit contract tests |
245
+ | Canonical CLI aliases | Checked as aliases, not counted as separate API surface |
246
+ | Intentional CLI-only helpers | Explicitly allowlisted with reasons in `direct_cli/wsdl_coverage.py` |
247
+
248
+ `100% coverage` in this project means full coverage of the supported
249
+ **canonical API surface**. Alias groups and CLI-only helpers remain supported,
250
+ but they are tracked outside the strict parity metric.
251
+
252
+ Useful maintenance commands:
253
+
254
+ ```bash
255
+ python scripts/build_api_coverage_report.py
256
+ python scripts/refresh_wsdl_cache.py
257
+ python scripts/check_wsdl_drift.py
258
+ ```
259
+
260
+ CI runs a scheduled API coverage workflow that:
261
+ - runs the fast coverage suites;
262
+ - uploads a machine-readable API coverage report artifact;
263
+ - checks the cached WSDL files against the live Yandex Direct API on schedule.
264
+
234
265
  #### Re-recording write cassettes
235
266
 
236
267
  The write tests replay HTTP traffic captured from the Yandex Direct **sandbox**
@@ -197,6 +197,34 @@ pytest -m integration -v # read-only integration tests (needs token)
197
197
  pytest -m integration_write -v # write cassette replay (no token needed)
198
198
  ```
199
199
 
200
+ ### API Coverage And Drift Monitoring
201
+
202
+ The project now distinguishes four surfaces:
203
+
204
+ | Surface | Coverage strategy |
205
+ |---|---|
206
+ | Canonical WSDL-backed SOAP services | `tests/test_api_coverage.py` verifies strict service/method parity and dry-run request-schema coverage or explicit exclusions |
207
+ | Non-WSDL services (`reports`) | Explicit contract tests |
208
+ | Canonical CLI aliases | Checked as aliases, not counted as separate API surface |
209
+ | Intentional CLI-only helpers | Explicitly allowlisted with reasons in `direct_cli/wsdl_coverage.py` |
210
+
211
+ `100% coverage` in this project means full coverage of the supported
212
+ **canonical API surface**. Alias groups and CLI-only helpers remain supported,
213
+ but they are tracked outside the strict parity metric.
214
+
215
+ Useful maintenance commands:
216
+
217
+ ```bash
218
+ python scripts/build_api_coverage_report.py
219
+ python scripts/refresh_wsdl_cache.py
220
+ python scripts/check_wsdl_drift.py
221
+ ```
222
+
223
+ CI runs a scheduled API coverage workflow that:
224
+ - runs the fast coverage suites;
225
+ - uploads a machine-readable API coverage report artifact;
226
+ - checks the cached WSDL files against the live Yandex Direct API on schedule.
227
+
200
228
  #### Re-recording write cassettes
201
229
 
202
230
  The write tests replay HTTP traffic captured from the Yandex Direct **sandbox**
@@ -14,6 +14,12 @@ def create_client(
14
14
  sandbox: bool = False,
15
15
  op_token_ref: Optional[str] = None,
16
16
  op_login_ref: Optional[str] = None,
17
+ processing_mode: str = "auto",
18
+ return_money_in_micros: bool = False,
19
+ skip_report_header: bool = True,
20
+ skip_column_header: bool = False,
21
+ skip_report_summary: bool = True,
22
+ language: Optional[str] = None,
17
23
  ) -> YandexDirect:
18
24
  """
19
25
  Create YandexDirect client
@@ -24,6 +30,12 @@ def create_client(
24
30
  sandbox: Use sandbox API
25
31
  op_token_ref: 1Password secret reference for token
26
32
  op_login_ref: 1Password secret reference for login
33
+ processing_mode: Report processing mode (auto/online/offline)
34
+ return_money_in_micros: Return monetary values in micros
35
+ skip_report_header: Omit report header row
36
+ skip_column_header: Omit column header row
37
+ skip_report_summary: Omit report summary row
38
+ language: Accept-Language for report (ru/en)
27
39
 
28
40
  Returns:
29
41
  YandexDirect client instance
@@ -39,12 +51,13 @@ def create_client(
39
51
  retry_if_exceeded_limit=True,
40
52
  retries_if_server_error=5,
41
53
  # Report settings
42
- processing_mode="auto",
54
+ processing_mode=processing_mode,
43
55
  wait_report=True,
44
- return_money_in_micros=False,
45
- skip_report_header=True,
46
- skip_column_header=False,
47
- skip_report_summary=True,
56
+ return_money_in_micros=return_money_in_micros,
57
+ skip_report_header=skip_report_header,
58
+ skip_column_header=skip_column_header,
59
+ skip_report_summary=skip_report_summary,
60
+ language=language,
48
61
  )
49
62
 
50
63
 
@@ -35,6 +35,7 @@ from .commands.smartadtargets import smartadtargets
35
35
  from .commands.businesses import businesses
36
36
  from .commands.keywordsresearch import keywordsresearch
37
37
  from .commands.dynamicads import dynamicads
38
+ from .commands.advideos import advideos
38
39
 
39
40
  # Load .env file
40
41
  load_dotenv()
@@ -133,6 +134,7 @@ cli.add_command(smartadtargets)
133
134
  cli.add_command(businesses)
134
135
  cli.add_command(keywordsresearch)
135
136
  cli.add_command(dynamicads)
137
+ cli.add_command(advideos)
136
138
 
137
139
  # Canonical aliases expected by external integrations.
138
140
  cli.add_command(dynamicads, name="dynamictargets")
@@ -117,21 +117,26 @@ def add(ctx, ext_type, extra_json, dry_run):
117
117
 
118
118
  @adextensions.command()
119
119
  @click.option("--id", "extension_id", required=True, type=int, help="Extension ID")
120
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
120
121
  @click.pass_context
121
- def delete(ctx, extension_id):
122
+ def delete(ctx, extension_id, dry_run):
122
123
  """Delete ad extension"""
123
124
  try:
125
+ body = {
126
+ "method": "delete",
127
+ "params": {"SelectionCriteria": {"Ids": [extension_id]}},
128
+ }
129
+
130
+ if dry_run:
131
+ format_output(body, "json", None)
132
+ return
133
+
124
134
  client = create_client(
125
135
  token=ctx.obj.get("token"),
126
136
  login=ctx.obj.get("login"),
127
137
  sandbox=ctx.obj.get("sandbox"),
128
138
  )
129
139
 
130
- body = {
131
- "method": "delete",
132
- "params": {"SelectionCriteria": {"Ids": [extension_id]}},
133
- }
134
-
135
140
  result = client.adextensions().post(data=body)
136
141
  format_output(result().extract(), "json", None)
137
142
 
@@ -196,21 +196,26 @@ def update(ctx, adgroup_id, name, status, extra_json, dry_run):
196
196
 
197
197
  @adgroups.command()
198
198
  @click.option("--id", "adgroup_id", required=True, type=int, help="Ad group ID")
199
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
199
200
  @click.pass_context
200
- def delete(ctx, adgroup_id):
201
+ def delete(ctx, adgroup_id, dry_run):
201
202
  """Delete ad group"""
202
203
  try:
204
+ body = {
205
+ "method": "delete",
206
+ "params": {"SelectionCriteria": {"Ids": [adgroup_id]}},
207
+ }
208
+
209
+ if dry_run:
210
+ format_output(body, "json", None)
211
+ return
212
+
203
213
  client = create_client(
204
214
  token=ctx.obj.get("token"),
205
215
  login=ctx.obj.get("login"),
206
216
  sandbox=ctx.obj.get("sandbox"),
207
217
  )
208
218
 
209
- body = {
210
- "method": "delete",
211
- "params": {"SelectionCriteria": {"Ids": [adgroup_id]}},
212
- }
213
-
214
219
  result = client.adgroups().post(data=body)
215
220
  format_output(result().extract(), "json", None)
216
221
 
@@ -92,21 +92,26 @@ def add(ctx, image_json, dry_run):
92
92
 
93
93
  @adimages.command()
94
94
  @click.option("--hash", "image_hash", required=True, help="Ad image hash")
95
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
95
96
  @click.pass_context
96
- def delete(ctx, image_hash):
97
+ def delete(ctx, image_hash, dry_run):
97
98
  """Delete ad image"""
98
99
  try:
100
+ body = {
101
+ "method": "delete",
102
+ "params": {"SelectionCriteria": {"AdImageHashes": [image_hash]}},
103
+ }
104
+
105
+ if dry_run:
106
+ format_output(body, "json", None)
107
+ return
108
+
99
109
  client = create_client(
100
110
  token=ctx.obj.get("token"),
101
111
  login=ctx.obj.get("login"),
102
112
  sandbox=ctx.obj.get("sandbox"),
103
113
  )
104
114
 
105
- body = {
106
- "method": "delete",
107
- "params": {"SelectionCriteria": {"AdImageHashes": [image_hash]}},
108
- }
109
-
110
115
  result = client.adimages().post(data=body)
111
116
  format_output(result().extract(), "json", None)
112
117
 
@@ -221,18 +221,23 @@ def update(ctx, ad_id, status, extra_json, dry_run):
221
221
 
222
222
  @ads.command()
223
223
  @click.option("--id", "ad_id", required=True, type=int, help="Ad ID")
224
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
224
225
  @click.pass_context
225
- def delete(ctx, ad_id):
226
+ def delete(ctx, ad_id, dry_run):
226
227
  """Delete ad"""
227
228
  try:
229
+ body = {"method": "delete", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
230
+
231
+ if dry_run:
232
+ format_output(body, "json", None)
233
+ return
234
+
228
235
  client = create_client(
229
236
  token=ctx.obj.get("token"),
230
237
  login=ctx.obj.get("login"),
231
238
  sandbox=ctx.obj.get("sandbox"),
232
239
  )
233
240
 
234
- body = {"method": "delete", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
235
-
236
241
  result = client.ads().post(data=body)
237
242
  format_output(result().extract(), "json", None)
238
243
 
@@ -243,18 +248,23 @@ def delete(ctx, ad_id):
243
248
 
244
249
  @ads.command()
245
250
  @click.option("--id", "ad_id", required=True, type=int, help="Ad ID")
251
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
246
252
  @click.pass_context
247
- def archive(ctx, ad_id):
253
+ def archive(ctx, ad_id, dry_run):
248
254
  """Archive ad"""
249
255
  try:
256
+ body = {"method": "archive", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
257
+
258
+ if dry_run:
259
+ format_output(body, "json", None)
260
+ return
261
+
250
262
  client = create_client(
251
263
  token=ctx.obj.get("token"),
252
264
  login=ctx.obj.get("login"),
253
265
  sandbox=ctx.obj.get("sandbox"),
254
266
  )
255
267
 
256
- body = {"method": "archive", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
257
-
258
268
  result = client.ads().post(data=body)
259
269
  format_output(result().extract(), "json", None)
260
270
 
@@ -265,21 +275,26 @@ def archive(ctx, ad_id):
265
275
 
266
276
  @ads.command()
267
277
  @click.option("--id", "ad_id", required=True, type=int, help="Ad ID")
278
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
268
279
  @click.pass_context
269
- def unarchive(ctx, ad_id):
280
+ def unarchive(ctx, ad_id, dry_run):
270
281
  """Unarchive ad"""
271
282
  try:
283
+ body = {
284
+ "method": "unarchive",
285
+ "params": {"SelectionCriteria": {"Ids": [ad_id]}},
286
+ }
287
+
288
+ if dry_run:
289
+ format_output(body, "json", None)
290
+ return
291
+
272
292
  client = create_client(
273
293
  token=ctx.obj.get("token"),
274
294
  login=ctx.obj.get("login"),
275
295
  sandbox=ctx.obj.get("sandbox"),
276
296
  )
277
297
 
278
- body = {
279
- "method": "unarchive",
280
- "params": {"SelectionCriteria": {"Ids": [ad_id]}},
281
- }
282
-
283
298
  result = client.ads().post(data=body)
284
299
  format_output(result().extract(), "json", None)
285
300
 
@@ -290,18 +305,23 @@ def unarchive(ctx, ad_id):
290
305
 
291
306
  @ads.command()
292
307
  @click.option("--id", "ad_id", required=True, type=int, help="Ad ID")
308
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
293
309
  @click.pass_context
294
- def suspend(ctx, ad_id):
310
+ def suspend(ctx, ad_id, dry_run):
295
311
  """Suspend ad"""
296
312
  try:
313
+ body = {"method": "suspend", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
314
+
315
+ if dry_run:
316
+ format_output(body, "json", None)
317
+ return
318
+
297
319
  client = create_client(
298
320
  token=ctx.obj.get("token"),
299
321
  login=ctx.obj.get("login"),
300
322
  sandbox=ctx.obj.get("sandbox"),
301
323
  )
302
324
 
303
- body = {"method": "suspend", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
304
-
305
325
  result = client.ads().post(data=body)
306
326
  format_output(result().extract(), "json", None)
307
327
 
@@ -312,18 +332,23 @@ def suspend(ctx, ad_id):
312
332
 
313
333
  @ads.command()
314
334
  @click.option("--id", "ad_id", required=True, type=int, help="Ad ID")
335
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
315
336
  @click.pass_context
316
- def resume(ctx, ad_id):
337
+ def resume(ctx, ad_id, dry_run):
317
338
  """Resume ad"""
318
339
  try:
340
+ body = {"method": "resume", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
341
+
342
+ if dry_run:
343
+ format_output(body, "json", None)
344
+ return
345
+
319
346
  client = create_client(
320
347
  token=ctx.obj.get("token"),
321
348
  login=ctx.obj.get("login"),
322
349
  sandbox=ctx.obj.get("sandbox"),
323
350
  )
324
351
 
325
- body = {"method": "resume", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
326
-
327
352
  result = client.ads().post(data=body)
328
353
  format_output(result().extract(), "json", None)
329
354
 
@@ -334,18 +359,23 @@ def resume(ctx, ad_id):
334
359
 
335
360
  @ads.command()
336
361
  @click.option("--id", "ad_id", required=True, type=int, help="Ad ID")
362
+ @click.option("--dry-run", is_flag=True, help="Show request without sending")
337
363
  @click.pass_context
338
- def moderate(ctx, ad_id):
364
+ def moderate(ctx, ad_id, dry_run):
339
365
  """Moderate ad"""
340
366
  try:
367
+ body = {"method": "moderate", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
368
+
369
+ if dry_run:
370
+ format_output(body, "json", None)
371
+ return
372
+
341
373
  client = create_client(
342
374
  token=ctx.obj.get("token"),
343
375
  login=ctx.obj.get("login"),
344
376
  sandbox=ctx.obj.get("sandbox"),
345
377
  )
346
378
 
347
- body = {"method": "moderate", "params": {"SelectionCriteria": {"Ids": [ad_id]}}}
348
-
349
379
  result = client.ads().post(data=body)
350
380
  format_output(result().extract(), "json", None)
351
381