direct-cli 0.3.13__tar.gz → 0.3.14__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 (238) hide show
  1. {direct_cli-0.3.13 → direct_cli-0.3.14}/CHANGELOG.md +253 -0
  2. {direct_cli-0.3.13 → direct_cli-0.3.14}/CLAUDE.md +30 -0
  3. {direct_cli-0.3.13 → direct_cli-0.3.14}/PKG-INFO +1 -1
  4. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_bidding_strategy.py +0 -5
  5. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +8 -8
  6. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/adgroups.py +117 -0
  7. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/ads.py +193 -7
  8. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/agencyclients.py +85 -1
  9. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/bidmodifiers.py +153 -1
  10. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/campaigns.py +113 -50
  11. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/clients.py +85 -1
  12. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/creatives.py +71 -2
  13. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/feeds.py +47 -2
  14. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/keywordbids.py +61 -3
  15. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/keywords.py +59 -1
  16. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/sitelinks.py +37 -4
  17. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/strategies.py +256 -10
  18. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/reports_coverage.py +23 -1
  19. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/utils.py +35 -1
  20. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/wsdl_coverage.py +57 -1
  21. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli.egg-info/PKG-INFO +1 -1
  22. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli.egg-info/SOURCES.txt +1 -0
  23. {direct_cli-0.3.13 → direct_cli-0.3.14}/pyproject.toml +1 -1
  24. direct_cli-0.3.14/scripts/check_all_docs_urls.py +166 -0
  25. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/release_pypi.sh +8 -0
  26. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/api_coverage_payloads.py +9 -6
  27. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/reports_cache/raw/fields-list.html +6 -6
  28. direct_cli-0.3.14/tests/reports_cache/raw/headers.html +88 -0
  29. direct_cli-0.3.14/tests/reports_cache/raw/period.html +119 -0
  30. direct_cli-0.3.14/tests/reports_cache/raw/spec.html +643 -0
  31. direct_cli-0.3.14/tests/reports_cache/raw/type.html +279 -0
  32. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/reports_cache/spec.json +1 -1
  33. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_api_coverage.py +191 -19
  34. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_comprehensive.py +7 -20
  35. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_dry_run.py +1263 -117
  36. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_integration.py +84 -9
  37. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_low_coverage_payloads.py +1 -1
  38. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_sandbox_write_audit.py +7 -3
  39. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_smoke_matrix.py +49 -7
  40. direct_cli-0.3.13/tests/reports_cache/raw/headers.html +0 -88
  41. direct_cli-0.3.13/tests/reports_cache/raw/period.html +0 -119
  42. direct_cli-0.3.13/tests/reports_cache/raw/spec.html +0 -660
  43. direct_cli-0.3.13/tests/reports_cache/raw/type.html +0 -279
  44. {direct_cli-0.3.13 → direct_cli-0.3.14}/.env.example +0 -0
  45. {direct_cli-0.3.13 → direct_cli-0.3.14}/.github/copilot-instructions.md +0 -0
  46. {direct_cli-0.3.13 → direct_cli-0.3.14}/.github/workflows/api-coverage.yml +0 -0
  47. {direct_cli-0.3.13 → direct_cli-0.3.14}/.github/workflows/claude.yml +0 -0
  48. {direct_cli-0.3.13 → direct_cli-0.3.14}/.github/workflows/quality.yml +0 -0
  49. {direct_cli-0.3.13 → direct_cli-0.3.14}/.gitignore +0 -0
  50. {direct_cli-0.3.13 → direct_cli-0.3.14}/AGENTS.md +0 -0
  51. {direct_cli-0.3.13 → direct_cli-0.3.14}/MANIFEST.in +0 -0
  52. {direct_cli-0.3.13 → direct_cli-0.3.14}/README.md +0 -0
  53. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/__init__.py +0 -0
  54. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_deprecated.py +0 -0
  55. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_smoke_probes.py +0 -0
  56. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/__init__.py +0 -0
  57. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
  58. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
  59. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
  60. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
  61. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
  62. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
  63. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
  64. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
  65. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
  66. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/api.py +0 -0
  67. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/auth.py +0 -0
  68. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/cli.py +0 -0
  69. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/__init__.py +0 -0
  70. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/adextensions.py +0 -0
  71. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/adimages.py +0 -0
  72. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/advideos.py +0 -0
  73. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/audiencetargets.py +0 -0
  74. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/auth.py +0 -0
  75. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/balance.py +0 -0
  76. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/bids.py +0 -0
  77. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/businesses.py +0 -0
  78. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/changes.py +0 -0
  79. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/dictionaries.py +0 -0
  80. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/dynamicads.py +0 -0
  81. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
  82. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/keywordsresearch.py +0 -0
  83. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/leads.py +0 -0
  84. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
  85. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/reports.py +0 -0
  86. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/retargeting.py +0 -0
  87. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/smartadtargets.py +0 -0
  88. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/turbopages.py +0 -0
  89. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/v4account.py +0 -0
  90. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/v4events.py +0 -0
  91. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/v4finance.py +0 -0
  92. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/v4forecast.py +0 -0
  93. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/v4goals.py +0 -0
  94. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/v4shells.py +0 -0
  95. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/v4tags.py +0 -0
  96. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/v4wordstat.py +0 -0
  97. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/commands/vcards.py +0 -0
  98. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/output.py +0 -0
  99. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/smoke_matrix.py +0 -0
  100. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/v4/__init__.py +0 -0
  101. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/v4/money.py +0 -0
  102. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli/v4_contracts.py +0 -0
  103. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli.egg-info/dependency_links.txt +0 -0
  104. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli.egg-info/entry_points.txt +0 -0
  105. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli.egg-info/requires.txt +0 -0
  106. {direct_cli-0.3.13 → direct_cli-0.3.14}/direct_cli.egg-info/top_level.txt +0 -0
  107. {direct_cli-0.3.13 → direct_cli-0.3.14}/docs/audits/issue-198-mutating-wsdl-audit.md +0 -0
  108. {direct_cli-0.3.13 → direct_cli-0.3.14}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
  109. {direct_cli-0.3.13 → direct_cli-0.3.14}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
  110. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/anonymize_cassettes.py +0 -0
  111. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/build_api_coverage_checklist.py +0 -0
  112. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/build_api_coverage_report.py +0 -0
  113. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/build_wsdl_optional_field_audit.py +0 -0
  114. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/check_reports_drift.py +0 -0
  115. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/check_wsdl_drift.py +0 -0
  116. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/patch_vendor_imports.py +0 -0
  117. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/refresh_reports_cache.py +0 -0
  118. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/refresh_wsdl_cache.py +0 -0
  119. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/sandbox_write_audit.py +0 -0
  120. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/sandbox_write_live.py +0 -0
  121. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/test_dangerous_commands.sh +0 -0
  122. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/test_safe_commands.sh +0 -0
  123. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/test_sandbox_write.sh +0 -0
  124. {direct_cli-0.3.13 → direct_cli-0.3.14}/scripts/update_vendor.sh +0 -0
  125. {direct_cli-0.3.13 → direct_cli-0.3.14}/setup.cfg +0 -0
  126. {direct_cli-0.3.13 → direct_cli-0.3.14}/setup.py +0 -0
  127. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/API_COVERAGE.md +0 -0
  128. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/API_ISSUE_AUDIT.md +0 -0
  129. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/MANUAL_COVERAGE.md +0 -0
  130. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/WSDL_OPTIONAL_FIELD_AUDIT.md +0 -0
  131. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/__init__.py +0 -0
  132. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/_orphan_store.py +0 -0
  133. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
  134. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
  135. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
  136. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
  137. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
  138. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
  139. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
  140. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
  141. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
  142. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
  143. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
  144. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
  145. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
  146. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
  147. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
  148. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
  149. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
  150. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
  151. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
  152. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
  153. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
  154. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
  155. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
  156. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
  157. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
  158. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
  159. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
  160. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
  161. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
  162. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
  163. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
  164. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
  165. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
  166. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
  167. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
  168. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
  169. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
  170. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
  171. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
  172. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
  173. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
  174. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/conftest.py +0 -0
  175. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/fixtures/test-video.mp4 +0 -0
  176. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_auth_bw.py +0 -0
  177. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_auth_oauth.py +0 -0
  178. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_auth_op.py +0 -0
  179. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_auth_write_json.py +0 -0
  180. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_balance.py +0 -0
  181. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_changes.py +0 -0
  182. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_cli.py +0 -0
  183. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_cli_contract.py +0 -0
  184. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_env_loading.py +0 -0
  185. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_integration_write.py +0 -0
  186. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_reports_drift.py +0 -0
  187. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_reports_parsing.py +0 -0
  188. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_transport_contract.py +0 -0
  189. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_unknown_option_hints.py +0 -0
  190. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4_contracts.py +0 -0
  191. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4_exit_codes.py +0 -0
  192. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4_foundation.py +0 -0
  193. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4_live_contracts.py +0 -0
  194. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4_runtime_shape.py +0 -0
  195. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4_safety.py +0 -0
  196. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4account.py +0 -0
  197. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4events.py +0 -0
  198. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4finance_money.py +0 -0
  199. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4finance_read.py +0 -0
  200. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4forecast.py +0 -0
  201. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4goals.py +0 -0
  202. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4tags.py +0 -0
  203. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v4wordstat.py +0 -0
  204. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_v5_live_write.py +0 -0
  205. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_vendor_imports.py +0 -0
  206. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/test_wsdl_parity_gate.py +0 -0
  207. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/adextensions.xml +0 -0
  208. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/adgroups.xml +0 -0
  209. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/adimages.xml +0 -0
  210. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/ads.xml +0 -0
  211. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/advideos.xml +0 -0
  212. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/agencyclients.xml +0 -0
  213. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/audiencetargets.xml +0 -0
  214. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/bidmodifiers.xml +0 -0
  215. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/bids.xml +0 -0
  216. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/businesses.xml +0 -0
  217. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/campaigns.xml +0 -0
  218. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/changes.xml +0 -0
  219. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/clients.xml +0 -0
  220. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/creatives.xml +0 -0
  221. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/dictionaries.xml +0 -0
  222. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
  223. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
  224. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/feeds.xml +0 -0
  225. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
  226. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/imports/general.xsd +0 -0
  227. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
  228. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/keywordbids.xml +0 -0
  229. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/keywords.xml +0 -0
  230. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/keywordsresearch.xml +0 -0
  231. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/leads.xml +0 -0
  232. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
  233. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/retargetinglists.xml +0 -0
  234. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/sitelinks.xml +0 -0
  235. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/smartadtargets.xml +0 -0
  236. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/strategies.xml +0 -0
  237. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/turbopages.xml +0 -0
  238. {direct_cli-0.3.13 → direct_cli-0.3.14}/tests/wsdl_cache/vcards.xml +0 -0
