direct-cli 0.3.6__tar.gz → 0.3.8__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.3.6 → direct_cli-0.3.8}/.github/workflows/claude.yml +1 -1
- direct_cli-0.3.8/CHANGELOG.md +43 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/CLAUDE.md +13 -1
- {direct_cli-0.3.6 → direct_cli-0.3.8}/PKG-INFO +58 -13
- {direct_cli-0.3.6 → direct_cli-0.3.8}/README.md +57 -12
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/cli.py +4 -1
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/ads.py +145 -26
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/feeds.py +11 -1
- direct_cli-0.3.8/direct_cli/commands/v4forecast.py +168 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/v4shells.py +0 -5
- direct_cli-0.3.8/direct_cli/commands/v4tags.py +263 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/smoke_matrix.py +8 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/v4_contracts.py +86 -24
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli.egg-info/PKG-INFO +58 -13
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli.egg-info/SOURCES.txt +28 -20
- {direct_cli-0.3.6 → direct_cli-0.3.8}/pyproject.toml +1 -1
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/build_api_coverage_report.py +85 -34
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/sandbox_write_live.py +78 -10
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/test_safe_commands.sh +12 -0
- direct_cli-0.3.8/scripts/test_sandbox_write.sh +38 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/API_COVERAGE.md +4 -4
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/API_ISSUE_AUDIT.md +1 -1
- direct_cli-0.3.8/tests/_orphan_store.py +124 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/api_coverage_payloads.py +424 -31
- direct_cli-0.3.8/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +437 -0
- direct_cli-0.3.8/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +437 -0
- direct_cli-0.3.8/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +58 -0
- direct_cli-0.3.6/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml → direct_cli-0.3.8/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +29 -29
- direct_cli-0.3.8/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +69 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/conftest.py +9 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_api_coverage.py +353 -3
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_cli_contract.py +1 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_comprehensive.py +1 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_dry_run.py +55 -12
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_integration.py +117 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_integration_write.py +256 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_low_coverage_payloads.py +213 -8
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_smoke_matrix.py +75 -4
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4_contracts.py +86 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4_foundation.py +2 -0
- direct_cli-0.3.8/tests/test_v4_live_contracts.py +341 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4_safety.py +14 -0
- direct_cli-0.3.8/tests/test_v4forecast.py +238 -0
- direct_cli-0.3.8/tests/test_v4tags.py +321 -0
- direct_cli-0.3.6/tests/test_integration_live_write.py → direct_cli-0.3.8/tests/test_v5_live_write.py +27 -28
- direct_cli-0.3.6/.github/workflows/claude-code-review.yml +0 -44
- direct_cli-0.3.6/CHANGELOG.md +0 -11
- direct_cli-0.3.6/scripts/test_sandbox_write.sh +0 -23
- direct_cli-0.3.6/tests/test_v4_live_contracts.py +0 -190
- {direct_cli-0.3.6 → direct_cli-0.3.8}/.env.example +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/.github/copilot-instructions.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/.github/workflows/api-coverage.yml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/.github/workflows/quality.yml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/.gitignore +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/AGENTS.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/MANIFEST.in +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_deprecated.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_smoke_probes.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/api.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/auth.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/adextensions.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/adgroups.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/adimages.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/advideos.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/agencyclients.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/audiencetargets.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/auth.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/balance.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/bidmodifiers.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/bids.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/businesses.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/campaigns.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/changes.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/clients.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/creatives.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/dictionaries.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/dynamicads.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/keywordbids.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/keywords.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/reports.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/retargeting.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/sitelinks.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/smartadtargets.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/strategies.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/turbopages.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/v4account.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/v4events.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/v4finance.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/v4goals.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/v4wordstat.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/commands/vcards.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/output.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/reports_coverage.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/utils.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/v4/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/v4/money.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli/wsdl_coverage.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli.egg-info/requires.txt +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/anonymize_cassettes.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/build_api_coverage_checklist.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/check_reports_drift.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/check_wsdl_drift.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/patch_vendor_imports.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/refresh_reports_cache.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/refresh_wsdl_cache.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/release_pypi.sh +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/test_dangerous_commands.sh +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/scripts/update_vendor.sh +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/setup.cfg +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/setup.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/MANUAL_COVERAGE.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_adgroups_add_update_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_adimages_add_get_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_ads_add_update_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_ads_suspend_resume_archive_unarchive.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_advideos_add_get.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_add_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_suspend_resume.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_bids_set.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_campaign_create_get_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_creatives_chain_advideo_to_creative.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_add_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_suspend_resume.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_keywordbids_set.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_keywords_add_update_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_keywords_suspend_resume.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_sitelinks_add_get_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_add_update_delete.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
- /direct_cli-0.3.6/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_suspend_resume.yaml → /direct_cli-0.3.8/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/fixtures/test-video.mp4 +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/reports_cache/raw/fields-list.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/reports_cache/raw/headers.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/reports_cache/raw/period.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/reports_cache/raw/spec.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/reports_cache/raw/type.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/reports_cache/spec.json +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_auth_oauth.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_auth_op.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_balance.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_cli.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_reports_drift.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_reports_parsing.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_transport_contract.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4account.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4events.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4finance_money.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4finance_read.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4goals.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_v4wordstat.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/test_vendor_imports.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/adextensions.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/adgroups.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/adimages.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/ads.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/advideos.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/agencyclients.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/audiencetargets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/bidmodifiers.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/bids.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/businesses.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/campaigns.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/changes.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/clients.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/creatives.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/dictionaries.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/feeds.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/imports/general.xsd +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/keywordbids.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/keywords.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/keywordsresearch.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/leads.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/retargetinglists.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/sitelinks.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/smartadtargets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/strategies.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/turbopages.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.8}/tests/wsdl_cache/vcards.xml +0 -0
|
@@ -46,5 +46,5 @@ jobs:
|
|
|
46
46
|
# Optional: Add claude_args to customize behavior and configuration
|
|
47
47
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
48
48
|
# or https://code.claude.com/docs/en/cli-reference for available options
|
|
49
|
-
|
|
49
|
+
claude_args: "--model claude-opus-4-7"
|
|
50
50
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.8
|
|
4
|
+
|
|
5
|
+
**BREAKING CHANGES:**
|
|
6
|
+
|
|
7
|
+
- `direct ads update` now requires `--type {TEXT_AD,TEXT_IMAGE_AD,MOBILE_APP_AD}`. Scripts that called `ads update` with only field flags will fail with `Missing option '--type'`. Mirrors the WSDL one-of choice between TextAd/TextImageAd/MobileAppAd update subtypes (PR #197).
|
|
8
|
+
- `direct ads add --type TEXT_IMAGE_AD` rejects `--title/--text` (TEXT_IMAGE_AD has no such WSDL fields). `direct ads update --status` rejected — use `ads suspend/resume/archive/unarchive` for status changes (PR #190).
|
|
9
|
+
- `direct ads add --type MOBILE_APP_AD --href` rejected — MobileAppAd uses `--tracking-url`, not `--href` (PR #196).
|
|
10
|
+
- `direct feeds add` now requires `--business-type {RETAIL,HOTELS,REALTY,AUTOMOBILES,FLIGHTS,OTHER}`. Mirrors WSDL FeedAddItem.BusinessType (minOccurs=1) (PR #201).
|
|
11
|
+
|
|
12
|
+
**Schema gate — mutating ops parity:**
|
|
13
|
+
|
|
14
|
+
- Extended the WSDL `*FieldNames` schema gate (introduced for `get` in 0.3.7) to mutating operations (`add/update/set/setBids/lifecycle`). Added per-operation waiver granularity via `SCHEMA_GATE_OPERATION_WAIVERS` (PR #181).
|
|
15
|
+
- Promoted dynamicads, bidmodifiers add/set, adimages/advideos/vcards add (media payloads), adextensions/retargeting/feeds.add typed fixtures to `PAYLOAD_CASES` (PRs #184, #185, #187, #188).
|
|
16
|
+
- Added MOBILE_APP_AD branch to `ads add` mirroring WSDL `MobileAppAdAdd` (PR #190).
|
|
17
|
+
- `bidmodifiers.delete` correctly classified as a real destructive WSDL operation and added to schema gate (PR #194); the earlier "Helper/legacy surface" rationale was a mis-classification — see post-mortem in #199 / PR #200.
|
|
18
|
+
|
|
19
|
+
**Strict WSDL parity policy:**
|
|
20
|
+
|
|
21
|
+
- Documented "Strict WSDL parity" principle in `CLAUDE.md`: `DRY_RUN_PAYLOAD_EXCLUSIONS` may only contain entries from five legitimate categories (read-path `*.get`, runtime-deprecated, v4-not-in-v5-wsdl, custom non-RPC endpoints, methods covered by `tests/test_dry_run.py`). New guard test `test_dry_run_exclusions_have_no_helper_or_legacy_rationale` fails CI if any rationale uses banned phrases (PR #200).
|
|
22
|
+
|
|
23
|
+
**Integration test coverage:**
|
|
24
|
+
|
|
25
|
+
- Added read-only sandbox integration tests for `changes`, `keywordsresearch`, `balance` (PR #186).
|
|
26
|
+
- Added v5 write integration coverage for `strategies` lifecycle, `retargeting update`, `bids get/set-auto`, plus `auth status/list` read-only tests (PR #189).
|
|
27
|
+
- Re-recorded TestWriteBidsRead cassettes against live API and rewrote host to sandbox so the bids endpoints get real coverage in replay mode (PR #193).
|
|
28
|
+
|
|
29
|
+
**CI infrastructure:**
|
|
30
|
+
|
|
31
|
+
- Switched Claude code-review GitHub Action from default (Sonnet 4.5) to Claude Opus 4.7 for deeper PR review (PR #192).
|
|
32
|
+
|
|
33
|
+
**Refs:** Closes issues #118, #136, #137, #175, #176, #180, #183, #191, #199.
|
|
34
|
+
|
|
35
|
+
## 0.3.3
|
|
36
|
+
|
|
37
|
+
**BREAKING CHANGE:** OAuth profiles created before 0.3.3 (without `refresh_token` and `expires_at`) are no longer accepted. Any such profile will fail immediately with an "incomplete profile" error. Run `direct auth login --profile <name>` to re-authenticate and create a valid 0.3.3 profile.
|
|
38
|
+
|
|
39
|
+
- Added refresh token persistence for OAuth profiles.
|
|
40
|
+
- Added automatic OAuth access token refresh before expiry.
|
|
41
|
+
- Added `expires_in` details to `direct auth status`.
|
|
42
|
+
- Added JSON output for `direct auth status`.
|
|
43
|
+
- Kept `direct auth login --oauth-token` as a manual access-token import without auto-refresh.
|
|
@@ -24,7 +24,9 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
|
|
|
24
24
|
|
|
25
25
|
**Request flow:** `cli.py` → `auth.py` (resolves token/login) → `api.py` (`create_client`) → `tapi_yandex_direct.YandexDirect` → Yandex API → `output.py` (format/print).
|
|
26
26
|
|
|
27
|
-
**Credentials priority:** CLI flags (`--token`, `--login`) > env vars (`YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN`) > `.env` file. `load_dotenv()` runs at `cli.py` import time.
|
|
27
|
+
**Credentials priority (CLI):** CLI flags (`--token`, `--login`) > active profile from `direct auth login` > env vars (`YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN`) > `.env` file > 1Password/Bitwarden refs. See `direct_cli/auth.py:600` (`get_credentials`) and README table for the full chain. `load_dotenv()` runs at `cli.py` import time.
|
|
28
|
+
|
|
29
|
+
**Credentials priority (tests):** **inverted** — env vars > active profile > skip. Tests must not silently hit production when a developer has an active `direct auth` profile, so env vars take precedence over the profile (see `tests/test_v4_live_contracts.py::_credentials`).
|
|
28
30
|
|
|
29
31
|
**Shared utilities** (`utils.py`): `parse_ids`, `parse_json`, `build_selection_criteria`, `build_common_params`, `get_default_fields`, `COMMON_FIELDS` dict. All command modules import from here — don't duplicate.
|
|
30
32
|
|
|
@@ -41,6 +43,15 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
|
|
|
41
43
|
|
|
42
44
|
**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`.
|
|
43
45
|
|
|
46
|
+
**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:
|
|
47
|
+
- read-path `*.get` (covered by SelectionCriteria tests);
|
|
48
|
+
- runtime-deprecated methods (see `RUNTIME_DEPRECATED_METHODS`);
|
|
49
|
+
- v4 methods that have no v5 WSDL (covered by `direct_cli/v4_contracts.py`);
|
|
50
|
+
- custom non-RPC endpoints (e.g. `reports.get` — TSV stream);
|
|
51
|
+
- methods explicitly covered by `tests/test_dry_run.py::test_<service>_<op>_payload`.
|
|
52
|
+
|
|
53
|
+
A guard in `tests/test_api_coverage.py::test_dry_run_exclusions_have_no_helper_or_legacy_rationale` enforces this — any rationale outside those five categories that uses the banned phrasing is a mis-classification: write a `PAYLOAD_CASES` fixture instead. See post-mortem in issue #199.
|
|
54
|
+
|
|
44
55
|
**SelectionCriteria:** Resources like `adgroups`, `ads`, `keywords` require at least one of `Ids`, `CampaignIds`, or `AdGroupIds` — otherwise API error 4001.
|
|
45
56
|
|
|
46
57
|
**Error handling:** All commands wrap API calls in `try/except Exception` → `print_error(str(e))` + `raise click.Abort()`.
|
|
@@ -55,6 +66,7 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
|
|
|
55
66
|
|
|
56
67
|
- **Unit** (`test_cli.py`, `test_comprehensive.py`) — no API calls, no token needed.
|
|
57
68
|
- **Integration** (`test_integration.py`, `@pytest.mark.integration`) — require `.env` with `YANDEX_DIRECT_TOKEN` and `YANDEX_DIRECT_LOGIN`. Auto-skip if absent.
|
|
69
|
+
- **Credential resolution in tests:** env vars first, then active `direct auth` profile, then skip. This is **inverted** vs. CLI (where the profile wins) on purpose: a developer machine with an active profile must not silently hit production on a plain `pytest`.
|
|
58
70
|
|
|
59
71
|
## Dangerous Commands — Never Auto-Test
|
|
60
72
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: direct-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: Command-line interface for Yandex Direct API
|
|
5
5
|
Author: axisrow
|
|
6
6
|
License: MIT
|
|
@@ -125,6 +125,8 @@ fall back to base `YANDEX_DIRECT_LOGIN`; this prevents mixing a profile token
|
|
|
125
125
|
with a login from the project `.env`. For multi-account setups, prefer OAuth
|
|
126
126
|
profiles or profile-specific env vars instead of base credentials.
|
|
127
127
|
|
|
128
|
+
> **Tests use the inverted order.** Live-API test suites (e.g. `tests/test_v4_live_contracts.py`) read `YANDEX_DIRECT_TOKEN` / `YANDEX_DIRECT_LOGIN` from the environment first, only then fall back to the active `direct auth` profile, and skip the test if neither is set. This is intentional: a developer machine with an active profile must not silently hit production on a plain `pytest` invocation. See `CLAUDE.md` for the contract.
|
|
129
|
+
|
|
128
130
|
Install with `pip install direct-cli`, then run commands with `direct`.
|
|
129
131
|
Invoking the deprecated `direct-cli` entrypoint exits with
|
|
130
132
|
`use direct instead of direct-cli`.
|
|
@@ -158,6 +160,24 @@ direct v4goals get-retargeting-goals --campaign-ids 123,456 --format table
|
|
|
158
160
|
direct v4goals get-stat-goals --campaign-ids 123 --dry-run
|
|
159
161
|
```
|
|
160
162
|
|
|
163
|
+
### V4 Live Tags
|
|
164
|
+
|
|
165
|
+
Campaign tags are managed as `{TagID, Tag}` pairs. Use `TagID=0` to create a
|
|
166
|
+
new campaign tag. Banner/ad tags are assigned by campaign tag IDs. Update
|
|
167
|
+
methods replace the full tag list for the target campaign or banner, so pass
|
|
168
|
+
existing tags again if they must remain assigned. Ad group tags are filter-only
|
|
169
|
+
through `direct adgroups get --tag-ids/--tags`; this release does not add ad
|
|
170
|
+
group tag mutation commands.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
direct v4tags get-campaigns --campaign-ids 3193279,1634563
|
|
174
|
+
direct v4tags get-banners --banner-ids 2571700,2571745
|
|
175
|
+
direct v4tags get-banners --campaign-ids 3193279
|
|
176
|
+
direct v4tags update-campaigns --campaign-id 3193279 --tag 0=akapulko --tag 16590=orange --dry-run
|
|
177
|
+
direct v4tags update-banners --banner-ids 2571700,2571745 --tag-ids 16590,16734 --dry-run
|
|
178
|
+
direct v4tags update-banners --banner-ids 2571700 --clear-tags --dry-run
|
|
179
|
+
```
|
|
180
|
+
|
|
161
181
|
### V4 Live Events
|
|
162
182
|
|
|
163
183
|
```bash
|
|
@@ -178,6 +198,19 @@ direct v4wordstat get-report --report-id 123 --format table
|
|
|
178
198
|
direct v4wordstat delete-report --report-id 123
|
|
179
199
|
```
|
|
180
200
|
|
|
201
|
+
### V4 Live Budget Forecasts
|
|
202
|
+
|
|
203
|
+
Budget forecasts are asynchronous. Direct CLI makes exactly one API call per
|
|
204
|
+
command and does not poll automatically; repeat `list` or `get` yourself until
|
|
205
|
+
the forecast is ready.
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
direct v4forecast create --phrases "buy laptop,buy desktop" --geo-ids 213 --currency RUB
|
|
209
|
+
direct v4forecast list --format table
|
|
210
|
+
direct v4forecast get --forecast-id 123 --format table
|
|
211
|
+
direct v4forecast delete --forecast-id 123
|
|
212
|
+
```
|
|
213
|
+
|
|
181
214
|
### V4 Live Finance
|
|
182
215
|
|
|
183
216
|
Finance methods require an extra financial token for money operations. In the
|
|
@@ -462,7 +495,7 @@ direct vcards add --campaign-id 555 --country "Russia" --city "Moscow" --company
|
|
|
462
495
|
direct adextensions add --callout-text "Free shipping" --dry-run
|
|
463
496
|
direct adimages add --name banner.png --image-data BASE64DATA --type ICON --dry-run
|
|
464
497
|
direct creatives add --video-id video-id --dry-run
|
|
465
|
-
direct feeds add --name "Feed A" --url "https://example.com/feed.xml" --dry-run
|
|
498
|
+
direct feeds add --name "Feed A" --url "https://example.com/feed.xml" --business-type RETAIL --dry-run
|
|
466
499
|
direct feeds update --id 18 --name "Feed A v2" --url "https://example.com/feed-v2.xml" --dry-run
|
|
467
500
|
direct clients update --client-info "Priority client" --phone +70000000000 --notification-email user@example.com --notification-lang EN --email-subscription RECEIVE_RECOMMENDATIONS=YES --setting DISPLAY_STORE_RATING=NO --dry-run
|
|
468
501
|
direct --login CLIENT_LOGIN clients update --phone +70000000000 --notification-email user@example.com --dry-run
|
|
@@ -534,17 +567,22 @@ Four tiers of tests live under `tests/`:
|
|
|
534
567
|
| Unit / CLI wiring / dry-run | *(none)* | No | No |
|
|
535
568
|
| Read-only integration | `-m integration` | Yes (production API, read-only) | Yes |
|
|
536
569
|
| Write integration | `-m integration_write` | No (replays VCR cassettes) | No |
|
|
537
|
-
| Live draft write integration | `-m integration_live_write` | Yes when recording, otherwise VCR replay | Yes + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
570
|
+
| Live draft write integration (v5) | `-m integration_live_write` | Yes when recording, otherwise VCR replay | Yes + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
571
|
+
| v4 live read | `-m v4_live_read` | Yes (production v4 JSON API, read-only) | Yes |
|
|
572
|
+
| v4 live account-level report write (opt-in) | `-k _opt_in_write` in `tests/test_v4_live_contracts.py` | Yes (production v4) | Yes + `YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1` |
|
|
538
573
|
|
|
539
574
|
```bash
|
|
540
575
|
pip install -e ".[dev]"
|
|
541
576
|
pytest # fast tier — no token
|
|
542
577
|
pytest -m integration -v # read-only integration tests (needs token)
|
|
543
578
|
pytest -m integration_write -v # write cassette replay (no token needed)
|
|
544
|
-
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # live draft cassette replay
|
|
579
|
+
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # live draft cassette replay (v5)
|
|
545
580
|
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rewrite # re-record live draft cassette
|
|
581
|
+
YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1 pytest tests/test_v4_live_contracts.py -k _opt_in_write -v # v4 wordstat/forecast account-level lifecycle
|
|
546
582
|
```
|
|
547
583
|
|
|
584
|
+
The v4 account-level write tier (`YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1`) creates real Wordstat and forecast reports in the production account and deletes them in the same run. There are **no cassettes** — these tests run against live API only. Created IDs are tracked in `~/.direct-cli/test-orphans.json` so that if the run is interrupted between create and delete, the next invocation will retry the cleanup automatically (see `tests/_orphan_store.py`).
|
|
585
|
+
|
|
548
586
|
#### Smoke command scripts
|
|
549
587
|
|
|
550
588
|
Every CLI subcommand is classified in `direct_cli/smoke_matrix.py`.
|
|
@@ -562,9 +600,9 @@ Current command surface:
|
|
|
562
600
|
| WSDL-backed API services | 29 |
|
|
563
601
|
| Supported API services including Reports | 30 |
|
|
564
602
|
| WSDL operations | 112 |
|
|
565
|
-
| CLI groups including `auth` |
|
|
566
|
-
| CLI subcommands including `auth` |
|
|
567
|
-
| API CLI subcommands excluding `auth` |
|
|
603
|
+
| CLI groups including `auth` | 40 |
|
|
604
|
+
| CLI subcommands including `auth` | 144 |
|
|
605
|
+
| API CLI subcommands excluding `auth` | 140 |
|
|
568
606
|
|
|
569
607
|
### API Coverage And Drift Monitoring
|
|
570
608
|
|
|
@@ -782,6 +820,8 @@ base `YANDEX_DIRECT_LOGIN`; это защищает от смешивания т
|
|
|
782
820
|
логином из project `.env`. Для нескольких аккаунтов используйте OAuth profiles
|
|
783
821
|
или профильные env vars, а не базовые credentials.
|
|
784
822
|
|
|
823
|
+
> **В тестах порядок инвертирован.** Live-API тесты (например `tests/test_v4_live_contracts.py`) сначала читают `YANDEX_DIRECT_TOKEN` / `YANDEX_DIRECT_LOGIN` из окружения, затем падают на активный профиль `direct auth`, и скипают тест если ни того ни другого нет. Это сделано специально: на машине разработчика с активным профилем обычный `pytest` не должен молча идти в боевой API. Контракт зафиксирован в `CLAUDE.md`.
|
|
824
|
+
|
|
785
825
|
Установка остаётся через `pip install direct-cli`, а запуск команд теперь идет
|
|
786
826
|
через `direct`. Вызов deprecated entrypoint `direct-cli` завершается ошибкой с
|
|
787
827
|
подсказкой `use direct instead of direct-cli`.
|
|
@@ -1082,7 +1122,7 @@ direct vcards add --campaign-id 555 --country "Россия" --city "Москв
|
|
|
1082
1122
|
direct adextensions add --callout-text "Free shipping" --dry-run
|
|
1083
1123
|
direct adimages add --name banner.png --image-data BASE64DATA --type ICON --dry-run
|
|
1084
1124
|
direct creatives add --video-id video-id --dry-run
|
|
1085
|
-
direct feeds add --name "Фид A" --url "https://example.com/feed.xml" --dry-run
|
|
1125
|
+
direct feeds add --name "Фид A" --url "https://example.com/feed.xml" --business-type RETAIL --dry-run
|
|
1086
1126
|
direct feeds update --id 18 --name "Фид A v2" --url "https://example.com/feed-v2.xml" --dry-run
|
|
1087
1127
|
direct clients update --client-info "Приоритетный клиент" --phone +70000000000 --notification-email user@example.com --notification-lang EN --email-subscription RECEIVE_RECOMMENDATIONS=YES --setting DISPLAY_STORE_RATING=NO --dry-run
|
|
1088
1128
|
direct --login CLIENT_LOGIN clients update --phone +70000000000 --notification-email user@example.com --dry-run
|
|
@@ -1155,17 +1195,22 @@ direct campaigns add --name "Тест" --start-date 2024-01-01 --dry-run
|
|
|
1155
1195
|
| Юнит / CLI / dry-run | *(без маркера)* | Нет | Нет |
|
|
1156
1196
|
| Read-only интеграция | `-m integration` | Да (prod API, только чтение) | Да |
|
|
1157
1197
|
| Write интеграция | `-m integration_write` | Нет (replay VCR-кассет) | Нет |
|
|
1158
|
-
| Live draft write интеграция | `-m integration_live_write` | Да при записи, иначе VCR replay | Да + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
1198
|
+
| Live draft write интеграция (v5) | `-m integration_live_write` | Да при записи, иначе VCR replay | Да + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
1199
|
+
| v4 live read | `-m v4_live_read` | Да (prod v4 JSON API, только чтение) | Да |
|
|
1200
|
+
| v4 live запись отчётов на уровне аккаунта (opt-in) | `-k _opt_in_write` в `tests/test_v4_live_contracts.py` | Да (prod v4) | Да + `YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1` |
|
|
1159
1201
|
|
|
1160
1202
|
```bash
|
|
1161
1203
|
pip install -e ".[dev]"
|
|
1162
1204
|
pytest # быстрый уровень — без токена
|
|
1163
1205
|
pytest -m integration -v # read-only интеграция (нужен токен)
|
|
1164
1206
|
pytest -m integration_write -v # replay write-кассет (токен не нужен)
|
|
1165
|
-
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # replay live draft-кассеты
|
|
1207
|
+
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # replay live draft-кассеты (v5)
|
|
1166
1208
|
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rewrite # перезапись live draft-кассеты
|
|
1209
|
+
YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1 pytest tests/test_v4_live_contracts.py -k _opt_in_write -v # жизненный цикл v4 wordstat/forecast
|
|
1167
1210
|
```
|
|
1168
1211
|
|
|
1212
|
+
Уровень v4 account-level write (`YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1`) создаёт настоящие Wordstat-отчёты и прогнозы в боевом аккаунте и удаляет их в том же запуске. **Кассет нет** — эти тесты идут только в живой API. Созданные ID пишутся в `~/.direct-cli/test-orphans.json`: если запуск оборвался между create и delete, при следующем вызове осиротевшие ID будут удалены автоматически (см. `tests/_orphan_store.py`).
|
|
1213
|
+
|
|
1169
1214
|
#### Smoke-скрипты команд
|
|
1170
1215
|
|
|
1171
1216
|
Каждая CLI-подкоманда классифицирована в `direct_cli/smoke_matrix.py`.
|
|
@@ -1183,9 +1228,9 @@ YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rew
|
|
|
1183
1228
|
| WSDL-backed API services | 29 |
|
|
1184
1229
|
| API services с учётом Reports | 30 |
|
|
1185
1230
|
| WSDL operations | 112 |
|
|
1186
|
-
| CLI groups с `auth` |
|
|
1187
|
-
| CLI subcommands с `auth` |
|
|
1188
|
-
| API CLI subcommands без `auth` |
|
|
1231
|
+
| CLI groups с `auth` | 40 |
|
|
1232
|
+
| CLI subcommands с `auth` | 144 |
|
|
1233
|
+
| API CLI subcommands без `auth` | 140 |
|
|
1189
1234
|
|
|
1190
1235
|
#### Live sandbox write smoke
|
|
1191
1236
|
|
|
@@ -82,6 +82,8 @@ fall back to base `YANDEX_DIRECT_LOGIN`; this prevents mixing a profile token
|
|
|
82
82
|
with a login from the project `.env`. For multi-account setups, prefer OAuth
|
|
83
83
|
profiles or profile-specific env vars instead of base credentials.
|
|
84
84
|
|
|
85
|
+
> **Tests use the inverted order.** Live-API test suites (e.g. `tests/test_v4_live_contracts.py`) read `YANDEX_DIRECT_TOKEN` / `YANDEX_DIRECT_LOGIN` from the environment first, only then fall back to the active `direct auth` profile, and skip the test if neither is set. This is intentional: a developer machine with an active profile must not silently hit production on a plain `pytest` invocation. See `CLAUDE.md` for the contract.
|
|
86
|
+
|
|
85
87
|
Install with `pip install direct-cli`, then run commands with `direct`.
|
|
86
88
|
Invoking the deprecated `direct-cli` entrypoint exits with
|
|
87
89
|
`use direct instead of direct-cli`.
|
|
@@ -115,6 +117,24 @@ direct v4goals get-retargeting-goals --campaign-ids 123,456 --format table
|
|
|
115
117
|
direct v4goals get-stat-goals --campaign-ids 123 --dry-run
|
|
116
118
|
```
|
|
117
119
|
|
|
120
|
+
### V4 Live Tags
|
|
121
|
+
|
|
122
|
+
Campaign tags are managed as `{TagID, Tag}` pairs. Use `TagID=0` to create a
|
|
123
|
+
new campaign tag. Banner/ad tags are assigned by campaign tag IDs. Update
|
|
124
|
+
methods replace the full tag list for the target campaign or banner, so pass
|
|
125
|
+
existing tags again if they must remain assigned. Ad group tags are filter-only
|
|
126
|
+
through `direct adgroups get --tag-ids/--tags`; this release does not add ad
|
|
127
|
+
group tag mutation commands.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
direct v4tags get-campaigns --campaign-ids 3193279,1634563
|
|
131
|
+
direct v4tags get-banners --banner-ids 2571700,2571745
|
|
132
|
+
direct v4tags get-banners --campaign-ids 3193279
|
|
133
|
+
direct v4tags update-campaigns --campaign-id 3193279 --tag 0=akapulko --tag 16590=orange --dry-run
|
|
134
|
+
direct v4tags update-banners --banner-ids 2571700,2571745 --tag-ids 16590,16734 --dry-run
|
|
135
|
+
direct v4tags update-banners --banner-ids 2571700 --clear-tags --dry-run
|
|
136
|
+
```
|
|
137
|
+
|
|
118
138
|
### V4 Live Events
|
|
119
139
|
|
|
120
140
|
```bash
|
|
@@ -135,6 +155,19 @@ direct v4wordstat get-report --report-id 123 --format table
|
|
|
135
155
|
direct v4wordstat delete-report --report-id 123
|
|
136
156
|
```
|
|
137
157
|
|
|
158
|
+
### V4 Live Budget Forecasts
|
|
159
|
+
|
|
160
|
+
Budget forecasts are asynchronous. Direct CLI makes exactly one API call per
|
|
161
|
+
command and does not poll automatically; repeat `list` or `get` yourself until
|
|
162
|
+
the forecast is ready.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
direct v4forecast create --phrases "buy laptop,buy desktop" --geo-ids 213 --currency RUB
|
|
166
|
+
direct v4forecast list --format table
|
|
167
|
+
direct v4forecast get --forecast-id 123 --format table
|
|
168
|
+
direct v4forecast delete --forecast-id 123
|
|
169
|
+
```
|
|
170
|
+
|
|
138
171
|
### V4 Live Finance
|
|
139
172
|
|
|
140
173
|
Finance methods require an extra financial token for money operations. In the
|
|
@@ -419,7 +452,7 @@ direct vcards add --campaign-id 555 --country "Russia" --city "Moscow" --company
|
|
|
419
452
|
direct adextensions add --callout-text "Free shipping" --dry-run
|
|
420
453
|
direct adimages add --name banner.png --image-data BASE64DATA --type ICON --dry-run
|
|
421
454
|
direct creatives add --video-id video-id --dry-run
|
|
422
|
-
direct feeds add --name "Feed A" --url "https://example.com/feed.xml" --dry-run
|
|
455
|
+
direct feeds add --name "Feed A" --url "https://example.com/feed.xml" --business-type RETAIL --dry-run
|
|
423
456
|
direct feeds update --id 18 --name "Feed A v2" --url "https://example.com/feed-v2.xml" --dry-run
|
|
424
457
|
direct clients update --client-info "Priority client" --phone +70000000000 --notification-email user@example.com --notification-lang EN --email-subscription RECEIVE_RECOMMENDATIONS=YES --setting DISPLAY_STORE_RATING=NO --dry-run
|
|
425
458
|
direct --login CLIENT_LOGIN clients update --phone +70000000000 --notification-email user@example.com --dry-run
|
|
@@ -491,17 +524,22 @@ Four tiers of tests live under `tests/`:
|
|
|
491
524
|
| Unit / CLI wiring / dry-run | *(none)* | No | No |
|
|
492
525
|
| Read-only integration | `-m integration` | Yes (production API, read-only) | Yes |
|
|
493
526
|
| Write integration | `-m integration_write` | No (replays VCR cassettes) | No |
|
|
494
|
-
| Live draft write integration | `-m integration_live_write` | Yes when recording, otherwise VCR replay | Yes + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
527
|
+
| Live draft write integration (v5) | `-m integration_live_write` | Yes when recording, otherwise VCR replay | Yes + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
528
|
+
| v4 live read | `-m v4_live_read` | Yes (production v4 JSON API, read-only) | Yes |
|
|
529
|
+
| v4 live account-level report write (opt-in) | `-k _opt_in_write` in `tests/test_v4_live_contracts.py` | Yes (production v4) | Yes + `YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1` |
|
|
495
530
|
|
|
496
531
|
```bash
|
|
497
532
|
pip install -e ".[dev]"
|
|
498
533
|
pytest # fast tier — no token
|
|
499
534
|
pytest -m integration -v # read-only integration tests (needs token)
|
|
500
535
|
pytest -m integration_write -v # write cassette replay (no token needed)
|
|
501
|
-
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # live draft cassette replay
|
|
536
|
+
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # live draft cassette replay (v5)
|
|
502
537
|
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rewrite # re-record live draft cassette
|
|
538
|
+
YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1 pytest tests/test_v4_live_contracts.py -k _opt_in_write -v # v4 wordstat/forecast account-level lifecycle
|
|
503
539
|
```
|
|
504
540
|
|
|
541
|
+
The v4 account-level write tier (`YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1`) creates real Wordstat and forecast reports in the production account and deletes them in the same run. There are **no cassettes** — these tests run against live API only. Created IDs are tracked in `~/.direct-cli/test-orphans.json` so that if the run is interrupted between create and delete, the next invocation will retry the cleanup automatically (see `tests/_orphan_store.py`).
|
|
542
|
+
|
|
505
543
|
#### Smoke command scripts
|
|
506
544
|
|
|
507
545
|
Every CLI subcommand is classified in `direct_cli/smoke_matrix.py`.
|
|
@@ -519,9 +557,9 @@ Current command surface:
|
|
|
519
557
|
| WSDL-backed API services | 29 |
|
|
520
558
|
| Supported API services including Reports | 30 |
|
|
521
559
|
| WSDL operations | 112 |
|
|
522
|
-
| CLI groups including `auth` |
|
|
523
|
-
| CLI subcommands including `auth` |
|
|
524
|
-
| API CLI subcommands excluding `auth` |
|
|
560
|
+
| CLI groups including `auth` | 40 |
|
|
561
|
+
| CLI subcommands including `auth` | 144 |
|
|
562
|
+
| API CLI subcommands excluding `auth` | 140 |
|
|
525
563
|
|
|
526
564
|
### API Coverage And Drift Monitoring
|
|
527
565
|
|
|
@@ -739,6 +777,8 @@ base `YANDEX_DIRECT_LOGIN`; это защищает от смешивания т
|
|
|
739
777
|
логином из project `.env`. Для нескольких аккаунтов используйте OAuth profiles
|
|
740
778
|
или профильные env vars, а не базовые credentials.
|
|
741
779
|
|
|
780
|
+
> **В тестах порядок инвертирован.** Live-API тесты (например `tests/test_v4_live_contracts.py`) сначала читают `YANDEX_DIRECT_TOKEN` / `YANDEX_DIRECT_LOGIN` из окружения, затем падают на активный профиль `direct auth`, и скипают тест если ни того ни другого нет. Это сделано специально: на машине разработчика с активным профилем обычный `pytest` не должен молча идти в боевой API. Контракт зафиксирован в `CLAUDE.md`.
|
|
781
|
+
|
|
742
782
|
Установка остаётся через `pip install direct-cli`, а запуск команд теперь идет
|
|
743
783
|
через `direct`. Вызов deprecated entrypoint `direct-cli` завершается ошибкой с
|
|
744
784
|
подсказкой `use direct instead of direct-cli`.
|
|
@@ -1039,7 +1079,7 @@ direct vcards add --campaign-id 555 --country "Россия" --city "Москв
|
|
|
1039
1079
|
direct adextensions add --callout-text "Free shipping" --dry-run
|
|
1040
1080
|
direct adimages add --name banner.png --image-data BASE64DATA --type ICON --dry-run
|
|
1041
1081
|
direct creatives add --video-id video-id --dry-run
|
|
1042
|
-
direct feeds add --name "Фид A" --url "https://example.com/feed.xml" --dry-run
|
|
1082
|
+
direct feeds add --name "Фид A" --url "https://example.com/feed.xml" --business-type RETAIL --dry-run
|
|
1043
1083
|
direct feeds update --id 18 --name "Фид A v2" --url "https://example.com/feed-v2.xml" --dry-run
|
|
1044
1084
|
direct clients update --client-info "Приоритетный клиент" --phone +70000000000 --notification-email user@example.com --notification-lang EN --email-subscription RECEIVE_RECOMMENDATIONS=YES --setting DISPLAY_STORE_RATING=NO --dry-run
|
|
1045
1085
|
direct --login CLIENT_LOGIN clients update --phone +70000000000 --notification-email user@example.com --dry-run
|
|
@@ -1112,17 +1152,22 @@ direct campaigns add --name "Тест" --start-date 2024-01-01 --dry-run
|
|
|
1112
1152
|
| Юнит / CLI / dry-run | *(без маркера)* | Нет | Нет |
|
|
1113
1153
|
| Read-only интеграция | `-m integration` | Да (prod API, только чтение) | Да |
|
|
1114
1154
|
| Write интеграция | `-m integration_write` | Нет (replay VCR-кассет) | Нет |
|
|
1115
|
-
| Live draft write интеграция | `-m integration_live_write` | Да при записи, иначе VCR replay | Да + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
1155
|
+
| Live draft write интеграция (v5) | `-m integration_live_write` | Да при записи, иначе VCR replay | Да + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
1156
|
+
| v4 live read | `-m v4_live_read` | Да (prod v4 JSON API, только чтение) | Да |
|
|
1157
|
+
| v4 live запись отчётов на уровне аккаунта (opt-in) | `-k _opt_in_write` в `tests/test_v4_live_contracts.py` | Да (prod v4) | Да + `YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1` |
|
|
1116
1158
|
|
|
1117
1159
|
```bash
|
|
1118
1160
|
pip install -e ".[dev]"
|
|
1119
1161
|
pytest # быстрый уровень — без токена
|
|
1120
1162
|
pytest -m integration -v # read-only интеграция (нужен токен)
|
|
1121
1163
|
pytest -m integration_write -v # replay write-кассет (токен не нужен)
|
|
1122
|
-
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # replay live draft-кассеты
|
|
1164
|
+
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # replay live draft-кассеты (v5)
|
|
1123
1165
|
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rewrite # перезапись live draft-кассеты
|
|
1166
|
+
YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1 pytest tests/test_v4_live_contracts.py -k _opt_in_write -v # жизненный цикл v4 wordstat/forecast
|
|
1124
1167
|
```
|
|
1125
1168
|
|
|
1169
|
+
Уровень v4 account-level write (`YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1`) создаёт настоящие Wordstat-отчёты и прогнозы в боевом аккаунте и удаляет их в том же запуске. **Кассет нет** — эти тесты идут только в живой API. Созданные ID пишутся в `~/.direct-cli/test-orphans.json`: если запуск оборвался между create и delete, при следующем вызове осиротевшие ID будут удалены автоматически (см. `tests/_orphan_store.py`).
|
|
1170
|
+
|
|
1126
1171
|
#### Smoke-скрипты команд
|
|
1127
1172
|
|
|
1128
1173
|
Каждая CLI-подкоманда классифицирована в `direct_cli/smoke_matrix.py`.
|
|
@@ -1140,9 +1185,9 @@ YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rew
|
|
|
1140
1185
|
| WSDL-backed API services | 29 |
|
|
1141
1186
|
| API services с учётом Reports | 30 |
|
|
1142
1187
|
| WSDL operations | 112 |
|
|
1143
|
-
| CLI groups с `auth` |
|
|
1144
|
-
| CLI subcommands с `auth` |
|
|
1145
|
-
| API CLI subcommands без `auth` |
|
|
1188
|
+
| CLI groups с `auth` | 40 |
|
|
1189
|
+
| CLI subcommands с `auth` | 144 |
|
|
1190
|
+
| API CLI subcommands без `auth` | 140 |
|
|
1146
1191
|
|
|
1147
1192
|
#### Live sandbox write smoke
|
|
1148
1193
|
|
|
@@ -43,10 +43,12 @@ from .commands.strategies import strategies
|
|
|
43
43
|
from .commands.auth import auth
|
|
44
44
|
from .commands.balance import balance
|
|
45
45
|
from .commands.v4events import v4events
|
|
46
|
+
from .commands.v4forecast import v4forecast
|
|
46
47
|
from .commands.v4finance import v4finance
|
|
47
48
|
from .commands.v4account import v4account
|
|
48
|
-
from .commands.v4shells import
|
|
49
|
+
from .commands.v4shells import v4meta
|
|
49
50
|
from .commands.v4goals import v4goals
|
|
51
|
+
from .commands.v4tags import v4tags
|
|
50
52
|
from .commands.v4wordstat import v4wordstat
|
|
51
53
|
|
|
52
54
|
# Load .env file
|
|
@@ -186,6 +188,7 @@ for command in (
|
|
|
186
188
|
v4goals,
|
|
187
189
|
v4events,
|
|
188
190
|
v4wordstat,
|
|
191
|
+
v4tags,
|
|
189
192
|
v4forecast,
|
|
190
193
|
v4meta,
|
|
191
194
|
auth,
|
|
@@ -163,21 +163,40 @@ def get(
|
|
|
163
163
|
default="TEXT_AD",
|
|
164
164
|
help="Ad type",
|
|
165
165
|
)
|
|
166
|
-
@click.option("--title", help="Ad title (TEXT_AD
|
|
167
|
-
@click.option("--text", help="Ad text (TEXT_AD
|
|
168
|
-
@click.option("--href", help="Ad URL (TEXT_AD
|
|
169
|
-
@click.option("--image-hash", help="Ad image hash
|
|
166
|
+
@click.option("--title", help="Ad title (TEXT_AD / MOBILE_APP_AD)")
|
|
167
|
+
@click.option("--text", help="Ad text (TEXT_AD / MOBILE_APP_AD)")
|
|
168
|
+
@click.option("--href", help="Ad URL (TEXT_AD / TEXT_IMAGE_AD)")
|
|
169
|
+
@click.option("--image-hash", help="Ad image hash (TEXT_IMAGE_AD / MOBILE_APP_AD)")
|
|
170
|
+
@click.option(
|
|
171
|
+
"--action",
|
|
172
|
+
help="MOBILE_APP_AD call-to-action (MobileAppAdActionEnum, e.g. INSTALL)",
|
|
173
|
+
)
|
|
174
|
+
@click.option("--tracking-url", help="MOBILE_APP_AD tracking URL")
|
|
175
|
+
@click.option("--age-label", help="MOBILE_APP_AD age label (MobAppAgeLabelEnum)")
|
|
170
176
|
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
171
177
|
@click.pass_context
|
|
172
|
-
def add(
|
|
178
|
+
def add(
|
|
179
|
+
ctx,
|
|
180
|
+
adgroup_id,
|
|
181
|
+
ad_type,
|
|
182
|
+
title,
|
|
183
|
+
text,
|
|
184
|
+
href,
|
|
185
|
+
image_hash,
|
|
186
|
+
action,
|
|
187
|
+
tracking_url,
|
|
188
|
+
age_label,
|
|
189
|
+
dry_run,
|
|
190
|
+
):
|
|
173
191
|
"""Add new ad"""
|
|
174
192
|
try:
|
|
175
193
|
ad_type_norm = (ad_type or "TEXT_AD").upper().replace("-", "_")
|
|
176
|
-
supported_types = {"TEXT_AD", "TEXT_IMAGE_AD"}
|
|
194
|
+
supported_types = {"TEXT_AD", "TEXT_IMAGE_AD", "MOBILE_APP_AD"}
|
|
177
195
|
if ad_type_norm not in supported_types:
|
|
178
196
|
raise click.UsageError(
|
|
179
197
|
"Invalid value for '--type': "
|
|
180
|
-
f"{ad_type!r} is not one of
|
|
198
|
+
f"{ad_type!r} is not one of "
|
|
199
|
+
"'TEXT_AD', 'TEXT_IMAGE_AD', 'MOBILE_APP_AD'."
|
|
181
200
|
)
|
|
182
201
|
|
|
183
202
|
ad_data = {"AdGroupId": adgroup_id}
|
|
@@ -200,6 +219,11 @@ def add(ctx, adgroup_id, ad_type, title, text, href, image_hash, dry_run):
|
|
|
200
219
|
"Href": href,
|
|
201
220
|
}
|
|
202
221
|
elif ad_type_norm == "TEXT_IMAGE_AD":
|
|
222
|
+
if title or text:
|
|
223
|
+
raise click.UsageError(
|
|
224
|
+
"--title/--text are only valid for TEXT_AD. "
|
|
225
|
+
"For TEXT_IMAGE_AD, use --image-hash and --href."
|
|
226
|
+
)
|
|
203
227
|
if not image_hash or not href:
|
|
204
228
|
raise click.UsageError(
|
|
205
229
|
"TEXT_IMAGE_AD requires both --image-hash and --href"
|
|
@@ -208,10 +232,37 @@ def add(ctx, adgroup_id, ad_type, title, text, href, image_hash, dry_run):
|
|
|
208
232
|
"AdImageHash": image_hash,
|
|
209
233
|
"Href": href,
|
|
210
234
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
235
|
+
elif ad_type_norm == "MOBILE_APP_AD":
|
|
236
|
+
if href:
|
|
237
|
+
raise click.UsageError(
|
|
238
|
+
"--href does not apply to MOBILE_APP_AD. "
|
|
239
|
+
"Use --tracking-url instead."
|
|
240
|
+
)
|
|
241
|
+
missing_fields = [
|
|
242
|
+
option_name
|
|
243
|
+
for option_name, value in (
|
|
244
|
+
("--title", title),
|
|
245
|
+
("--text", text),
|
|
246
|
+
("--action", action),
|
|
247
|
+
)
|
|
248
|
+
if not value
|
|
249
|
+
]
|
|
250
|
+
if missing_fields:
|
|
251
|
+
raise click.UsageError(
|
|
252
|
+
"MOBILE_APP_AD requires " + ", ".join(missing_fields)
|
|
253
|
+
)
|
|
254
|
+
mobile_app_ad = {
|
|
255
|
+
"Title": title,
|
|
256
|
+
"Text": text,
|
|
257
|
+
"Action": action.upper(),
|
|
258
|
+
}
|
|
259
|
+
if image_hash:
|
|
260
|
+
mobile_app_ad["AdImageHash"] = image_hash
|
|
261
|
+
if tracking_url:
|
|
262
|
+
mobile_app_ad["TrackingUrl"] = tracking_url
|
|
263
|
+
if age_label:
|
|
264
|
+
mobile_app_ad["AgeLabel"] = age_label.upper()
|
|
265
|
+
ad_data["MobileAppAd"] = mobile_app_ad
|
|
215
266
|
|
|
216
267
|
body = {"method": "add", "params": {"Ads": [ad_data]}}
|
|
217
268
|
|
|
@@ -237,30 +288,98 @@ def add(ctx, adgroup_id, ad_type, title, text, href, image_hash, dry_run):
|
|
|
237
288
|
|
|
238
289
|
@ads.command()
|
|
239
290
|
@click.option("--id", "ad_id", required=True, type=int, help="Ad ID")
|
|
240
|
-
@click.option(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
291
|
+
@click.option(
|
|
292
|
+
"--type",
|
|
293
|
+
"ad_type",
|
|
294
|
+
required=True,
|
|
295
|
+
help="Ad subtype: TEXT_AD | TEXT_IMAGE_AD | MOBILE_APP_AD",
|
|
296
|
+
)
|
|
297
|
+
@click.option(
|
|
298
|
+
"--status",
|
|
299
|
+
help=(
|
|
300
|
+
"Deprecated: not part of WSDL AdUpdateItem. "
|
|
301
|
+
"Use 'direct ads suspend/resume/archive/unarchive' instead."
|
|
302
|
+
),
|
|
303
|
+
)
|
|
304
|
+
@click.option("--title", help="Title (TEXT_AD / MOBILE_APP_AD)")
|
|
305
|
+
@click.option("--text", help="Text (TEXT_AD / MOBILE_APP_AD)")
|
|
306
|
+
@click.option("--href", help="URL (TEXT_AD / TEXT_IMAGE_AD)")
|
|
307
|
+
@click.option("--image-hash", help="Image hash (TEXT_IMAGE_AD / MOBILE_APP_AD)")
|
|
308
|
+
@click.option(
|
|
309
|
+
"--action",
|
|
310
|
+
help="MOBILE_APP_AD call-to-action (MobileAppAdActionEnum, e.g. INSTALL)",
|
|
311
|
+
)
|
|
312
|
+
@click.option("--tracking-url", help="MOBILE_APP_AD tracking URL")
|
|
313
|
+
@click.option("--age-label", help="MOBILE_APP_AD age label (MobAppAgeLabelEnum)")
|
|
245
314
|
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
246
315
|
@click.pass_context
|
|
247
|
-
def update(
|
|
316
|
+
def update(
|
|
317
|
+
ctx,
|
|
318
|
+
ad_id,
|
|
319
|
+
ad_type,
|
|
320
|
+
status,
|
|
321
|
+
title,
|
|
322
|
+
text,
|
|
323
|
+
href,
|
|
324
|
+
image_hash,
|
|
325
|
+
action,
|
|
326
|
+
tracking_url,
|
|
327
|
+
age_label,
|
|
328
|
+
dry_run,
|
|
329
|
+
):
|
|
248
330
|
"""Update ad"""
|
|
331
|
+
if status:
|
|
332
|
+
raise click.UsageError(
|
|
333
|
+
"Use 'direct ads suspend/resume/archive/unarchive' to change status. "
|
|
334
|
+
"The --status flag is not supported by WSDL AdUpdateItem."
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
ad_type_norm = ad_type.upper().replace("-", "_")
|
|
338
|
+
supported_types = {"TEXT_AD", "TEXT_IMAGE_AD", "MOBILE_APP_AD"}
|
|
339
|
+
if ad_type_norm not in supported_types:
|
|
340
|
+
raise click.UsageError(
|
|
341
|
+
"Invalid value for '--type': "
|
|
342
|
+
f"{ad_type!r} is not one of "
|
|
343
|
+
"'TEXT_AD', 'TEXT_IMAGE_AD', 'MOBILE_APP_AD'."
|
|
344
|
+
)
|
|
345
|
+
|
|
249
346
|
try:
|
|
250
347
|
ad_data = {"Id": ad_id}
|
|
251
348
|
|
|
252
|
-
if
|
|
253
|
-
|
|
254
|
-
if any([title, text, href]):
|
|
255
|
-
ad_data["TextAd"] = {}
|
|
349
|
+
if ad_type_norm == "TEXT_AD":
|
|
350
|
+
text_ad = {}
|
|
256
351
|
if title:
|
|
257
|
-
|
|
352
|
+
text_ad["Title"] = title
|
|
258
353
|
if text:
|
|
259
|
-
|
|
354
|
+
text_ad["Text"] = text
|
|
355
|
+
if href:
|
|
356
|
+
text_ad["Href"] = href
|
|
357
|
+
if text_ad:
|
|
358
|
+
ad_data["TextAd"] = text_ad
|
|
359
|
+
elif ad_type_norm == "TEXT_IMAGE_AD":
|
|
360
|
+
text_image_ad = {}
|
|
361
|
+
if image_hash:
|
|
362
|
+
text_image_ad["AdImageHash"] = image_hash
|
|
260
363
|
if href:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
364
|
+
text_image_ad["Href"] = href
|
|
365
|
+
if text_image_ad:
|
|
366
|
+
ad_data["TextImageAd"] = text_image_ad
|
|
367
|
+
elif ad_type_norm == "MOBILE_APP_AD":
|
|
368
|
+
mobile_app_ad = {}
|
|
369
|
+
if title:
|
|
370
|
+
mobile_app_ad["Title"] = title
|
|
371
|
+
if text:
|
|
372
|
+
mobile_app_ad["Text"] = text
|
|
373
|
+
if image_hash:
|
|
374
|
+
mobile_app_ad["AdImageHash"] = image_hash
|
|
375
|
+
if action:
|
|
376
|
+
mobile_app_ad["Action"] = action.upper()
|
|
377
|
+
if tracking_url:
|
|
378
|
+
mobile_app_ad["TrackingUrl"] = tracking_url
|
|
379
|
+
if age_label:
|
|
380
|
+
mobile_app_ad["AgeLabel"] = age_label.upper()
|
|
381
|
+
if mobile_app_ad:
|
|
382
|
+
ad_data["MobileAppAd"] = mobile_app_ad
|
|
264
383
|
|
|
265
384
|
body = {"method": "update", "params": {"Ads": [ad_data]}}
|
|
266
385
|
|