direct-cli 0.3.7__tar.gz → 0.3.9__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.7 → direct_cli-0.3.9}/.github/workflows/claude.yml +1 -1
- direct_cli-0.3.9/CHANGELOG.md +110 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/CLAUDE.md +22 -1
- {direct_cli-0.3.7 → direct_cli-0.3.9}/PKG-INFO +156 -15
- {direct_cli-0.3.7 → direct_cli-0.3.9}/README.md +155 -14
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/cli.py +14 -1
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/adgroups.py +53 -9
- direct_cli-0.3.9/direct_cli/commands/ads.py +798 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/bidmodifiers.py +93 -45
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/bids.py +5 -4
- direct_cli-0.3.9/direct_cli/commands/campaigns.py +1061 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/dynamicads.py +4 -4
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/feeds.py +11 -1
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/keywordbids.py +6 -0
- direct_cli-0.3.9/direct_cli/commands/keywords.py +626 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/strategies.py +198 -24
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/output.py +72 -2
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/utils.py +45 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/wsdl_coverage.py +31 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli.egg-info/PKG-INFO +156 -15
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli.egg-info/SOURCES.txt +28 -19
- direct_cli-0.3.9/docs/audits/issue-198-mutating-wsdl-audit.md +148 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/pyproject.toml +1 -1
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/build_api_coverage_report.py +85 -34
- direct_cli-0.3.9/scripts/sandbox_write_audit.py +198 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/sandbox_write_live.py +161 -21
- direct_cli-0.3.9/scripts/test_sandbox_write.sh +44 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/API_COVERAGE.md +1 -1
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/API_ISSUE_AUDIT.md +1 -1
- direct_cli-0.3.9/tests/_orphan_store.py +124 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/api_coverage_payloads.py +536 -31
- direct_cli-0.3.9/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +437 -0
- direct_cli-0.3.9/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +437 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +15 -15
- direct_cli-0.3.9/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +166 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +16 -16
- direct_cli-0.3.9/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +69 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/conftest.py +47 -9
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_api_coverage.py +382 -3
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_cli.py +85 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_dry_run.py +1807 -290
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_integration.py +117 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_integration_write.py +291 -17
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_low_coverage_payloads.py +340 -19
- direct_cli-0.3.9/tests/test_sandbox_write_audit.py +67 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_smoke_matrix.py +202 -0
- direct_cli-0.3.9/tests/test_v4_live_contracts.py +341 -0
- direct_cli-0.3.7/tests/test_integration_live_write.py → direct_cli-0.3.9/tests/test_v5_live_write.py +27 -28
- direct_cli-0.3.9/tests/test_wsdl_parity_gate.py +569 -0
- direct_cli-0.3.7/CHANGELOG.md +0 -11
- direct_cli-0.3.7/direct_cli/commands/ads.py +0 -447
- direct_cli-0.3.7/direct_cli/commands/campaigns.py +0 -558
- direct_cli-0.3.7/direct_cli/commands/keywords.py +0 -322
- direct_cli-0.3.7/scripts/test_sandbox_write.sh +0 -23
- direct_cli-0.3.7/tests/test_v4_live_contracts.py +0 -190
- {direct_cli-0.3.7 → direct_cli-0.3.9}/.env.example +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/.github/copilot-instructions.md +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/.github/workflows/api-coverage.yml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/.github/workflows/quality.yml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/.gitignore +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/AGENTS.md +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/MANIFEST.in +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/__init__.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_deprecated.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_smoke_probes.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/__init__.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/api.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/auth.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/adextensions.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/adimages.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/advideos.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/agencyclients.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/audiencetargets.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/auth.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/balance.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/businesses.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/changes.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/clients.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/creatives.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/dictionaries.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/reports.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/retargeting.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/sitelinks.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/smartadtargets.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/turbopages.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/v4account.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/v4events.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/v4finance.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/v4forecast.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/v4goals.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/v4shells.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/v4tags.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/v4wordstat.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/commands/vcards.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/reports_coverage.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/smoke_matrix.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/v4/__init__.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/v4/money.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli/v4_contracts.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli.egg-info/requires.txt +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/anonymize_cassettes.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/build_api_coverage_checklist.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/check_reports_drift.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/check_wsdl_drift.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/patch_vendor_imports.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/refresh_reports_cache.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/refresh_wsdl_cache.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/release_pypi.sh +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/test_dangerous_commands.sh +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/test_safe_commands.sh +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/scripts/update_vendor.sh +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/setup.cfg +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/setup.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/MANUAL_COVERAGE.md +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/__init__.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_adgroups_add_update_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_adimages_add_get_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_ads_add_update_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_ads_suspend_resume_archive_unarchive.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_advideos_add_get.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_add_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_suspend_resume.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_bids_set.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_campaign_create_get_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_creatives_chain_advideo_to_creative.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_add_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_suspend_resume.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_keywordbids_set.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_keywords_add_update_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_keywords_suspend_resume.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_sitelinks_add_get_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_add_update_delete.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
- /direct_cli-0.3.7/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_suspend_resume.yaml → /direct_cli-0.3.9/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/fixtures/test-video.mp4 +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/reports_cache/raw/fields-list.html +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/reports_cache/raw/headers.html +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/reports_cache/raw/period.html +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/reports_cache/raw/spec.html +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/reports_cache/raw/type.html +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/reports_cache/spec.json +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_auth_oauth.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_auth_op.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_balance.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_cli_contract.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_comprehensive.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_reports_drift.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_reports_parsing.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_transport_contract.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4_contracts.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4_foundation.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4_safety.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4account.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4events.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4finance_money.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4finance_read.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4forecast.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4goals.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4tags.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_v4wordstat.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/test_vendor_imports.py +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/adextensions.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/adgroups.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/adimages.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/ads.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/advideos.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/agencyclients.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/audiencetargets.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/bidmodifiers.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/bids.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/businesses.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/campaigns.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/changes.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/clients.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/creatives.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/dictionaries.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/feeds.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/imports/general.xsd +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/keywordbids.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/keywords.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/keywordsresearch.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/leads.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/retargetinglists.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/sitelinks.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/smartadtargets.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/strategies.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/tests/wsdl_cache/turbopages.xml +0 -0
- {direct_cli-0.3.7 → direct_cli-0.3.9}/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,110 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.9
|
|
4
|
+
|
|
5
|
+
**Added:**
|
|
6
|
+
|
|
7
|
+
- `direct keywords add` now supports batch mode via `--from-file PATH`
|
|
8
|
+
(JSONL, one keyword object per line) or `--keywords-json '[…]'`
|
|
9
|
+
(inline JSON array). The CLI splits input into chunks of 10 — the
|
|
10
|
+
Yandex Direct API limit for `keywords.add` documented at
|
|
11
|
+
https://yandex.ru/dev/direct/doc/dg/objects/keyword.html — preserves
|
|
12
|
+
input order, and merges `AddResults` from every chunk into a single
|
|
13
|
+
response. Item-level errors do not abort the batch. If a chunk-level
|
|
14
|
+
exception breaks the loop, already-created Ids are printed to stderr
|
|
15
|
+
with a "Partial success before failure" header so a retry doesn't
|
|
16
|
+
duplicate them. Pre-flight warning when any AdGroupId in the input
|
|
17
|
+
exceeds the per-ad-group limit of 200 keywords (the API rejects the
|
|
18
|
+
excess with per-item errors; warning surfaces this before any chunk
|
|
19
|
+
is sent). Row keys use WSDL CamelCase (`Keyword`, `AdGroupId`,
|
|
20
|
+
`Bid`, `ContextBid`, `UserParam1`, `UserParam2`); unknown keys are
|
|
21
|
+
rejected with the row number, and JSON booleans are explicitly
|
|
22
|
+
rejected to prevent silent `True → 1` coercion. `--adgroup-id` is
|
|
23
|
+
optional in batch mode and acts as a default, overridable per row.
|
|
24
|
+
`--dry-run` prints the first chunk's payload alongside
|
|
25
|
+
`{chunks, totalItems, chunkSize}`. Single-item mode (`--keyword`)
|
|
26
|
+
is unchanged (#203).
|
|
27
|
+
- `direct campaigns add` typed flags for CPA strategies and
|
|
28
|
+
cross-cutting `CampaignAddItem` fields: `--goal-id` (single
|
|
29
|
+
Metrika goal), `--crr` (CRR percentage for
|
|
30
|
+
`PAY_FOR_CONVERSION_CRR`),
|
|
31
|
+
`--priority-goals goal_id:value,…` (multi-goal CPA via
|
|
32
|
+
WSDL `PriorityGoalsArray`), `--average-cpa MICRO_RUBLES`,
|
|
33
|
+
`--bid-ceiling MICRO_RUBLES`, `--counter-ids`
|
|
34
|
+
(TextCampaign/DynamicTextCampaign), `--notification JSON`
|
|
35
|
+
(`CampaignBase.Notification` with `SmsSettings`/`EmailSettings`
|
|
36
|
+
shape validation), `--time-targeting JSON`
|
|
37
|
+
(`CampaignAddItem.TimeTargeting` with `HolidaysSchedule`
|
|
38
|
+
shape validation). Strategy-subtype compatibility is enforced
|
|
39
|
+
via `UsageError` at CLI level both ways: WSDL-incompatible flags
|
|
40
|
+
are rejected (e.g. `--average-cpa` for `HIGHEST_POSITION`,
|
|
41
|
+
`--crr` outside `PAY_FOR_CONVERSION_CRR`,
|
|
42
|
+
`--bid-ceiling` for `PayForConversionCrr` /
|
|
43
|
+
`PayForConversionMultipleGoals`), and WSDL `minOccurs=1`
|
|
44
|
+
fields are demanded up-front (e.g. picking `AVERAGE_CPA`
|
|
45
|
+
without `--average-cpa`+`--goal-id`, or `PAY_FOR_CONVERSION_CRR`
|
|
46
|
+
without `--crr`+`--goal-id`, or `*_MULTIPLE_GOALS` without
|
|
47
|
+
`--priority-goals`, all fail at the CLI instead of the API).
|
|
48
|
+
Closes #204.
|
|
49
|
+
|
|
50
|
+
**Notes:**
|
|
51
|
+
|
|
52
|
+
- Issue #204 also requested `--goals` (array) and
|
|
53
|
+
`--network-settings`; both were dropped after WSDL audit. Yandex
|
|
54
|
+
`Strategy*Add` complex types declare only scalar `GoalId`, so
|
|
55
|
+
multi-goal CPA is shipped through `--priority-goals` instead
|
|
56
|
+
(correct WSDL path: `TextCampaign.PriorityGoals.Items[].GoalId/Value`).
|
|
57
|
+
No `NetworkSettings` field exists on `CampaignAddItem` /
|
|
58
|
+
`TextCampaignAddItem` / `DynamicTextCampaignAddItem` /
|
|
59
|
+
`SmartCampaignAddItem` in the current `campaigns.xml` WSDL.
|
|
60
|
+
|
|
61
|
+
**Fixed:**
|
|
62
|
+
|
|
63
|
+
- Refreshed `TestWriteFeeds` and `TestWriteSmartAdTargets` VCR cassettes against a real sandbox, dropped the `_FEED_REGRESSION_PATTERNS` skip workaround, and updated `sandbox_feed` / `sandbox_smart_adgroup` fixtures to pass the now-WSDL-required `--business-type RETAIL` (FeedAddItem) and `--counter-id` (SmartCampaignAddItem). Tests now skip only on genuine sandbox limitations, not on the missing-option proxy that the workaround papered over (#206, fallout from #201). Test invocation now also passes `--login` and prefers env vars over an active `direct auth` profile, matching the inversion documented in CLAUDE.md.
|
|
64
|
+
- WSDL parity gate now fails fast when `COMMAND_WSDL_MAP` points at a container that does not exist in the WSDL request schema. The previous skip-on-empty-required-list silently masked typo'd container names (#206, Copilot follow-up from #205).
|
|
65
|
+
- `WSDL_FIELD_TO_CLI_OPTION` no longer references the non-existent generic `--file` flag. `SourceType` maps to `{--url}` and `ImageData` maps to `{--image-data, --image-file}`, matching the real CLI surface (#206, Copilot follow-up from #205).
|
|
66
|
+
- `direct bidmodifiers set --help` no longer advertises the rejected `--campaign-id`/`--type` legacy path; the rejection now happens via an eager Click callback (same pattern as deprecated `keywords update` options), preserving the existing `UsageError` message for regression coverage (#206, Copilot follow-up from #214).
|
|
67
|
+
|
|
68
|
+
**Refs:** Closes issues #122, #138, #198, #202, #203, #204, #206, #207.
|
|
69
|
+
|
|
70
|
+
## 0.3.8
|
|
71
|
+
|
|
72
|
+
**BREAKING CHANGES:**
|
|
73
|
+
|
|
74
|
+
- `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).
|
|
75
|
+
- `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).
|
|
76
|
+
- `direct ads add --type MOBILE_APP_AD --href` rejected — MobileAppAd uses `--tracking-url`, not `--href` (PR #196).
|
|
77
|
+
- `direct feeds add` now requires `--business-type {RETAIL,HOTELS,REALTY,AUTOMOBILES,FLIGHTS,OTHER}`. Mirrors WSDL FeedAddItem.BusinessType (minOccurs=1) (PR #201).
|
|
78
|
+
|
|
79
|
+
**Schema gate — mutating ops parity:**
|
|
80
|
+
|
|
81
|
+
- 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).
|
|
82
|
+
- 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).
|
|
83
|
+
- Added MOBILE_APP_AD branch to `ads add` mirroring WSDL `MobileAppAdAdd` (PR #190).
|
|
84
|
+
- `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.
|
|
85
|
+
|
|
86
|
+
**Strict WSDL parity policy:**
|
|
87
|
+
|
|
88
|
+
- 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).
|
|
89
|
+
|
|
90
|
+
**Integration test coverage:**
|
|
91
|
+
|
|
92
|
+
- Added read-only sandbox integration tests for `changes`, `keywordsresearch`, `balance` (PR #186).
|
|
93
|
+
- Added v5 write integration coverage for `strategies` lifecycle, `retargeting update`, `bids get/set-auto`, plus `auth status/list` read-only tests (PR #189).
|
|
94
|
+
- Re-recorded TestWriteBidsRead cassettes against live API and rewrote host to sandbox so the bids endpoints get real coverage in replay mode (PR #193).
|
|
95
|
+
|
|
96
|
+
**CI infrastructure:**
|
|
97
|
+
|
|
98
|
+
- Switched Claude code-review GitHub Action from default (Sonnet 4.5) to Claude Opus 4.7 for deeper PR review (PR #192).
|
|
99
|
+
|
|
100
|
+
**Refs:** Closes issues #118, #136, #137, #175, #176, #180, #183, #191, #199.
|
|
101
|
+
|
|
102
|
+
## 0.3.3
|
|
103
|
+
|
|
104
|
+
**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.
|
|
105
|
+
|
|
106
|
+
- Added refresh token persistence for OAuth profiles.
|
|
107
|
+
- Added automatic OAuth access token refresh before expiry.
|
|
108
|
+
- Added `expires_in` details to `direct auth status`.
|
|
109
|
+
- Added JSON output for `direct auth status`.
|
|
110
|
+
- 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,24 @@ 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
|
+
|
|
55
|
+
**WSDL parity gate:** `tests/test_wsdl_parity_gate.py` runs four invariant checks across every `add`/`update`/`set` command in `WRITE_SANDBOX`:
|
|
56
|
+
|
|
57
|
+
1. *Empty subtype no-op* — a mutating command with only the resource ID must refuse to send the payload (no silent no-op on the live API).
|
|
58
|
+
2. *Silent data loss* — a typed flag that does not belong to the chosen `--type` must raise `UsageError`, not be dropped.
|
|
59
|
+
3. *WSDL `minOccurs=1` not validated* — every required WSDL item field must be enforced either via Click `required=True` *or* a documented `UsageError` body check (listed in `INTERNAL_VALIDATION`).
|
|
60
|
+
4. *Strategy enum drift* — `STRATEGY_TYPES` (`direct_cli/commands/strategies.py`) must equal the subtype-of-one field names in `StrategyAddItem`.
|
|
61
|
+
|
|
62
|
+
Adding a new mutating command requires extending `COMMAND_WSDL_MAP` in `tests/test_wsdl_parity_gate.py` (the coverage test fails otherwise) and, if the WSDL request has a non-mechanical field name, also `WSDL_FIELD_TO_CLI_OPTION`. Tracked in issue #198.
|
|
63
|
+
|
|
44
64
|
**SelectionCriteria:** Resources like `adgroups`, `ads`, `keywords` require at least one of `Ids`, `CampaignIds`, or `AdGroupIds` — otherwise API error 4001.
|
|
45
65
|
|
|
46
66
|
**Error handling:** All commands wrap API calls in `try/except Exception` → `print_error(str(e))` + `raise click.Abort()`.
|
|
@@ -55,6 +75,7 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
|
|
|
55
75
|
|
|
56
76
|
- **Unit** (`test_cli.py`, `test_comprehensive.py`) — no API calls, no token needed.
|
|
57
77
|
- **Integration** (`test_integration.py`, `@pytest.mark.integration`) — require `.env` with `YANDEX_DIRECT_TOKEN` and `YANDEX_DIRECT_LOGIN`. Auto-skip if absent.
|
|
78
|
+
- **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
79
|
|
|
59
80
|
## Dangerous Commands — Never Auto-Test
|
|
60
81
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: direct-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
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`.
|
|
@@ -382,6 +384,15 @@ direct campaigns add --name "My Campaign" --start-date 2024-02-01 --type TEXT_CA
|
|
|
382
384
|
direct campaigns add --name "Dynamic Campaign" --start-date 2024-02-01 --type DYNAMIC_TEXT_CAMPAIGN --setting ADD_METRICA_TAG=NO --search-strategy HIGHEST_POSITION --network-strategy SERVING_OFF --dry-run
|
|
383
385
|
direct campaigns add --name "Smart Campaign" --start-date 2024-02-01 --type SMART_CAMPAIGN --network-strategy AVERAGE_CPC_PER_FILTER --filter-average-cpc 1000000 --counter-id 123 --dry-run
|
|
384
386
|
|
|
387
|
+
# CPA strategy (single goal): --goal-id required, --average-cpa / --bid-ceiling are micro-rubles
|
|
388
|
+
direct campaigns add --name "CPA Campaign" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy AVERAGE_CPA --network-strategy SERVING_OFF --goal-id 1234567 --average-cpa 500000000 --bid-ceiling 1000000000 --counter-ids 111,222 --dry-run
|
|
389
|
+
|
|
390
|
+
# Multi-goal CPA via PriorityGoals (goal_id:value pairs, WSDL PriorityGoalsItem)
|
|
391
|
+
direct campaigns add --name "Multi-Goal CPA" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy AVERAGE_CPA_MULTIPLE_GOALS --network-strategy SERVING_OFF --priority-goals 1234567:80,9876543:20 --bid-ceiling 1000000000 --dry-run
|
|
392
|
+
|
|
393
|
+
# Notification (Sms/Email) and TimeTargeting accept JSON with WSDL CamelCase keys
|
|
394
|
+
direct campaigns add --name "Notify+Schedule" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy HIGHEST_POSITION --network-strategy SERVING_OFF --notification '{"EmailSettings":{"Email":"ops@example.com","SendWarnings":"YES"}}' --time-targeting '{"Schedule":["1A0123456789ABCDEFGHIJKL"],"ConsiderWorkingWeekends":"YES"}' --dry-run
|
|
395
|
+
|
|
385
396
|
# Update / lifecycle
|
|
386
397
|
direct campaigns update --id 12345 --name "New Name" --status SUSPENDED --budget 100000000 --start-date 2024-02-10 --end-date 2024-03-01
|
|
387
398
|
direct campaigns suspend --id 12345
|
|
@@ -408,11 +419,21 @@ direct adgroups delete --id 67890
|
|
|
408
419
|
direct ads get --campaign-ids 1,2,3
|
|
409
420
|
direct ads get --adgroup-ids 45678 --format table
|
|
410
421
|
direct ads add --adgroup-id 12345 --type TEXT_AD --title "Title" --text "Ad text" --href "https://example.com" --dry-run
|
|
411
|
-
direct ads add --adgroup-id 12345 --type
|
|
412
|
-
direct ads
|
|
422
|
+
direct ads add --adgroup-id 12345 --type TEXT_AD --title "Title" --text "Ad text" --href "https://example.com" --title2 "Second headline" --display-url-path "deals" --mobile YES --vcard-id 111 --sitelink-set-id 222 --turbo-page-id 333 --ad-extensions "444,555" --dry-run
|
|
423
|
+
direct ads add --adgroup-id 12345 --type TEXT_IMAGE_AD --image-hash abcdefghijklmnopqrst --href "https://example.com" --turbo-page-id 555 --dry-run
|
|
424
|
+
direct ads update --id 99999 --type TEXT_AD --title "New Title" --text "New text" --href "https://example.com"
|
|
425
|
+
direct ads update --id 99999 --type TEXT_AD --image-hash abcdefghijklmnopqrst
|
|
426
|
+
direct ads update --id 99999 --type TEXT_AD --title2 "New second headline" --vcard-id 222
|
|
413
427
|
direct ads delete --id 99999
|
|
414
428
|
```
|
|
415
429
|
|
|
430
|
+
Available TEXT_AD typed flags for `ads add` / `ads update`: `--title`, `--text`,
|
|
431
|
+
`--href`, `--image-hash`, `--title2`, `--display-url-path`, `--vcard-id`,
|
|
432
|
+
`--sitelink-set-id`, `--turbo-page-id`. `--mobile` (default `NO`) and
|
|
433
|
+
`--ad-extensions` are `ads add`-only — `TextAdUpdate` does not contain `Mobile`,
|
|
434
|
+
and ad-extension updates go through the `CalloutSetting` WSDL field, which is
|
|
435
|
+
not yet exposed by the CLI. TEXT_IMAGE_AD additionally accepts `--turbo-page-id`.
|
|
436
|
+
|
|
416
437
|
#### Keywords
|
|
417
438
|
|
|
418
439
|
```bash
|
|
@@ -422,6 +443,33 @@ direct keywords update --id 88888 --keyword "updated keyword text"
|
|
|
422
443
|
direct keywords delete --id 88888
|
|
423
444
|
```
|
|
424
445
|
|
|
446
|
+
**Batch keyword upload** (CLI auto-chunks to the API limit of 10 per request):
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
# From a JSONL file (one keyword object per line)
|
|
450
|
+
direct keywords add --adgroup-id 12345 --from-file keywords.jsonl
|
|
451
|
+
|
|
452
|
+
# Inline JSON array
|
|
453
|
+
direct keywords add --keywords-json '[{"Keyword":"buy laptop","Bid":10000000},{"Keyword":"buy desktop"}]'
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Example `keywords.jsonl`:
|
|
457
|
+
|
|
458
|
+
```jsonl
|
|
459
|
+
{"Keyword":"buy laptop","Bid":10000000,"UserParam1":"src=ad1"}
|
|
460
|
+
{"Keyword":"buy desktop","ContextBid":5000000}
|
|
461
|
+
{"Keyword":"купить ноутбук","AdGroupId":99999}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
- Row keys use WSDL CamelCase: `Keyword`, `AdGroupId`, `Bid`, `ContextBid`, `UserParam1`, `UserParam2`.
|
|
465
|
+
- `--adgroup-id` provides the default group ID; rows can override it via per-row `AdGroupId`.
|
|
466
|
+
- Each effective row must resolve `Keyword` and `AdGroupId`; unknown fields are rejected with the row number.
|
|
467
|
+
- API limit: 10 items per `keywords.add` request — see [Yandex Direct docs](https://yandex.ru/dev/direct/doc/dg/objects/keyword.html). The CLI sends as many chunks as needed and merges `AddResults`.
|
|
468
|
+
- API limit: 200 keywords per ad group. The CLI prints a warning if any `AdGroupId` in the input exceeds it; the API rejects the excess as per-item errors.
|
|
469
|
+
- Item-level errors from the API do not abort the batch; the merged output includes successes and per-item errors.
|
|
470
|
+
- If a chunk fails with a network-level error mid-batch, already-created Ids are printed to stderr (`Partial success before failure`) so a retry doesn't duplicate them.
|
|
471
|
+
- `--dry-run` shows the first chunk's payload plus `{chunks, totalItems, chunkSize}`.
|
|
472
|
+
|
|
425
473
|
#### Reports
|
|
426
474
|
|
|
427
475
|
```bash
|
|
@@ -478,8 +526,8 @@ direct dynamicads set-bids --id 789 --bid 12500000 --context-bid 9000000 --prior
|
|
|
478
526
|
|
|
479
527
|
# Shared bidding strategies
|
|
480
528
|
direct strategies get --limit 5
|
|
481
|
-
direct strategies add --name "Shared Clicks" --type WbMaximumClicks --spend-limit 1000000000 --
|
|
482
|
-
direct strategies update --id 42 --type WbMaximumClicks --
|
|
529
|
+
direct strategies add --name "Shared Clicks" --type WbMaximumClicks --weekly-spend-limit 1000000000 --bid-ceiling 30000000 --dry-run
|
|
530
|
+
direct strategies update --id 42 --type WbMaximumClicks --weekly-spend-limit 35000000 --dry-run
|
|
483
531
|
direct strategies archive --id 42 --dry-run
|
|
484
532
|
|
|
485
533
|
# Dynamic feed ad targets
|
|
@@ -493,7 +541,7 @@ direct vcards add --campaign-id 555 --country "Russia" --city "Moscow" --company
|
|
|
493
541
|
direct adextensions add --callout-text "Free shipping" --dry-run
|
|
494
542
|
direct adimages add --name banner.png --image-data BASE64DATA --type ICON --dry-run
|
|
495
543
|
direct creatives add --video-id video-id --dry-run
|
|
496
|
-
direct feeds add --name "Feed A" --url "https://example.com/feed.xml" --dry-run
|
|
544
|
+
direct feeds add --name "Feed A" --url "https://example.com/feed.xml" --business-type RETAIL --dry-run
|
|
497
545
|
direct feeds update --id 18 --name "Feed A v2" --url "https://example.com/feed-v2.xml" --dry-run
|
|
498
546
|
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
|
|
499
547
|
direct --login CLIENT_LOGIN clients update --phone +70000000000 --notification-email user@example.com --dry-run
|
|
@@ -556,6 +604,17 @@ Use `--dry-run` on `add` / `update` commands to preview the API request before s
|
|
|
556
604
|
direct campaigns add --name "Test" --start-date 2024-01-01 --dry-run
|
|
557
605
|
```
|
|
558
606
|
|
|
607
|
+
### API Errors
|
|
608
|
+
|
|
609
|
+
Yandex Direct can return a successful HTTP response that still contains
|
|
610
|
+
item-level `Errors` for one object. Direct CLI treats those responses as
|
|
611
|
+
failed operations: it exits non-zero and prints the error code, message, and
|
|
612
|
+
details.
|
|
613
|
+
|
|
614
|
+
Code `8800` with `Object not found` usually means the object is not available
|
|
615
|
+
under the current `Client-Login` or account. Check the selected `--login`,
|
|
616
|
+
`YANDEX_DIRECT_LOGIN`, or auth profile before retrying.
|
|
617
|
+
|
|
559
618
|
### Testing
|
|
560
619
|
|
|
561
620
|
Four tiers of tests live under `tests/`:
|
|
@@ -565,17 +624,22 @@ Four tiers of tests live under `tests/`:
|
|
|
565
624
|
| Unit / CLI wiring / dry-run | *(none)* | No | No |
|
|
566
625
|
| Read-only integration | `-m integration` | Yes (production API, read-only) | Yes |
|
|
567
626
|
| Write integration | `-m integration_write` | No (replays VCR cassettes) | No |
|
|
568
|
-
| Live draft write integration | `-m integration_live_write` | Yes when recording, otherwise VCR replay | Yes + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
627
|
+
| Live draft write integration (v5) | `-m integration_live_write` | Yes when recording, otherwise VCR replay | Yes + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
628
|
+
| v4 live read | `-m v4_live_read` | Yes (production v4 JSON API, read-only) | Yes |
|
|
629
|
+
| 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` |
|
|
569
630
|
|
|
570
631
|
```bash
|
|
571
632
|
pip install -e ".[dev]"
|
|
572
633
|
pytest # fast tier — no token
|
|
573
634
|
pytest -m integration -v # read-only integration tests (needs token)
|
|
574
635
|
pytest -m integration_write -v # write cassette replay (no token needed)
|
|
575
|
-
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # live draft cassette replay
|
|
636
|
+
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # live draft cassette replay (v5)
|
|
576
637
|
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rewrite # re-record live draft cassette
|
|
638
|
+
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
|
|
577
639
|
```
|
|
578
640
|
|
|
641
|
+
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`).
|
|
642
|
+
|
|
579
643
|
#### Smoke command scripts
|
|
580
644
|
|
|
581
645
|
Every CLI subcommand is classified in `direct_cli/smoke_matrix.py`.
|
|
@@ -659,6 +723,12 @@ For `v4account` sandbox smoke, `enable-shared-account` uses
|
|
|
659
723
|
`account-management` requires `YANDEX_DIRECT_V4ACCOUNT_ACCOUNT_ID`; without it
|
|
660
724
|
the runner reports `NOT_COVERED` for that command.
|
|
661
725
|
|
|
726
|
+
`clients.update` is opt-in because it mutates client-level account metadata.
|
|
727
|
+
Set `YANDEX_DIRECT_CLIENTS_UPDATE_LOGIN` to an expendable sandbox
|
|
728
|
+
`Client-Login`; the runner passes it through `--login` and updates only
|
|
729
|
+
`ClientInfo` with a unique smoke marker. Without that variable, the runner
|
|
730
|
+
reports `NOT_COVERED` for `clients.update`.
|
|
731
|
+
|
|
662
732
|
#### Re-recording write cassettes
|
|
663
733
|
|
|
664
734
|
The `integration_write` pytest tier still replays stored write-test traffic
|
|
@@ -813,6 +883,8 @@ base `YANDEX_DIRECT_LOGIN`; это защищает от смешивания т
|
|
|
813
883
|
логином из project `.env`. Для нескольких аккаунтов используйте OAuth profiles
|
|
814
884
|
или профильные env vars, а не базовые credentials.
|
|
815
885
|
|
|
886
|
+
> **В тестах порядок инвертирован.** Live-API тесты (например `tests/test_v4_live_contracts.py`) сначала читают `YANDEX_DIRECT_TOKEN` / `YANDEX_DIRECT_LOGIN` из окружения, затем падают на активный профиль `direct auth`, и скипают тест если ни того ни другого нет. Это сделано специально: на машине разработчика с активным профилем обычный `pytest` не должен молча идти в боевой API. Контракт зафиксирован в `CLAUDE.md`.
|
|
887
|
+
|
|
816
888
|
Установка остаётся через `pip install direct-cli`, а запуск команд теперь идет
|
|
817
889
|
через `direct`. Вызов deprecated entrypoint `direct-cli` завершается ошибкой с
|
|
818
890
|
подсказкой `use direct instead of direct-cli`.
|
|
@@ -1002,6 +1074,15 @@ direct campaigns add --name "Моя кампания" --start-date 2024-02-01 --
|
|
|
1002
1074
|
direct campaigns add --name "Динамическая кампания" --start-date 2024-02-01 --type DYNAMIC_TEXT_CAMPAIGN --setting ADD_METRICA_TAG=NO --search-strategy HIGHEST_POSITION --network-strategy SERVING_OFF --dry-run
|
|
1003
1075
|
direct campaigns add --name "Смарт-кампания" --start-date 2024-02-01 --type SMART_CAMPAIGN --network-strategy AVERAGE_CPC_PER_FILTER --filter-average-cpc 1000000 --counter-id 123 --dry-run
|
|
1004
1076
|
|
|
1077
|
+
# CPA-стратегия (одна цель): --goal-id обязателен, --average-cpa/--bid-ceiling — micro-рубли
|
|
1078
|
+
direct campaigns add --name "CPA-кампания" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy AVERAGE_CPA --network-strategy SERVING_OFF --goal-id 1234567 --average-cpa 500000000 --bid-ceiling 1000000000 --counter-ids 111,222 --dry-run
|
|
1079
|
+
|
|
1080
|
+
# Мульти-целевой CPA через PriorityGoals (пары goal_id:value, WSDL PriorityGoalsItem)
|
|
1081
|
+
direct campaigns add --name "Мульти-целевой CPA" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy AVERAGE_CPA_MULTIPLE_GOALS --network-strategy SERVING_OFF --priority-goals 1234567:80,9876543:20 --bid-ceiling 1000000000 --dry-run
|
|
1082
|
+
|
|
1083
|
+
# Notification (Sms/Email) и TimeTargeting принимают JSON с CamelCase ключами WSDL
|
|
1084
|
+
direct campaigns add --name "Уведомления+Расписание" --start-date 2026-06-01 --type TEXT_CAMPAIGN --search-strategy HIGHEST_POSITION --network-strategy SERVING_OFF --notification '{"EmailSettings":{"Email":"ops@example.com","SendWarnings":"YES"}}' --time-targeting '{"Schedule":["1A0123456789ABCDEFGHIJKL"],"ConsiderWorkingWeekends":"YES"}' --dry-run
|
|
1085
|
+
|
|
1005
1086
|
# Обновление и управление статусом
|
|
1006
1087
|
direct campaigns update --id 12345 --name "Новое название" --status SUSPENDED --budget 100000000 --start-date 2024-02-10 --end-date 2024-03-01
|
|
1007
1088
|
direct campaigns suspend --id 12345
|
|
@@ -1028,11 +1109,22 @@ direct adgroups delete --id 67890
|
|
|
1028
1109
|
direct ads get --campaign-ids 1,2,3
|
|
1029
1110
|
direct ads get --adgroup-ids 45678 --format table
|
|
1030
1111
|
direct ads add --adgroup-id 12345 --type TEXT_AD --title "Заголовок" --text "Текст объявления" --href "https://example.com" --dry-run
|
|
1031
|
-
direct ads add --adgroup-id 12345 --type
|
|
1032
|
-
direct ads
|
|
1112
|
+
direct ads add --adgroup-id 12345 --type TEXT_AD --title "Заголовок" --text "Текст" --href "https://example.com" --title2 "Второй заголовок" --display-url-path "deals" --mobile YES --vcard-id 111 --sitelink-set-id 222 --turbo-page-id 333 --ad-extensions "444,555" --dry-run
|
|
1113
|
+
direct ads add --adgroup-id 12345 --type TEXT_IMAGE_AD --image-hash abcdefghijklmnopqrst --href "https://example.com" --turbo-page-id 555 --dry-run
|
|
1114
|
+
direct ads update --id 99999 --type TEXT_AD --title "Новый заголовок" --text "Новый текст" --href "https://example.com"
|
|
1115
|
+
direct ads update --id 99999 --type TEXT_AD --image-hash abcdefghijklmnopqrst
|
|
1116
|
+
direct ads update --id 99999 --type TEXT_AD --title2 "Новый второй заголовок" --vcard-id 222
|
|
1033
1117
|
direct ads delete --id 99999
|
|
1034
1118
|
```
|
|
1035
1119
|
|
|
1120
|
+
Доступные типизированные флаги TEXT_AD для `ads add` / `ads update`:
|
|
1121
|
+
`--title`, `--text`, `--href`, `--image-hash`, `--title2`, `--display-url-path`,
|
|
1122
|
+
`--vcard-id`, `--sitelink-set-id`, `--turbo-page-id`. `--mobile`
|
|
1123
|
+
(default `NO`) и `--ad-extensions` доступны только в `ads add` — WSDL
|
|
1124
|
+
`TextAdUpdate` не содержит `Mobile`, а обновление расширений идёт через поле
|
|
1125
|
+
`CalloutSetting`, которое пока не покрыто CLI. Для TEXT_IMAGE_AD дополнительно
|
|
1126
|
+
доступен `--turbo-page-id`.
|
|
1127
|
+
|
|
1036
1128
|
#### Ключевые слова
|
|
1037
1129
|
|
|
1038
1130
|
```bash
|
|
@@ -1042,6 +1134,33 @@ direct keywords update --id 88888 --keyword "updated keyword text"
|
|
|
1042
1134
|
direct keywords delete --id 88888
|
|
1043
1135
|
```
|
|
1044
1136
|
|
|
1137
|
+
**Пакетная загрузка ключевых слов** (CLI автоматически режет на куски по API-лимиту 10/запрос):
|
|
1138
|
+
|
|
1139
|
+
```bash
|
|
1140
|
+
# Из JSONL-файла (по одному объекту ключевого слова на строку)
|
|
1141
|
+
direct keywords add --adgroup-id 12345 --from-file keywords.jsonl
|
|
1142
|
+
|
|
1143
|
+
# Inline JSON-массив
|
|
1144
|
+
direct keywords add --keywords-json '[{"Keyword":"купить ноутбук","Bid":10000000},{"Keyword":"купить ПК"}]'
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
Пример `keywords.jsonl`:
|
|
1148
|
+
|
|
1149
|
+
```jsonl
|
|
1150
|
+
{"Keyword":"купить ноутбук","Bid":10000000,"UserParam1":"src=ad1"}
|
|
1151
|
+
{"Keyword":"купить ПК","ContextBid":5000000}
|
|
1152
|
+
{"Keyword":"buy laptop","AdGroupId":99999}
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
- Ключи строки — WSDL CamelCase: `Keyword`, `AdGroupId`, `Bid`, `ContextBid`, `UserParam1`, `UserParam2`.
|
|
1156
|
+
- `--adgroup-id` задаёт значение по умолчанию; в строке можно переопределить через `AdGroupId`.
|
|
1157
|
+
- В каждой строке должны разрешаться `Keyword` и `AdGroupId`; неизвестные поля отклоняются с указанием номера строки.
|
|
1158
|
+
- API-лимит: 10 элементов на запрос `keywords.add` — см. [документацию Yandex Direct](https://yandex.ru/dev/direct/doc/dg/objects/keyword.html). CLI отправит нужное число чанков и склеит `AddResults`.
|
|
1159
|
+
- API-лимит: 200 ключевых слов на одну группу объявлений. CLI печатает предупреждение, если в каком-то `AdGroupId` во входе их больше; API отклонит излишек item-level ошибками.
|
|
1160
|
+
- Item-level ошибки от API не прерывают batch; объединённый вывод содержит и успешные Id, и ошибки.
|
|
1161
|
+
- При сетевой ошибке в середине batch уже созданные Id выводятся в stderr (`Partial success before failure`), чтобы при retry не возникли дубли.
|
|
1162
|
+
- `--dry-run` показывает payload первого чанка плюс `{chunks, totalItems, chunkSize}`.
|
|
1163
|
+
|
|
1045
1164
|
#### Отчёты
|
|
1046
1165
|
|
|
1047
1166
|
```bash
|
|
@@ -1098,8 +1217,8 @@ direct dynamicads set-bids --id 789 --bid 12500000 --context-bid 9000000 --prior
|
|
|
1098
1217
|
|
|
1099
1218
|
# Общие стратегии ставок
|
|
1100
1219
|
direct strategies get --limit 5
|
|
1101
|
-
direct strategies add --name "Общая стратегия" --type WbMaximumClicks --spend-limit 1000000000 --
|
|
1102
|
-
direct strategies update --id 42 --type WbMaximumClicks --
|
|
1220
|
+
direct strategies add --name "Общая стратегия" --type WbMaximumClicks --weekly-spend-limit 1000000000 --bid-ceiling 30000000 --dry-run
|
|
1221
|
+
direct strategies update --id 42 --type WbMaximumClicks --weekly-spend-limit 35000000 --dry-run
|
|
1103
1222
|
direct strategies archive --id 42 --dry-run
|
|
1104
1223
|
|
|
1105
1224
|
# Динамические таргеты по фиду
|
|
@@ -1113,7 +1232,7 @@ direct vcards add --campaign-id 555 --country "Россия" --city "Москв
|
|
|
1113
1232
|
direct adextensions add --callout-text "Free shipping" --dry-run
|
|
1114
1233
|
direct adimages add --name banner.png --image-data BASE64DATA --type ICON --dry-run
|
|
1115
1234
|
direct creatives add --video-id video-id --dry-run
|
|
1116
|
-
direct feeds add --name "Фид A" --url "https://example.com/feed.xml" --dry-run
|
|
1235
|
+
direct feeds add --name "Фид A" --url "https://example.com/feed.xml" --business-type RETAIL --dry-run
|
|
1117
1236
|
direct feeds update --id 18 --name "Фид A v2" --url "https://example.com/feed-v2.xml" --dry-run
|
|
1118
1237
|
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
|
|
1119
1238
|
direct --login CLIENT_LOGIN clients update --phone +70000000000 --notification-email user@example.com --dry-run
|
|
@@ -1177,6 +1296,17 @@ direct campaigns get --fetch-all # все страницы
|
|
|
1177
1296
|
direct campaigns add --name "Тест" --start-date 2024-01-01 --dry-run
|
|
1178
1297
|
```
|
|
1179
1298
|
|
|
1299
|
+
### Ошибки API
|
|
1300
|
+
|
|
1301
|
+
Яндекс Директ может вернуть успешный HTTP-ответ, внутри которого есть
|
|
1302
|
+
item-level `Errors` для конкретного объекта. Direct CLI считает такой ответ
|
|
1303
|
+
ошибкой операции: команда завершается с ненулевым кодом и печатает код ошибки,
|
|
1304
|
+
сообщение и детали.
|
|
1305
|
+
|
|
1306
|
+
Код `8800` с `Object not found` обычно означает, что объект недоступен в
|
|
1307
|
+
текущем `Client-Login` или аккаунте. Перед повтором проверьте выбранный
|
|
1308
|
+
`--login`, `YANDEX_DIRECT_LOGIN` или auth profile.
|
|
1309
|
+
|
|
1180
1310
|
### Тестирование
|
|
1181
1311
|
|
|
1182
1312
|
В `tests/` четыре уровня тестов:
|
|
@@ -1186,17 +1316,22 @@ direct campaigns add --name "Тест" --start-date 2024-01-01 --dry-run
|
|
|
1186
1316
|
| Юнит / CLI / dry-run | *(без маркера)* | Нет | Нет |
|
|
1187
1317
|
| Read-only интеграция | `-m integration` | Да (prod API, только чтение) | Да |
|
|
1188
1318
|
| Write интеграция | `-m integration_write` | Нет (replay VCR-кассет) | Нет |
|
|
1189
|
-
| Live draft write интеграция | `-m integration_live_write` | Да при записи, иначе VCR replay | Да + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
1319
|
+
| Live draft write интеграция (v5) | `-m integration_live_write` | Да при записи, иначе VCR replay | Да + `YANDEX_DIRECT_LIVE_WRITE=1` |
|
|
1320
|
+
| v4 live read | `-m v4_live_read` | Да (prod v4 JSON API, только чтение) | Да |
|
|
1321
|
+
| v4 live запись отчётов на уровне аккаунта (opt-in) | `-k _opt_in_write` в `tests/test_v4_live_contracts.py` | Да (prod v4) | Да + `YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1` |
|
|
1190
1322
|
|
|
1191
1323
|
```bash
|
|
1192
1324
|
pip install -e ".[dev]"
|
|
1193
1325
|
pytest # быстрый уровень — без токена
|
|
1194
1326
|
pytest -m integration -v # read-only интеграция (нужен токен)
|
|
1195
1327
|
pytest -m integration_write -v # replay write-кассет (токен не нужен)
|
|
1196
|
-
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # replay live draft-кассеты
|
|
1328
|
+
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v # replay live draft-кассеты (v5)
|
|
1197
1329
|
YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rewrite # перезапись live draft-кассеты
|
|
1330
|
+
YANDEX_DIRECT_V4_LIVE_REPORT_WRITE=1 pytest tests/test_v4_live_contracts.py -k _opt_in_write -v # жизненный цикл v4 wordstat/forecast
|
|
1198
1331
|
```
|
|
1199
1332
|
|
|
1333
|
+
Уровень 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`).
|
|
1334
|
+
|
|
1200
1335
|
#### Smoke-скрипты команд
|
|
1201
1336
|
|
|
1202
1337
|
Каждая CLI-подкоманда классифицирована в `direct_cli/smoke_matrix.py`.
|
|
@@ -1248,6 +1383,12 @@ sandbox-токен не нужен.
|
|
|
1248
1383
|
Для `account-management` нужна переменная
|
|
1249
1384
|
`YANDEX_DIRECT_V4ACCOUNT_ACCOUNT_ID`; без неё runner покажет `NOT_COVERED`.
|
|
1250
1385
|
|
|
1386
|
+
`clients.update` включается только явно, потому что меняет client-level
|
|
1387
|
+
metadata аккаунта. Укажите `YANDEX_DIRECT_CLIENTS_UPDATE_LOGIN` с disposable
|
|
1388
|
+
sandbox `Client-Login`; runner передаст его через `--login` и изменит только
|
|
1389
|
+
`ClientInfo` на уникальный smoke marker. Без этой переменной runner покажет
|
|
1390
|
+
`NOT_COVERED` для `clients.update`.
|
|
1391
|
+
|
|
1251
1392
|
#### Перезапись write-кассет
|
|
1252
1393
|
|
|
1253
1394
|
Уровень `integration_write` в pytest всё ещё воспроизводит сохранённый
|