@@ -1,5 +1,258 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.14
4
+
5
+ **Fixed:**
6
+
7
+ - Reports drift checker now points at the canonical Yandex docs URLs
8
+ (`/ru/type`, `/ru/period`, `/ru/fields-list`, `/ru/spec`) after Yandex
9
+ retired the `/ru/reports/<page>` path layout and renamed `spec.html`
10
+ to `spec`. The pre-existing `tests/reports_cache/raw/` had silently
11
+ been captcha-poisoned for three of those pages (~14.6 KB Yandex
12
+ SmartCaptcha gateway in place of real docs); cache is now refetched
13
+ from the live canonical URLs and `spec.json` is byte-equivalent to
14
+ the pre-migration snapshot except for one updated description string.
15
+ - Five `RESOURCE_MAPPING_V5[*]["docs"]` URLs that Yandex moved from the
16
+ legacy `…/ru/<group>/<group>` to `…/ru/<group>` single-segment form
17
+ (`dynamictextadtargets`, `dynamicfeedadtargets`, `reports`,
18
+ `smartadtargets`, `vcards`). Closes #426.
19
+
20
+ **Added (drift protection):**
21
+
22
+ - `direct_cli/reports_coverage.py::fetch_reports_spec` and
23
+ `direct_cli/wsdl_coverage.py::fetch_wsdl` / `fetch_live_wsdl` now
24
+ refuse responses that look like a Yandex SmartCaptcha gateway (markers
25
+ `showcaptcha`, `smartcaptcha`, `<title>Captcha`) or are suspiciously
26
+ short. This prevents silently poisoning the docs/WSDL cache with
27
+ rate-limited captcha HTML.
28
+ - `tests/test_api_coverage.py::TestReportsCoverage::test_reports_cache_files_are_real_content`
29
+ and `TestWsdlCacheFreshness::test_wsdl_cache_files_are_real_content`
30
+ guard the committed cache files against the same poisoning.
31
+ - `scripts/check_all_docs_urls.py` — health-checks every URL in
32
+ `RESOURCE_MAPPING_V5` and `REPORTS_SPEC_URLS`. Hard-fails on
33
+ redirect-to-captcha, canonical move (`Location` with a different path
34
+ segment), 4xx, or captcha body; soft-warns on 5xx; paces requests to
35
+ avoid Yandex rate-limit. Wired into `scripts/release_pypi.sh` as a
36
+ mandatory pre-release gate together with `refresh_reports_cache.py`
37
+ and a focused pytest pass.
38
+
39
+ **Contract** (`CLAUDE.md`):
40
+
41
+ - New rule "No URL literals outside the registry" — every Yandex
42
+ docs/API URL is declared once in `RESOURCE_MAPPING_V5` or
43
+ `REPORTS_SPEC_URLS`; importers reference the constant.
44
+ - New rule "Docs/cache freshness guard" — fetchers and cache files
45
+ enforce minimum-size and no-captcha invariants.
46
+ - New section "PyPI Release" — documents the three pre-release health
47
+ checks executed by `release_pypi.sh`.
48
+
49
+ **Breaking changes:**
50
+
51
+ - `direct ads get` flag `--text-ad-fields` is **renamed** to the
52
+ WSDL-canonical `--text-ad-field-names` form matching the
53
+ `TextAdFieldNames` request parameter declared by `AdsGetRequest`.
54
+ The old `--text-ad-fields` form is no longer accepted — update
55
+ scripts and automation accordingly. Closes #406.
56
+ - `direct campaigns add` / `direct campaigns update` and `direct
57
+ strategies add` / `direct strategies update` now reject `--priority-goals`
58
+ / `--priority-goal` values below 100,000 (0.1 unit in micro-currency).
59
+ Per Yandex Direct API (add-text-campaign, strategies-types),
60
+ `PriorityGoalsItem.Value` is `xsd:long` in advertiser currency
61
+ multiplied by 1,000,000 — the same contract as `--budget`,
62
+ `--average-cpa`, and other money flags after #399/#400. The error
63
+ message suggests the micro-currency conversion (e.g. `Did you mean
64
+ 500000000?`). Negative values are also rejected up-front rather than
65
+ reaching the API. Both parsers share a single
66
+ `validate_priority_goal_value` helper. Closes #387.
67
+
68
+ **Added:**
69
+
70
+ - `direct sitelinks get` now exposes `--sitelink-field-names` for the
71
+ separate WSDL `SitelinkFieldNames` request parameter
72
+ (`SitelinkFieldEnum`: `Title`, `Href`, `Description`, `TurboPageId`).
73
+ Previously only the top-level `--fields` (mapping to `FieldNames`)
74
+ was available, so the nested `Sitelinks[]` projection could not be
75
+ controlled from CLI.
76
+ - `direct keywordbids get` now exposes `--fields`,
77
+ `--search-field-names`, and `--network-field-names` for the
78
+ separate `FieldNames`, `SearchFieldNames`, and `NetworkFieldNames`
79
+ request parameters declared by `KeywordBidsGetRequest`. Defaults
80
+ from `COMMON_FIELDS` are preserved when flags are absent.
81
+ - Regression test `test_every_nested_fieldnames_param_has_cli_option`
82
+ (`tests/test_api_coverage.py`) scans every cached WSDL `get`
83
+ request type for `*FieldNames` parameters and verifies that each
84
+ one has a matching kebab-case CLI option. Acknowledged remaining
85
+ gaps are tracked in `NESTED_FIELDNAMES_EXCLUSIONS` and #402 so
86
+ future additions cannot silently slip in.
87
+ - `direct feeds get` now exposes `--file-feed-field-names` and
88
+ `--url-feed-field-names` for the separate WSDL `FileFeedFieldNames`
89
+ (`FileFeedFieldEnum`: `Filename`) and `UrlFeedFieldNames`
90
+ (`UrlFeedFieldEnum`: `Login`, `Url`, `RemoveUtmTags`) request
91
+ parameters declared by `FeedsGetRequest`. Previously only the
92
+ top-level `--fields` (mapping to `FieldNames`) was available, so
93
+ the nested `FileFeed` / `UrlFeed` projections could not be
94
+ controlled from CLI. Closes #412.
95
+ - `direct keywords get` now exposes
96
+ `--autotargeting-settings-brand-options-field-names`
97
+ (`AutotargetingBrandOptionsFieldEnum`: `WithoutBrands`,
98
+ `WithAdvertiserBrand`, `WithCompetitorsBrand`) and
99
+ `--autotargeting-settings-categories-field-names`
100
+ (`AutotargetingCategoriesFieldEnum`: `Exact`, `Narrow`,
101
+ `Alternative`, `Accessory`, `Broader`) for the separate WSDL
102
+ `*FieldNames` request parameters declared by
103
+ `KeywordsGetRequest`. Previously only the top-level `--fields`
104
+ (mapping to `FieldNames`) was available, so the nested
105
+ `AutotargetingSettings.BrandOptions` / `Categories` projections
106
+ could not be controlled from CLI. Closes #413.
107
+ - `direct creatives get` now exposes
108
+ `--cpc-video-creative-field-names`,
109
+ `--cpm-video-creative-field-names`,
110
+ `--smart-creative-field-names`, and
111
+ `--video-extension-creative-field-names` for the four nested
112
+ WSDL `*FieldNames` request parameters declared by
113
+ `CreativesGetRequest` (`CpcVideoCreativeFieldEnum`,
114
+ `CpmVideoCreativeFieldEnum`, `SmartCreativeFieldEnum`,
115
+ `VideoExtensionCreativeFieldEnum`). Previously only the top-level
116
+ `--fields` (mapping to `FieldNames`) was available, so the
117
+ per-subtype projections could not be controlled from CLI.
118
+ Closes #411.
119
+ - `direct clients get` now exposes `--contract-field-names`,
120
+ `--contragent-field-names`, `--contragent-tin-info-field-names`,
121
+ `--organization-field-names`, and `--tin-info-field-names` for
122
+ the five nested WSDL `*FieldNames` request parameters declared
123
+ by `ClientsGetRequest` (`ContractInfoFieldEnum`,
124
+ `ContragentInfoFieldEnum`, `TinInfoFieldEnum`,
125
+ `OrgInfoFieldEnum`, `TinInfoFieldEnum`). The command also gains
126
+ `--dry-run` for parity with other read-path commands.
127
+ Previously only the top-level `--fields` (mapping to `FieldNames`)
128
+ was available, so the per-subtype ERIR projections could not be
129
+ controlled from CLI. Closes #410.
130
+ - `direct agencyclients get` now exposes `--contract-field-names`,
131
+ `--contragent-field-names`, `--contragent-tin-info-field-names`,
132
+ `--organization-field-names`, and `--tin-info-field-names` for
133
+ the five nested WSDL `*FieldNames` request parameters declared
134
+ by `AgencyClientsGetRequest` (`ContractInfoFieldEnum`,
135
+ `ContragentInfoFieldEnum`, `TinInfoFieldEnum`,
136
+ `OrgInfoFieldEnum`, `TinInfoFieldEnum`). The command also gains
137
+ `--dry-run` for parity with other read-path commands.
138
+ Previously only the top-level `--fields` (mapping to `FieldNames`)
139
+ was available, so the per-subtype ERIR projections could not be
140
+ controlled from CLI. Closes #407.
141
+ - `direct adgroups get` now exposes eight additional
142
+ `--*-field-names` flags for the separate WSDL `*FieldNames`
143
+ request parameters declared by `AdGroupsGetRequest`:
144
+ `--autotargeting-settings-brand-options-field-names`,
145
+ `--autotargeting-settings-categories-field-names`,
146
+ `--dynamic-text-ad-group-field-names`,
147
+ `--dynamic-text-feed-ad-group-field-names`,
148
+ `--mobile-app-ad-group-field-names`,
149
+ `--smart-ad-group-field-names`,
150
+ `--text-ad-group-feed-params-field-names`, and
151
+ `--unified-ad-group-field-names`. Previously only the top-level
152
+ `--fields` (mapping to `FieldNames`) was available, so the
153
+ per-subtype ad-group projections could not be controlled from
154
+ CLI. Closes #405.
155
+ - `direct ads get` now exposes sixteen additional `--*-field-names`
156
+ flags for the separate WSDL `*FieldNames` request parameters
157
+ declared by `AdsGetRequest`: `--cpc-video-ad-builder-ad-field-names`,
158
+ `--cpm-banner-ad-builder-ad-field-names`,
159
+ `--cpm-video-ad-builder-ad-field-names`,
160
+ `--dynamic-text-ad-field-names`, `--listing-ad-field-names`,
161
+ `--mobile-app-ad-builder-ad-field-names`,
162
+ `--mobile-app-ad-field-names`,
163
+ `--mobile-app-cpc-video-ad-builder-ad-field-names`,
164
+ `--mobile-app-image-ad-field-names`,
165
+ `--responsive-ad-field-names`, `--shopping-ad-field-names`,
166
+ `--smart-ad-builder-ad-field-names`,
167
+ `--text-ad-builder-ad-field-names`,
168
+ `--text-ad-field-names`,
169
+ `--text-ad-price-extension-field-names`, and
170
+ `--text-image-ad-field-names`. Previously only the top-level
171
+ `--fields` (mapping to `FieldNames`) and non-canonical
172
+ `--text-ad-fields` were available, so the per-ad-subtype projections
173
+ could not be controlled from CLI. Closes #406.
174
+
175
+ **BREAKING CHANGES:**
176
+
177
+ - `direct campaigns get` flags `--text-campaign-fields`,
178
+ `--mobile-app-campaign-fields`, `--dynamic-text-campaign-fields`,
179
+ `--cpm-banner-campaign-fields`, `--smart-campaign-fields`,
180
+ `--unified-campaign-fields`,
181
+ `--text-campaign-search-strategy-placement-types-fields`,
182
+ `--dynamic-text-campaign-search-strategy-placement-types-fields`,
183
+ `--unified-campaign-search-strategy-placement-types-fields`, and
184
+ `--unified-campaign-package-bidding-strategy-platforms-fields`
185
+ are **renamed** to their kebab-case WSDL-canonical `*-field-names`
186
+ form (`--text-campaign-field-names`,
187
+ `--mobile-app-campaign-field-names`, ...), matching the parameter
188
+ names declared by `CampaignsGetRequest`. The old `--*-fields`
189
+ forms are no longer accepted — update scripts and automation
190
+ accordingly. Closes #409.
191
+
192
+ **Additional features:**
193
+
194
+ - `direct bidmodifiers get` now exposes thirteen additional
195
+ `--*-adjustment-field-names` flags for the per-adjustment-subtype
196
+ WSDL `*FieldNames` request parameters declared by
197
+ `BidModifiersGetRequest`: `--ad-group-adjustment-field-names`,
198
+ `--demographics-adjustment-field-names`,
199
+ `--desktop-adjustment-field-names`,
200
+ `--desktop-only-adjustment-field-names`,
201
+ `--income-grade-adjustment-field-names`,
202
+ `--mobile-adjustment-field-names`,
203
+ `--regional-adjustment-field-names`,
204
+ `--retargeting-adjustment-field-names`,
205
+ `--serp-layout-adjustment-field-names`,
206
+ `--smart-ad-adjustment-field-names`,
207
+ `--smart-tv-adjustment-field-names`,
208
+ `--tablet-adjustment-field-names`, and
209
+ `--video-adjustment-field-names`. Previously only the top-level
210
+ `--fields` (mapping to `FieldNames`) was available, so the
211
+ per-adjustment projections could not be controlled from CLI.
212
+ Closes #408.
213
+ - `direct strategies get` now exposes sixteen additional
214
+ `--strategy-*-field-names` flags for the separate WSDL
215
+ `*FieldNames` request parameters declared by `StrategiesGetRequest`,
216
+ including `--strategy-average-cpa-field-names`,
217
+ `--strategy-average-cpa-multiple-goals-field-names`,
218
+ `--strategy-average-cpc-field-names`,
219
+ `--strategy-maximum-clicks-field-names`,
220
+ `--strategy-maximum-conversion-rate-field-names`,
221
+ `--strategy-pay-for-conversion-field-names`, and the remaining
222
+ per-campaign / per-filter strategy projections. The command also
223
+ gains `--dry-run` for read-path payload tests. Previously only the
224
+ top-level `--fields` (mapping to `FieldNames`) was available, so
225
+ per-strategy-subtype projections could not be controlled from CLI.
226
+ Closes #414.
227
+
228
+ Closes #360.
229
+
230
+ **Tests:**
231
+
232
+ - `tests/test_integration.py` now gracefully skips the seven read-only
233
+ classes that rely on live-API probes (`TestReadOnlyAdGroups`,
234
+ `TestReadOnlyAds`, `TestReadOnlyKeywords`,
235
+ `TestReadOnlyDynamicFeedAdTargets`, `TestReadOnlyLeads`,
236
+ `TestReadOnlyBusinesses`, `TestReadOnlyAdVideos`) when the probe
237
+ raises — previously a temporary API outage crashed `setUpClass`
238
+ with an opaque traceback.
239
+ - `invoke_get` in `tests/test_integration.py` now passes the resolved
240
+ test credentials as explicit `--token`/`--login` flags so the
241
+ integration suite cannot silently fall through to a developer's
242
+ active `direct auth` profile (priority 1 in the CLI credential
243
+ chain wins over the profile, matching CLAUDE.md guidance).
244
+ - `tests/test_comprehensive.py` slimmed down: `TestCLIHelp` (full
245
+ duplicate of `tests/test_cli.py`) removed; the unique
246
+ `TestCommandsRegistered`, `TestUtils`, `TestOutputFormatters`,
247
+ `TestAuth`, and `TestErrorHandling` classes are kept.
248
+ - `tests/test_smoke_matrix.py` no longer hard-codes
249
+ `total_cli_subcommands == 144` or `wsdl_operations == 112`. Counts
250
+ are derived from the live Click registry and parsed WSDLs.
251
+ - `tests/test_sandbox_write_audit.py` no longer hard-codes
252
+ `total == 83`. The count derives from `commands_for_category`.
253
+
254
+ Closes #396.
255
+
3
256
  ## 0.3.13
