direct-cli 0.3.6__tar.gz → 0.3.7__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.7}/PKG-INFO +38 -7
- {direct_cli-0.3.6 → direct_cli-0.3.7}/README.md +37 -6
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/cli.py +4 -1
- direct_cli-0.3.7/direct_cli/commands/v4forecast.py +168 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/v4shells.py +0 -5
- direct_cli-0.3.7/direct_cli/commands/v4tags.py +263 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/smoke_matrix.py +8 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/v4_contracts.py +86 -24
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli.egg-info/PKG-INFO +38 -7
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli.egg-info/SOURCES.txt +4 -1
- {direct_cli-0.3.6 → direct_cli-0.3.7}/pyproject.toml +1 -1
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/sandbox_write_live.py +60 -3
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/test_safe_commands.sh +12 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/API_COVERAGE.md +3 -3
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/api_coverage_payloads.py +8 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_cli_contract.py +1 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_comprehensive.py +1 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_smoke_matrix.py +75 -4
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4_contracts.py +86 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4_foundation.py +2 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4_safety.py +14 -0
- direct_cli-0.3.7/tests/test_v4forecast.py +238 -0
- direct_cli-0.3.7/tests/test_v4tags.py +321 -0
- direct_cli-0.3.6/.github/workflows/claude-code-review.yml +0 -44
- {direct_cli-0.3.6 → direct_cli-0.3.7}/.env.example +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/.github/copilot-instructions.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/.github/workflows/api-coverage.yml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/.github/workflows/claude.yml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/.github/workflows/quality.yml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/.gitignore +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/AGENTS.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/CHANGELOG.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/CLAUDE.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/MANIFEST.in +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_deprecated.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_smoke_probes.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/api.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/auth.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/adextensions.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/adgroups.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/adimages.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/ads.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/advideos.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/agencyclients.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/audiencetargets.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/auth.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/balance.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/bidmodifiers.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/bids.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/businesses.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/campaigns.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/changes.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/clients.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/creatives.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/dictionaries.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/dynamicads.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/feeds.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/keywordbids.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/keywords.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/reports.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/retargeting.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/sitelinks.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/smartadtargets.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/strategies.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/turbopages.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/v4account.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/v4events.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/v4finance.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/v4goals.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/v4wordstat.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/commands/vcards.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/output.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/reports_coverage.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/utils.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/v4/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/v4/money.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli/wsdl_coverage.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli.egg-info/requires.txt +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/anonymize_cassettes.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/build_api_coverage_checklist.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/build_api_coverage_report.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/check_reports_drift.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/check_wsdl_drift.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/patch_vendor_imports.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/refresh_reports_cache.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/refresh_wsdl_cache.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/release_pypi.sh +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/test_dangerous_commands.sh +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/test_sandbox_write.sh +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/scripts/update_vendor.sh +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/setup.cfg +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/setup.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/API_ISSUE_AUDIT.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/MANUAL_COVERAGE.md +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/__init__.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_adgroups_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_adimages_add_get_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_ads_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_advideos_add_get.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_suspend_resume.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_bids_set.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_campaign_create_get_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_suspend_resume.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_keywordbids_set.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_suspend_resume.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_sitelinks_add_get_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_suspend_resume.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/conftest.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/fixtures/test-video.mp4 +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/reports_cache/raw/fields-list.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/reports_cache/raw/headers.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/reports_cache/raw/period.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/reports_cache/raw/spec.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/reports_cache/raw/type.html +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/reports_cache/spec.json +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_api_coverage.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_auth_oauth.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_auth_op.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_balance.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_cli.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_dry_run.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_integration.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_integration_live_write.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_integration_write.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_low_coverage_payloads.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_reports_drift.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_reports_parsing.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_transport_contract.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4_live_contracts.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4account.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4events.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4finance_money.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4finance_read.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4goals.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_v4wordstat.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/test_vendor_imports.py +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/adextensions.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/adgroups.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/adimages.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/ads.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/advideos.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/agencyclients.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/audiencetargets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/bidmodifiers.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/bids.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/businesses.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/campaigns.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/changes.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/clients.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/creatives.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/dictionaries.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/feeds.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/imports/general.xsd +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/keywordbids.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/keywords.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/keywordsresearch.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/leads.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/retargetinglists.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/sitelinks.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/smartadtargets.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/strategies.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/turbopages.xml +0 -0
- {direct_cli-0.3.6 → direct_cli-0.3.7}/tests/wsdl_cache/vcards.xml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: direct-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Command-line interface for Yandex Direct API
|
|
5
5
|
Author: axisrow
|
|
6
6
|
License: MIT
|
|
@@ -158,6 +158,24 @@ direct v4goals get-retargeting-goals --campaign-ids 123,456 --format table
|
|
|
158
158
|
direct v4goals get-stat-goals --campaign-ids 123 --dry-run
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
### V4 Live Tags
|
|
162
|
+
|
|
163
|
+
Campaign tags are managed as `{TagID, Tag}` pairs. Use `TagID=0` to create a
|
|
164
|
+
new campaign tag. Banner/ad tags are assigned by campaign tag IDs. Update
|
|
165
|
+
methods replace the full tag list for the target campaign or banner, so pass
|
|
166
|
+
existing tags again if they must remain assigned. Ad group tags are filter-only
|
|
167
|
+
through `direct adgroups get --tag-ids/--tags`; this release does not add ad
|
|
168
|
+
group tag mutation commands.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
direct v4tags get-campaigns --campaign-ids 3193279,1634563
|
|
172
|
+
direct v4tags get-banners --banner-ids 2571700,2571745
|
|
173
|
+
direct v4tags get-banners --campaign-ids 3193279
|
|
174
|
+
direct v4tags update-campaigns --campaign-id 3193279 --tag 0=akapulko --tag 16590=orange --dry-run
|
|
175
|
+
direct v4tags update-banners --banner-ids 2571700,2571745 --tag-ids 16590,16734 --dry-run
|
|
176
|
+
direct v4tags update-banners --banner-ids 2571700 --clear-tags --dry-run
|
|
177
|
+
```
|
|
178
|
+
|
|
161
179
|
### V4 Live Events
|
|
162
180
|
|
|
163
181
|
```bash
|
|
@@ -178,6 +196,19 @@ direct v4wordstat get-report --report-id 123 --format table
|
|
|
178
196
|
direct v4wordstat delete-report --report-id 123
|
|
179
197
|
```
|
|
180
198
|
|
|
199
|
+
### V4 Live Budget Forecasts
|
|
200
|
+
|
|
201
|
+
Budget forecasts are asynchronous. Direct CLI makes exactly one API call per
|
|
202
|
+
command and does not poll automatically; repeat `list` or `get` yourself until
|
|
203
|
+
the forecast is ready.
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
direct v4forecast create --phrases "buy laptop,buy desktop" --geo-ids 213 --currency RUB
|
|
207
|
+
direct v4forecast list --format table
|
|
208
|
+
direct v4forecast get --forecast-id 123 --format table
|
|
209
|
+
direct v4forecast delete --forecast-id 123
|
|
210
|
+
```
|
|
211
|
+
|
|
181
212
|
### V4 Live Finance
|
|
182
213
|
|
|
183
214
|
Finance methods require an extra financial token for money operations. In the
|
|
@@ -562,9 +593,9 @@ Current command surface:
|
|
|
562
593
|
| WSDL-backed API services | 29 |
|
|
563
594
|
| Supported API services including Reports | 30 |
|
|
564
595
|
| WSDL operations | 112 |
|
|
565
|
-
| CLI groups including `auth` |
|
|
566
|
-
| CLI subcommands including `auth` |
|
|
567
|
-
| API CLI subcommands excluding `auth` |
|
|
596
|
+
| CLI groups including `auth` | 40 |
|
|
597
|
+
| CLI subcommands including `auth` | 144 |
|
|
598
|
+
| API CLI subcommands excluding `auth` | 140 |
|
|
568
599
|
|
|
569
600
|
### API Coverage And Drift Monitoring
|
|
570
601
|
|
|
@@ -1183,9 +1214,9 @@ YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rew
|
|
|
1183
1214
|
| WSDL-backed API services | 29 |
|
|
1184
1215
|
| API services с учётом Reports | 30 |
|
|
1185
1216
|
| WSDL operations | 112 |
|
|
1186
|
-
| CLI groups с `auth` |
|
|
1187
|
-
| CLI subcommands с `auth` |
|
|
1188
|
-
| API CLI subcommands без `auth` |
|
|
1217
|
+
| CLI groups с `auth` | 40 |
|
|
1218
|
+
| CLI subcommands с `auth` | 144 |
|
|
1219
|
+
| API CLI subcommands без `auth` | 140 |
|
|
1189
1220
|
|
|
1190
1221
|
#### Live sandbox write smoke
|
|
1191
1222
|
|
|
@@ -115,6 +115,24 @@ direct v4goals get-retargeting-goals --campaign-ids 123,456 --format table
|
|
|
115
115
|
direct v4goals get-stat-goals --campaign-ids 123 --dry-run
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
### V4 Live Tags
|
|
119
|
+
|
|
120
|
+
Campaign tags are managed as `{TagID, Tag}` pairs. Use `TagID=0` to create a
|
|
121
|
+
new campaign tag. Banner/ad tags are assigned by campaign tag IDs. Update
|
|
122
|
+
methods replace the full tag list for the target campaign or banner, so pass
|
|
123
|
+
existing tags again if they must remain assigned. Ad group tags are filter-only
|
|
124
|
+
through `direct adgroups get --tag-ids/--tags`; this release does not add ad
|
|
125
|
+
group tag mutation commands.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
direct v4tags get-campaigns --campaign-ids 3193279,1634563
|
|
129
|
+
direct v4tags get-banners --banner-ids 2571700,2571745
|
|
130
|
+
direct v4tags get-banners --campaign-ids 3193279
|
|
131
|
+
direct v4tags update-campaigns --campaign-id 3193279 --tag 0=akapulko --tag 16590=orange --dry-run
|
|
132
|
+
direct v4tags update-banners --banner-ids 2571700,2571745 --tag-ids 16590,16734 --dry-run
|
|
133
|
+
direct v4tags update-banners --banner-ids 2571700 --clear-tags --dry-run
|
|
134
|
+
```
|
|
135
|
+
|
|
118
136
|
### V4 Live Events
|
|
119
137
|
|
|
120
138
|
```bash
|
|
@@ -135,6 +153,19 @@ direct v4wordstat get-report --report-id 123 --format table
|
|
|
135
153
|
direct v4wordstat delete-report --report-id 123
|
|
136
154
|
```
|
|
137
155
|
|
|
156
|
+
### V4 Live Budget Forecasts
|
|
157
|
+
|
|
158
|
+
Budget forecasts are asynchronous. Direct CLI makes exactly one API call per
|
|
159
|
+
command and does not poll automatically; repeat `list` or `get` yourself until
|
|
160
|
+
the forecast is ready.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
direct v4forecast create --phrases "buy laptop,buy desktop" --geo-ids 213 --currency RUB
|
|
164
|
+
direct v4forecast list --format table
|
|
165
|
+
direct v4forecast get --forecast-id 123 --format table
|
|
166
|
+
direct v4forecast delete --forecast-id 123
|
|
167
|
+
```
|
|
168
|
+
|
|
138
169
|
### V4 Live Finance
|
|
139
170
|
|
|
140
171
|
Finance methods require an extra financial token for money operations. In the
|
|
@@ -519,9 +550,9 @@ Current command surface:
|
|
|
519
550
|
| WSDL-backed API services | 29 |
|
|
520
551
|
| Supported API services including Reports | 30 |
|
|
521
552
|
| WSDL operations | 112 |
|
|
522
|
-
| CLI groups including `auth` |
|
|
523
|
-
| CLI subcommands including `auth` |
|
|
524
|
-
| API CLI subcommands excluding `auth` |
|
|
553
|
+
| CLI groups including `auth` | 40 |
|
|
554
|
+
| CLI subcommands including `auth` | 144 |
|
|
555
|
+
| API CLI subcommands excluding `auth` | 140 |
|
|
525
556
|
|
|
526
557
|
### API Coverage And Drift Monitoring
|
|
527
558
|
|
|
@@ -1140,9 +1171,9 @@ YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rew
|
|
|
1140
1171
|
| WSDL-backed API services | 29 |
|
|
1141
1172
|
| API services с учётом Reports | 30 |
|
|
1142
1173
|
| WSDL operations | 112 |
|
|
1143
|
-
| CLI groups с `auth` |
|
|
1144
|
-
| CLI subcommands с `auth` |
|
|
1145
|
-
| API CLI subcommands без `auth` |
|
|
1174
|
+
| CLI groups с `auth` | 40 |
|
|
1175
|
+
| CLI subcommands с `auth` | 144 |
|
|
1176
|
+
| API CLI subcommands без `auth` | 140 |
|
|
1146
1177
|
|
|
1147
1178
|
#### Live sandbox write smoke
|
|
1148
1179
|
|
|
@@ -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,
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Yandex Direct v4 Live budget forecast commands."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..api import create_v4_client
|
|
8
|
+
from ..output import format_output, print_error
|
|
9
|
+
from ..utils import parse_csv_strings, parse_ids
|
|
10
|
+
from ..v4 import build_v4_body, call_v4
|
|
11
|
+
from ..v4_contracts import v4_method_contract
|
|
12
|
+
from .v4shells import V4_EPILOG
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _forecast_param(
|
|
16
|
+
phrases: str, geo_ids: Optional[str], currency: str
|
|
17
|
+
) -> dict[str, object]:
|
|
18
|
+
"""Build the v4 Live CreateNewForecast parameter."""
|
|
19
|
+
phrase_list = parse_csv_strings(phrases)
|
|
20
|
+
if not phrase_list:
|
|
21
|
+
raise click.UsageError("--phrases must not be empty")
|
|
22
|
+
if len(phrase_list) > 100:
|
|
23
|
+
raise click.UsageError("--phrases accepts at most 100 phrases")
|
|
24
|
+
|
|
25
|
+
param: dict[str, object] = {
|
|
26
|
+
"Phrases": phrase_list,
|
|
27
|
+
"Currency": currency,
|
|
28
|
+
}
|
|
29
|
+
if geo_ids:
|
|
30
|
+
try:
|
|
31
|
+
parsed_geo_ids = parse_ids(geo_ids)
|
|
32
|
+
except ValueError as exc:
|
|
33
|
+
raise click.UsageError(str(exc)) from exc
|
|
34
|
+
if parsed_geo_ids:
|
|
35
|
+
param["GeoID"] = parsed_geo_ids
|
|
36
|
+
return param
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _call_forecast(
|
|
40
|
+
ctx,
|
|
41
|
+
method: str,
|
|
42
|
+
param,
|
|
43
|
+
output_format: str,
|
|
44
|
+
output: Optional[str],
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Call one v4 Live budget forecast method and print formatted output."""
|
|
47
|
+
try:
|
|
48
|
+
client = create_v4_client(
|
|
49
|
+
token=ctx.obj.get("token"),
|
|
50
|
+
login=ctx.obj.get("login"),
|
|
51
|
+
profile=ctx.obj.get("profile"),
|
|
52
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
53
|
+
)
|
|
54
|
+
data = call_v4(client, method, param)
|
|
55
|
+
format_output(data, output_format, output)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print_error(str(e))
|
|
58
|
+
raise click.Abort()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@click.group(epilog=V4_EPILOG)
|
|
62
|
+
def v4forecast():
|
|
63
|
+
"""Yandex Direct v4 Live budget forecast commands."""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@v4_method_contract("CreateNewForecast")
|
|
67
|
+
@v4forecast.command()
|
|
68
|
+
@click.option("--phrases", required=True, help="Comma-separated phrases, up to 100")
|
|
69
|
+
@click.option("--geo-ids", help="Comma-separated geo region IDs")
|
|
70
|
+
@click.option(
|
|
71
|
+
"--currency",
|
|
72
|
+
default="RUB",
|
|
73
|
+
show_default=True,
|
|
74
|
+
help="Forecast currency",
|
|
75
|
+
)
|
|
76
|
+
@click.option(
|
|
77
|
+
"--format",
|
|
78
|
+
"output_format",
|
|
79
|
+
default="json",
|
|
80
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
81
|
+
help="Output format",
|
|
82
|
+
)
|
|
83
|
+
@click.option("--output", help="Output file")
|
|
84
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
85
|
+
@click.pass_context
|
|
86
|
+
def create(ctx, phrases, geo_ids, currency, output_format, output, dry_run):
|
|
87
|
+
"""Create a v4 Live budget forecast."""
|
|
88
|
+
param = _forecast_param(phrases, geo_ids, currency)
|
|
89
|
+
if dry_run:
|
|
90
|
+
format_output(build_v4_body("CreateNewForecast", param), "json", None)
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
_call_forecast(ctx, "CreateNewForecast", param, output_format, output)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@v4_method_contract("GetForecastList")
|
|
97
|
+
@v4forecast.command(name="list")
|
|
98
|
+
@click.option(
|
|
99
|
+
"--format",
|
|
100
|
+
"output_format",
|
|
101
|
+
default="json",
|
|
102
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
103
|
+
help="Output format",
|
|
104
|
+
)
|
|
105
|
+
@click.option("--output", help="Output file")
|
|
106
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
107
|
+
@click.pass_context
|
|
108
|
+
def list_forecasts(ctx, output_format, output, dry_run):
|
|
109
|
+
"""List v4 Live budget forecasts."""
|
|
110
|
+
if dry_run:
|
|
111
|
+
format_output(build_v4_body("GetForecastList"), "json", None)
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
_call_forecast(ctx, "GetForecastList", None, output_format, output)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@v4_method_contract("GetForecast")
|
|
118
|
+
@v4forecast.command()
|
|
119
|
+
@click.option(
|
|
120
|
+
"--forecast-id",
|
|
121
|
+
required=True,
|
|
122
|
+
type=click.IntRange(min=1),
|
|
123
|
+
help="Forecast ID",
|
|
124
|
+
)
|
|
125
|
+
@click.option(
|
|
126
|
+
"--format",
|
|
127
|
+
"output_format",
|
|
128
|
+
default="json",
|
|
129
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
130
|
+
help="Output format",
|
|
131
|
+
)
|
|
132
|
+
@click.option("--output", help="Output file")
|
|
133
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
134
|
+
@click.pass_context
|
|
135
|
+
def get(ctx, forecast_id, output_format, output, dry_run):
|
|
136
|
+
"""Get a ready v4 Live budget forecast."""
|
|
137
|
+
if dry_run:
|
|
138
|
+
format_output(build_v4_body("GetForecast", forecast_id), "json", None)
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
_call_forecast(ctx, "GetForecast", forecast_id, output_format, output)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@v4_method_contract("DeleteForecastReport")
|
|
145
|
+
@v4forecast.command()
|
|
146
|
+
@click.option(
|
|
147
|
+
"--forecast-id",
|
|
148
|
+
required=True,
|
|
149
|
+
type=click.IntRange(min=1),
|
|
150
|
+
help="Forecast ID",
|
|
151
|
+
)
|
|
152
|
+
@click.option(
|
|
153
|
+
"--format",
|
|
154
|
+
"output_format",
|
|
155
|
+
default="json",
|
|
156
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
157
|
+
help="Output format",
|
|
158
|
+
)
|
|
159
|
+
@click.option("--output", help="Output file")
|
|
160
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
161
|
+
@click.pass_context
|
|
162
|
+
def delete(ctx, forecast_id, output_format, output, dry_run):
|
|
163
|
+
"""Delete a v4 Live budget forecast."""
|
|
164
|
+
if dry_run:
|
|
165
|
+
format_output(build_v4_body("DeleteForecastReport", forecast_id), "json", None)
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
_call_forecast(ctx, "DeleteForecastReport", forecast_id, output_format, output)
|
|
@@ -15,11 +15,6 @@ def v4wordstat():
|
|
|
15
15
|
"""Yandex Direct v4 Live wordstat commands."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
@click.group(epilog=V4_EPILOG)
|
|
19
|
-
def v4forecast():
|
|
20
|
-
"""Yandex Direct v4 Live forecast commands."""
|
|
21
|
-
|
|
22
|
-
|
|
23
18
|
@click.group(epilog=V4_EPILOG)
|
|
24
19
|
def v4meta():
|
|
25
20
|
"""Yandex Direct v4 Live metadata commands."""
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Yandex Direct v4 Live tag commands."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from ..api import create_v4_client
|
|
8
|
+
from ..output import format_output, print_error
|
|
9
|
+
from ..utils import parse_ids
|
|
10
|
+
from ..v4 import build_v4_body, call_v4
|
|
11
|
+
from ..v4_contracts import v4_method_contract
|
|
12
|
+
from .v4shells import V4_EPILOG
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _positive_ids_param(value: str, option_name: str) -> list[int]:
|
|
16
|
+
"""Parse a required comma-separated positive integer list."""
|
|
17
|
+
try:
|
|
18
|
+
ids = parse_ids(value)
|
|
19
|
+
except ValueError as exc:
|
|
20
|
+
raise click.UsageError(str(exc)) from exc
|
|
21
|
+
if not ids:
|
|
22
|
+
raise click.UsageError(f"{option_name} must not be empty")
|
|
23
|
+
if any(item <= 0 for item in ids):
|
|
24
|
+
raise click.UsageError(f"{option_name} must contain only positive integers")
|
|
25
|
+
return ids
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _tag_ids_param(tag_ids: str) -> list[int]:
|
|
29
|
+
"""Parse v4 banner tag IDs."""
|
|
30
|
+
parsed = _positive_ids_param(tag_ids, "--tag-ids")
|
|
31
|
+
if len(parsed) > 30:
|
|
32
|
+
raise click.UsageError("--tag-ids accepts at most 30 tag IDs")
|
|
33
|
+
return parsed
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_campaigns_tags_param(campaign_ids: str) -> dict:
|
|
37
|
+
"""Build the v4 Live GetCampaignsTags parameter."""
|
|
38
|
+
return {"CampaignIDS": _positive_ids_param(campaign_ids, "--campaign-ids")}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_banners_tags_param(
|
|
42
|
+
campaign_ids: Optional[str], banner_ids: Optional[str]
|
|
43
|
+
) -> dict:
|
|
44
|
+
"""Build the v4 Live GetBannersTags parameter."""
|
|
45
|
+
if (campaign_ids is not None) == (banner_ids is not None):
|
|
46
|
+
raise click.UsageError("Use exactly one of --campaign-ids or --banner-ids")
|
|
47
|
+
if campaign_ids is not None:
|
|
48
|
+
ids = _positive_ids_param(campaign_ids, "--campaign-ids")
|
|
49
|
+
if len(ids) > 10:
|
|
50
|
+
raise click.UsageError("--campaign-ids accepts at most 10 campaign IDs")
|
|
51
|
+
return {"CampaignIDS": ids}
|
|
52
|
+
|
|
53
|
+
ids = _positive_ids_param(banner_ids or "", "--banner-ids")
|
|
54
|
+
if len(ids) > 2000:
|
|
55
|
+
raise click.UsageError("--banner-ids accepts at most 2000 banner IDs")
|
|
56
|
+
return {"BannerIDS": ids}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _campaign_tag_param(tag_specs: tuple[str, ...], clear_tags: bool) -> list[dict]:
|
|
60
|
+
"""Build campaign TagInfo objects from repeated TAG_ID=TEXT specs."""
|
|
61
|
+
if clear_tags:
|
|
62
|
+
if tag_specs:
|
|
63
|
+
raise click.UsageError("Use either --tag or --clear-tags, not both")
|
|
64
|
+
return []
|
|
65
|
+
if not tag_specs:
|
|
66
|
+
raise click.UsageError("--tag is required unless --clear-tags is used")
|
|
67
|
+
|
|
68
|
+
tags = []
|
|
69
|
+
seen_texts = set()
|
|
70
|
+
seen_existing_ids = set()
|
|
71
|
+
for spec in tag_specs:
|
|
72
|
+
text = (spec or "").strip()
|
|
73
|
+
tag_id_text, separator, tag_text = text.partition("=")
|
|
74
|
+
if not separator:
|
|
75
|
+
raise click.UsageError("--tag must use TAG_ID=TEXT")
|
|
76
|
+
tag_id_text = tag_id_text.strip()
|
|
77
|
+
tag_text = tag_text.strip()
|
|
78
|
+
try:
|
|
79
|
+
tag_id = int(tag_id_text)
|
|
80
|
+
except ValueError as exc:
|
|
81
|
+
raise click.UsageError("--tag ID must be a non-negative integer") from exc
|
|
82
|
+
if tag_id < 0:
|
|
83
|
+
raise click.UsageError("--tag ID must be a non-negative integer")
|
|
84
|
+
if tag_id > 0:
|
|
85
|
+
if tag_id in seen_existing_ids:
|
|
86
|
+
raise click.UsageError("--tag IDs must be unique")
|
|
87
|
+
seen_existing_ids.add(tag_id)
|
|
88
|
+
if not tag_text:
|
|
89
|
+
raise click.UsageError("--tag text must not be empty")
|
|
90
|
+
if len(tag_text) > 25:
|
|
91
|
+
raise click.UsageError("--tag text must be 25 characters or fewer")
|
|
92
|
+
normalized_text = tag_text.casefold()
|
|
93
|
+
if normalized_text in seen_texts:
|
|
94
|
+
raise click.UsageError("--tag texts must be unique ignoring case")
|
|
95
|
+
seen_texts.add(normalized_text)
|
|
96
|
+
tags.append({"TagID": tag_id, "Tag": tag_text})
|
|
97
|
+
|
|
98
|
+
if len(tags) > 200:
|
|
99
|
+
raise click.UsageError("--tag accepts at most 200 campaign tags")
|
|
100
|
+
return tags
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _update_campaigns_tags_param(
|
|
104
|
+
campaign_id: int, tag_specs: tuple[str, ...], clear_tags: bool
|
|
105
|
+
) -> list[dict]:
|
|
106
|
+
"""Build the v4 Live UpdateCampaignsTags parameter."""
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
"CampaignID": campaign_id,
|
|
110
|
+
"Tags": _campaign_tag_param(tag_specs, clear_tags),
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _update_banners_tags_param(
|
|
116
|
+
banner_ids: str, tag_ids: Optional[str], clear_tags: bool
|
|
117
|
+
) -> list[dict]:
|
|
118
|
+
"""Build the v4 Live UpdateBannersTags parameter."""
|
|
119
|
+
parsed_banner_ids = _positive_ids_param(banner_ids, "--banner-ids")
|
|
120
|
+
if clear_tags:
|
|
121
|
+
if tag_ids is not None:
|
|
122
|
+
raise click.UsageError("Use either --tag-ids or --clear-tags, not both")
|
|
123
|
+
parsed_tag_ids: list[int] = []
|
|
124
|
+
else:
|
|
125
|
+
if tag_ids is None:
|
|
126
|
+
raise click.UsageError("--tag-ids is required unless --clear-tags is used")
|
|
127
|
+
parsed_tag_ids = _tag_ids_param(tag_ids)
|
|
128
|
+
return [
|
|
129
|
+
{"BannerID": banner_id, "TagIDS": parsed_tag_ids}
|
|
130
|
+
for banner_id in parsed_banner_ids
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _call_v4tags(ctx, method: str, param, output_format: str, output: str) -> None:
|
|
135
|
+
"""Call one v4 Live tag method and print formatted output."""
|
|
136
|
+
try:
|
|
137
|
+
client = create_v4_client(
|
|
138
|
+
token=ctx.obj.get("token"),
|
|
139
|
+
login=ctx.obj.get("login"),
|
|
140
|
+
profile=ctx.obj.get("profile"),
|
|
141
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
142
|
+
)
|
|
143
|
+
data = call_v4(client, method, param)
|
|
144
|
+
format_output(data, output_format, output)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
print_error(str(e))
|
|
147
|
+
raise click.Abort()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@click.group(epilog=V4_EPILOG)
|
|
151
|
+
def v4tags():
|
|
152
|
+
"""Yandex Direct v4 Live tag commands."""
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@v4_method_contract("GetCampaignsTags")
|
|
156
|
+
@v4tags.command(name="get-campaigns")
|
|
157
|
+
@click.option("--campaign-ids", required=True, help="Comma-separated campaign IDs")
|
|
158
|
+
@click.option(
|
|
159
|
+
"--format",
|
|
160
|
+
"output_format",
|
|
161
|
+
default="json",
|
|
162
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
163
|
+
help="Output format",
|
|
164
|
+
)
|
|
165
|
+
@click.option("--output", help="Output file")
|
|
166
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
167
|
+
@click.pass_context
|
|
168
|
+
def get_campaigns(ctx, campaign_ids, output_format, output, dry_run):
|
|
169
|
+
"""Get campaign tags."""
|
|
170
|
+
param = _get_campaigns_tags_param(campaign_ids)
|
|
171
|
+
if dry_run:
|
|
172
|
+
format_output(build_v4_body("GetCampaignsTags", param), "json", None)
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
_call_v4tags(ctx, "GetCampaignsTags", param, output_format, output)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@v4_method_contract("GetBannersTags")
|
|
179
|
+
@v4tags.command(name="get-banners")
|
|
180
|
+
@click.option("--campaign-ids", help="Comma-separated campaign IDs, up to 10")
|
|
181
|
+
@click.option("--banner-ids", help="Comma-separated banner IDs, up to 2000")
|
|
182
|
+
@click.option(
|
|
183
|
+
"--format",
|
|
184
|
+
"output_format",
|
|
185
|
+
default="json",
|
|
186
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
187
|
+
help="Output format",
|
|
188
|
+
)
|
|
189
|
+
@click.option("--output", help="Output file")
|
|
190
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
191
|
+
@click.pass_context
|
|
192
|
+
def get_banners(ctx, campaign_ids, banner_ids, output_format, output, dry_run):
|
|
193
|
+
"""Get banner tag IDs."""
|
|
194
|
+
param = _get_banners_tags_param(campaign_ids, banner_ids)
|
|
195
|
+
if dry_run:
|
|
196
|
+
format_output(build_v4_body("GetBannersTags", param), "json", None)
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
_call_v4tags(ctx, "GetBannersTags", param, output_format, output)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@v4_method_contract("UpdateCampaignsTags")
|
|
203
|
+
@v4tags.command(name="update-campaigns")
|
|
204
|
+
@click.option(
|
|
205
|
+
"--campaign-id",
|
|
206
|
+
required=True,
|
|
207
|
+
type=click.IntRange(min=1),
|
|
208
|
+
help="Campaign ID",
|
|
209
|
+
)
|
|
210
|
+
@click.option(
|
|
211
|
+
"--tag",
|
|
212
|
+
"tag_specs",
|
|
213
|
+
multiple=True,
|
|
214
|
+
help="Campaign tag as TAG_ID=TEXT; use 0 for a new tag",
|
|
215
|
+
)
|
|
216
|
+
@click.option("--clear-tags", is_flag=True, help="Remove all campaign tags")
|
|
217
|
+
@click.option(
|
|
218
|
+
"--format",
|
|
219
|
+
"output_format",
|
|
220
|
+
default="json",
|
|
221
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
222
|
+
help="Output format",
|
|
223
|
+
)
|
|
224
|
+
@click.option("--output", help="Output file")
|
|
225
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
226
|
+
@click.pass_context
|
|
227
|
+
def update_campaigns(
|
|
228
|
+
ctx, campaign_id, tag_specs, clear_tags, output_format, output, dry_run
|
|
229
|
+
):
|
|
230
|
+
"""Replace the campaign tag list."""
|
|
231
|
+
param = _update_campaigns_tags_param(campaign_id, tag_specs, clear_tags)
|
|
232
|
+
if dry_run:
|
|
233
|
+
format_output(build_v4_body("UpdateCampaignsTags", param), "json", None)
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
_call_v4tags(ctx, "UpdateCampaignsTags", param, output_format, output)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@v4_method_contract("UpdateBannersTags")
|
|
240
|
+
@v4tags.command(name="update-banners")
|
|
241
|
+
@click.option("--banner-ids", required=True, help="Comma-separated banner IDs")
|
|
242
|
+
@click.option("--tag-ids", help="Comma-separated campaign tag IDs, up to 30")
|
|
243
|
+
@click.option("--clear-tags", is_flag=True, help="Remove all banner tags")
|
|
244
|
+
@click.option(
|
|
245
|
+
"--format",
|
|
246
|
+
"output_format",
|
|
247
|
+
default="json",
|
|
248
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
249
|
+
help="Output format",
|
|
250
|
+
)
|
|
251
|
+
@click.option("--output", help="Output file")
|
|
252
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
253
|
+
@click.pass_context
|
|
254
|
+
def update_banners(
|
|
255
|
+
ctx, banner_ids, tag_ids, clear_tags, output_format, output, dry_run
|
|
256
|
+
):
|
|
257
|
+
"""Replace banner tag assignments."""
|
|
258
|
+
param = _update_banners_tags_param(banner_ids, tag_ids, clear_tags)
|
|
259
|
+
if dry_run:
|
|
260
|
+
format_output(build_v4_body("UpdateBannersTags", param), "json", None)
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
_call_v4tags(ctx, "UpdateBannersTags", param, output_format, output)
|
|
@@ -58,11 +58,15 @@ SMOKE_MATRIX = {
|
|
|
58
58
|
"strategies.get",
|
|
59
59
|
"turbopages.get",
|
|
60
60
|
"v4events.get-events-log",
|
|
61
|
+
"v4forecast.get",
|
|
62
|
+
"v4forecast.list",
|
|
61
63
|
"v4finance.check-payment",
|
|
62
64
|
"v4finance.get-clients-units",
|
|
63
65
|
"v4finance.get-credit-limits",
|
|
64
66
|
"v4goals.get-retargeting-goals",
|
|
65
67
|
"v4goals.get-stat-goals",
|
|
68
|
+
"v4tags.get-banners",
|
|
69
|
+
"v4tags.get-campaigns",
|
|
66
70
|
"v4wordstat.get-report",
|
|
67
71
|
"v4wordstat.list-reports",
|
|
68
72
|
"vcards.get",
|
|
@@ -143,6 +147,10 @@ SMOKE_MATRIX = {
|
|
|
143
147
|
"strategies.update",
|
|
144
148
|
"v4account.account-management",
|
|
145
149
|
"v4account.enable-shared-account",
|
|
150
|
+
"v4forecast.create",
|
|
151
|
+
"v4forecast.delete",
|
|
152
|
+
"v4tags.update-banners",
|
|
153
|
+
"v4tags.update-campaigns",
|
|
146
154
|
"v4wordstat.create-report",
|
|
147
155
|
"v4wordstat.delete-report",
|
|
148
156
|
"vcards.add",
|