4
257
 
5
258
  **Breaking changes:**
@@ -41,6 +41,8 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
41
41
 
42
42
  **`--dry-run`:** `add`/`update` commands print request JSON without calling the API. Use as test seam.
43
43
 
44
+ **No legacy CLI flag aliases.** A CLI option must be exactly the kebab-case form of the WSDL request parameter (`SitelinkFieldNames` → `--sitelink-field-names`). If an existing flag uses a different name for the same WSDL parameter, rename it as a **breaking change** — do not keep the old form as a second `@click.option` name on the same variable. Document the rename in `CHANGELOG.md` under a `BREAKING CHANGES` heading and bump the version accordingly.
45
+
44
46
  **Runtime-deprecated methods:** WSDL-visible methods that Yandex rejects at runtime belong in `RUNTIME_DEPRECATED_METHODS` (`direct_cli/wsdl_coverage.py`) and must fail with `click.UsageError` before request construction. `agencyclients add` is blocked this way; use `agencyclients add-passport-organization`.
45
47
 
46
48
  **Strict WSDL parity:** `DRY_RUN_PAYLOAD_EXCLUSIONS` in `tests/api_coverage_payloads.py` must NOT contain any entry whose rationale claims the CLI surface is a «helper», «legacy», or «not part of strict WSDL parity». If the WSDL declares the operation, the CLI mirrors it 1:1 with a `PAYLOAD_CASES` fixture. Legitimate permanent exclusions are limited to:
@@ -72,6 +74,34 @@ Adding a new mutating command requires extending `COMMAND_WSDL_MAP` in `tests/te
72
74
 
73
75
  **Smoke probes:** Live ID discovery for safe-smoke and integration tests lives in `direct_cli/_smoke_probes.py`. Functions like `advideo_probe_id()` query the live API to find a real resource ID (env override `YANDEX_DIRECT_TEST_ADVIDEO_ID`, fallback through `creatives.get`) and return `None` on any failure — smoke scripts treat `None` as a benign skip, not a fatal error. CLI entry: `python3 -m direct_cli._smoke_probes advideo`.
74
76
 
77
+ **No URL literals outside the registry.** Every Yandex docs/API URL is declared once — either in `direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py` (`docs`, `docs_pages.*`) or in `direct_cli/reports_coverage.py::REPORTS_SPEC_URLS`. Tests and scripts import these constants; never write the URL as a string literal anywhere else. Captcha-poisoning of the docs cache (#426) was possible only because the same URL was duplicated in a hard-coded test assertion. Don't repeat that.
78
+
79
+ **Docs/cache freshness guard.** `direct_cli.reports_coverage.fetch_reports_spec` and `direct_cli.wsdl_coverage.fetch_wsdl` both refuse responses that look like a Yandex SmartCaptcha gate (markers `showcaptcha`, `smartcaptcha`, `<title>Captcha`) or are suspiciously short (<30 KB for HTML, <3 KB for WSDL). The matching tests `test_reports_cache_files_are_real_content` and `test_wsdl_cache_files_are_real_content` enforce the same invariant on committed cache files.
80
+
81
+ ## PyPI Release
82
+
83
+ Release is two-phase on purpose. Yandex frequently rate-limits the docs host with a SmartCaptcha gateway, which is an external rate-limit on our IP — not evidence that an URL is gone. Mixing docs-health checks into the publish path made releases non-deterministic, so they live in a separate preflight script.
84
+
85
+ **Phase 1 — preflight (manual, network-dependent):**
86
+
87
+ ```
88
+ bash scripts/preflight_check.sh
89
+ ```
90
+
91
+ Runs three checks:
92
+
93
+ 1. `python scripts/check_all_docs_urls.py` — every Yandex docs URL in `RESOURCE_MAPPING_V5` and `REPORTS_SPEC_URLS` must be reachable. The probe falls back from `HEAD` to `GET` on 4xx (some CDNs reject `HEAD`), then verifies real content. **Hard fail:** confirmed 3xx-to-different-path or 4xx (canonical move / gone). **Soft warning (does not block):** 5xx or persistent captcha — treated as a transient Yandex rate-limit; re-run later to confirm.
94
+ 2. `pytest TestReportsCoverage TestWsdlCacheFreshness -v` — read-only check that the committed Reports/WSDL/XSD caches are real content (not captcha).
95
+ 3. `git diff --quiet -- tests/reports_cache tests/wsdl_cache` — refuses to proceed with an uncommitted cache refresh. If Yandex changed docs since the last snapshot, run `scripts/refresh_reports_cache.py` separately, review the diff, and commit it before releasing.
96
+
97
+ **Phase 2 — release (deterministic, no Yandex network calls):**
98
+
99
+ ```
100
+ bash scripts/release_pypi.sh all
101
+ ```
102
+
103
+ Builds dist artifacts, runs twine checks, uploads to TestPyPI + PyPI. Does **not** re-run docs/cache checks — if the captcha rate-limit was active during preflight, that should not block a release of an already-verified artifact.
104
+
75
105
  ## Tests
76
106
 
77
107
  - **Unit** (`test_cli.py`, `test_comprehensive.py`) — no API calls, no token needed.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: direct-cli
3
- Version: 0.3.13
3
+ Version: 0.3.14
4
4
  Summary: Command-line interface for Yandex Direct API
5
5
  Author: axisrow
6
6
  License: MIT
@@ -1718,11 +1718,6 @@ def build_text_campaign_search_strategy(
1718
1718
  # the caller has already placed PriorityGoals via the dedicated
1719
1719
  # ``PriorityGoalsUpdateSetting`` shape (with ``Operation: SET``) so
1720
1720
  # we only validate the strategy/subtype combination here.
1721
- #
1722
- # KNOWN ISSUE (#387): ``parse_priority_goals_spec`` forwards
1723
- # ``Value`` as a raw integer. Per Yandex docs ``Value`` is
1724
- # advertiser currency × 1,000,000; the cross-cutting fix is
1725
- # tracked in issue #387 and intentionally NOT bundled into #361.
1726
1721
  if priority_goals_items is not None:
1727
1722
  if subtype not in _TEXT_SEARCH_REQUIRES_PRIORITY_GOALS:
1728
1723
  raise click.UsageError(
@@ -78,12 +78,12 @@ RESOURCE_MAPPING_V5 = {
78
78
  },
79
79
  "dynamicads": {
80
80
  "resource": "json/v5/dynamictextadtargets",
81
- "docs": "https://yandex.ru/dev/direct/doc/ru/dynamictextadtargets/dynamictextadtargets",
81
+ "docs": "https://yandex.ru/dev/direct/doc/ru/dynamictextadtargets",
82
82
  "methods": ["get", "add", "delete", "suspend", "resume", "setBids"],
83
83
  },
84
84
  "dynamicfeedadtargets": {
85
85
  "resource": "json/v5/dynamicfeedadtargets",
86
- "docs": "https://yandex.ru/dev/direct/doc/ru/dynamicfeedadtargets/dynamicfeedadtargets",
86
+ "docs": "https://yandex.ru/dev/direct/doc/ru/dynamicfeedadtargets",
87
87
  "methods": ["get", "add", "delete", "suspend", "resume", "setBids"],
88
88
  },
89
89
  "keywordbids": {
@@ -118,7 +118,7 @@ RESOURCE_MAPPING_V5 = {
118
118
  },
119
119
  "vcards": {
120
120
  "resource": "json/v5/vcards",
121
- "docs": "https://yandex.ru/dev/direct/doc/ru/vcards/vcards",
121
+ "docs": "https://yandex.ru/dev/direct/doc/ru/vcards",
122
122
  "methods": ["get", "add", "delete"],
123
123
  },
124
124
  "turbopages": {
@@ -133,12 +133,12 @@ RESOURCE_MAPPING_V5 = {
133
133
  },
134
134
  "reports": {
135
135
  "resource": "json/v5/reports",
136
- "docs": "https://yandex.ru/dev/direct/doc/ru/reports/reports",
136
+ "docs": "https://yandex.ru/dev/direct/doc/ru/reports",
137
137
  "methods": ["get"],
138
138
  "docs_pages": {
139
- "type": "https://yandex.ru/dev/direct/doc/ru/reports/type",
140
- "period": "https://yandex.ru/dev/direct/doc/ru/reports/period",
141
- "fields-list": "https://yandex.ru/dev/direct/doc/ru/reports/fields-list",
139
+ "type": "https://yandex.ru/dev/direct/doc/ru/type",
140
+ "period": "https://yandex.ru/dev/direct/doc/ru/period",
141
+ "fields-list": "https://yandex.ru/dev/direct/doc/ru/fields-list",
142
142
  "headers": "https://yandex.ru/dev/direct/doc/ru/headers",
143
143
  },
144
144
  },
@@ -154,7 +154,7 @@ RESOURCE_MAPPING_V5 = {
154
154
  },
155
155
  "smartadtargets": {
156
156
  "resource": "json/v5/smartadtargets",
157
- "docs": "https://yandex.ru/dev/direct/doc/ru/smartadtargets/smartadtargets",
157
+ "docs": "https://yandex.ru/dev/direct/doc/ru/smartadtargets",
158
158
  "methods": ["get", "add", "update", "delete", "suspend", "resume", "setBids"],
159
159
  },
160
160
  "strategies": {
@@ -473,6 +473,77 @@ def _post_adgroups(client: Any, body: dict[str, Any]) -> Any:
473
473
  @click.option("--format", "output_format", default="json", help="Output format")
474
474
  @click.option("--output", help="Output file")
475
475
  @click.option("--fields", help="Comma-separated field names")
476
+ @click.option(
477
+ "--autotargeting-settings-brand-options-field-names",
478
+ help=(
479
+ "Comma-separated AutotargetingSettingsBrandOptionsFieldNames "
480
+ "(e.g. WithoutBrands,WithAdvertiserBrand,WithCompetitorsBrand). "
481
+ "Sent as separate top-level request parameter per the "
482
+ "AdGroupsGetRequest WSDL."
483
+ ),
484
+ )
485
+ @click.option(
486
+ "--autotargeting-settings-categories-field-names",
487
+ help=(
488
+ "Comma-separated AutotargetingSettingsCategoriesFieldNames "
489
+ "(e.g. Exact,Narrow,Alternative,Accessory,Broader). "
490
+ "Sent as separate top-level request parameter per the "
491
+ "AdGroupsGetRequest WSDL."
492
+ ),
493
+ )
494
+ @click.option(
495
+ "--dynamic-text-ad-group-field-names",
496
+ help=(
497
+ "Comma-separated DynamicTextAdGroupFieldNames "
498
+ "(e.g. AutotargetingSettings,DomainUrl). "
499
+ "Sent as separate top-level request parameter per the "
500
+ "AdGroupsGetRequest WSDL."
501
+ ),
502
+ )
503
+ @click.option(
504
+ "--dynamic-text-feed-ad-group-field-names",
505
+ help=(
506
+ "Comma-separated DynamicTextFeedAdGroupFieldNames "
507
+ "(e.g. Source,FeedId,SourceType). "
508
+ "Sent as separate top-level request parameter per the "
509
+ "AdGroupsGetRequest WSDL."
510
+ ),
511
+ )
512
+ @click.option(
513
+ "--mobile-app-ad-group-field-names",
514
+ help=(
515
+ "Comma-separated MobileAppAdGroupFieldNames "
516
+ "(e.g. StoreUrl,TargetDeviceType,AppOperatingSystemType). "
517
+ "Sent as separate top-level request parameter per the "
518
+ "AdGroupsGetRequest WSDL."
519
+ ),
520
+ )
521
+ @click.option(
522
+ "--smart-ad-group-field-names",
523
+ help=(
524
+ "Comma-separated SmartAdGroupFieldNames "
525
+ "(e.g. FeedId,AdTitleSource,AdBodySource). "
526
+ "Sent as separate top-level request parameter per the "
527
+ "AdGroupsGetRequest WSDL."
528
+ ),
529
+ )
530
+ @click.option(
531
+ "--text-ad-group-feed-params-field-names",
532
+ help=(
533
+ "Comma-separated TextAdGroupFeedParamsFieldNames "
534
+ "(e.g. FeedId,FeedCategoryIds). "
535
+ "Sent as separate top-level request parameter per the "
536
+ "AdGroupsGetRequest WSDL."
537
+ ),
538
+ )
539
+ @click.option(
540
+ "--unified-ad-group-field-names",
541
+ help=(
542
+ "Comma-separated UnifiedAdGroupFieldNames (e.g. OfferRetargeting). "
543
+ "Sent as separate top-level request parameter per the "
544
+ "AdGroupsGetRequest WSDL."
545
+ ),
546
+ )
476
547
  @click.option("--dry-run", is_flag=True, help="Show request without sending")
477
548
  @click.pass_context
478
549
  def get(
@@ -492,6 +563,14 @@ def get(
492
563
  output_format,
493
564
  output,
494
565
  fields,
566
+ autotargeting_settings_brand_options_field_names,
567
+ autotargeting_settings_categories_field_names,
568
+ dynamic_text_ad_group_field_names,
569
+ dynamic_text_feed_ad_group_field_names,
570
+ mobile_app_ad_group_field_names,
571
+ smart_ad_group_field_names,
572
+ text_ad_group_feed_params_field_names,
573
+ unified_ad_group_field_names,
495
574
  dry_run,
496
575
  ):
497
576
  """Get ad groups"""
@@ -507,6 +586,41 @@ def get(
507
586
 
508
587
  field_names = fields.split(",") if fields else get_default_fields("adgroups")
509
588
 
589
+ raw_nested = (
590
+ (
591
+ "AutotargetingSettingsBrandOptionsFieldNames",
592
+ autotargeting_settings_brand_options_field_names,
593
+ ),
594
+ (
595
+ "AutotargetingSettingsCategoriesFieldNames",
596
+ autotargeting_settings_categories_field_names,
597
+ ),
598
+ (
599
+ "DynamicTextAdGroupFieldNames",
600
+ dynamic_text_ad_group_field_names,
601
+ ),
602
+ (
603
+ "DynamicTextFeedAdGroupFieldNames",
604
+ dynamic_text_feed_ad_group_field_names,
605
+ ),
606
+ ("MobileAppAdGroupFieldNames", mobile_app_ad_group_field_names),
607
+ ("SmartAdGroupFieldNames", smart_ad_group_field_names),
608
+ (
609
+ "TextAdGroupFeedParamsFieldNames",
610
+ text_ad_group_feed_params_field_names,
611
+ ),
612
+ ("UnifiedAdGroupFieldNames", unified_ad_group_field_names),
613
+ )
614
+ parsed_nested = {}
615
+ for wsdl_key, raw_value in raw_nested:
616
+ parsed = parse_csv_strings(raw_value)
617
+ if raw_value is not None and not parsed:
618
+ raise click.UsageError(
619
+ f"Provide a non-empty comma-separated {wsdl_key} list."
620
+ )
621
+ if parsed:
622
+ parsed_nested[wsdl_key] = parsed
623
+
510
624
  criteria = {}
511
625
  if ids:
512
626
  criteria["Ids"] = parse_ids(ids)
@@ -529,6 +643,7 @@ def get(
529
643
  )
530
644
 
531
645
  params = {"SelectionCriteria": criteria, "FieldNames": field_names}
646
+ params.update(parsed_nested)
532
647
 
533
648
  if limit:
534
649
  params["Page"] = {"Limit": limit}
@@ -550,6 +665,8 @@ def get(
550
665
  data = result().extract()
551
666
  format_output(data, output_format, output)
552
667
 
668
+ except click.UsageError:
669
+ raise
553
670
  except Exception as e:
554
671
  print_error(str(e))
555
672
  raise click.Abort()