direct-cli 0.3.16__tar.gz → 0.4.1__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.16 → direct_cli-0.4.1}/CHANGELOG.md +196 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/PKG-INFO +50 -4
- {direct_cli-0.3.16 → direct_cli-0.4.1}/README.md +49 -3
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +1 -1
- direct_cli-0.4.1/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +27 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +36 -14
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +5 -1
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +10 -6
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +1 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/auth.py +91 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/cli.py +125 -23
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/adextensions.py +2 -1
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/adgroups.py +82 -42
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/adimages.py +2 -1
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/ads.py +125 -62
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/advideos.py +4 -1
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/agencyclients.py +13 -6
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/audiencetargets.py +4 -3
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/auth.py +14 -11
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/balance.py +4 -1
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/bidmodifiers.py +21 -13
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/bids.py +25 -10
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/campaigns.py +150 -87
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/changes.py +16 -10
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/clients.py +5 -2
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/creatives.py +4 -1
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/dynamicads.py +6 -2
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/dynamicfeedadtargets.py +6 -3
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/feeds.py +32 -20
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/keywordbids.py +23 -12
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/keywords.py +107 -55
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/negativekeywordsharedsets.py +8 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/reports.py +5 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/retargeting.py +12 -4
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/sitelinks.py +41 -16
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/smartadtargets.py +7 -4
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/strategies.py +48 -23
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/v4account.py +48 -28
- direct_cli-0.4.1/direct_cli/commands/v4adimage.py +209 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/v4events.py +14 -6
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/v4finance.py +160 -35
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/v4forecast.py +3 -2
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/v4goals.py +2 -1
- direct_cli-0.4.1/direct_cli/commands/v4keywords.py +81 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/v4tags.py +29 -18
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/v4wordstat.py +3 -2
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/vcards.py +10 -3
- direct_cli-0.4.1/direct_cli/i18n.py +158 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/smoke_matrix.py +4 -0
- direct_cli-0.4.1/direct_cli/translations/adextensions.json +11 -0
- direct_cli-0.4.1/direct_cli/translations/adgroups.json +74 -0
- direct_cli-0.4.1/direct_cli/translations/adimages.json +13 -0
- direct_cli-0.4.1/direct_cli/translations/ads.json +134 -0
- direct_cli-0.4.1/direct_cli/translations/advideos.json +11 -0
- direct_cli-0.4.1/direct_cli/translations/agencyclients.json +32 -0
- direct_cli-0.4.1/direct_cli/translations/audiencetargets.json +17 -0
- direct_cli-0.4.1/direct_cli/translations/auth.json +23 -0
- direct_cli-0.4.1/direct_cli/translations/balance.json +4 -0
- direct_cli-0.4.1/direct_cli/translations/bidmodifiers.json +46 -0
- direct_cli-0.4.1/direct_cli/translations/bids.json +31 -0
- direct_cli-0.4.1/direct_cli/translations/businesses.json +5 -0
- direct_cli-0.4.1/direct_cli/translations/campaigns.json +313 -0
- direct_cli-0.4.1/direct_cli/translations/changes.json +18 -0
- direct_cli-0.4.1/direct_cli/translations/clients.json +41 -0
- direct_cli-0.4.1/direct_cli/translations/common.json +20 -0
- direct_cli-0.4.1/direct_cli/translations/creatives.json +12 -0
- direct_cli-0.4.1/direct_cli/translations/dictionaries.json +10 -0
- direct_cli-0.4.1/direct_cli/translations/dynamicads.json +10 -0
- direct_cli-0.4.1/direct_cli/translations/dynamicfeedadtargets.json +12 -0
- direct_cli-0.4.1/direct_cli/translations/feeds.json +37 -0
- direct_cli-0.4.1/direct_cli/translations/keywordbids.json +20 -0
- direct_cli-0.4.1/direct_cli/translations/keywords.json +62 -0
- direct_cli-0.4.1/direct_cli/translations/keywordsresearch.json +7 -0
- direct_cli-0.4.1/direct_cli/translations/leads.json +7 -0
- direct_cli-0.4.1/direct_cli/translations/negativekeywordsharedsets.json +11 -0
- direct_cli-0.4.1/direct_cli/translations/reports.json +23 -0
- direct_cli-0.4.1/direct_cli/translations/retargeting.json +16 -0
- direct_cli-0.4.1/direct_cli/translations/sitelinks.json +26 -0
- direct_cli-0.4.1/direct_cli/translations/smartadtargets.json +19 -0
- direct_cli-0.4.1/direct_cli/translations/strategies.json +60 -0
- direct_cli-0.4.1/direct_cli/translations/turbopages.json +6 -0
- direct_cli-0.4.1/direct_cli/translations/v4account.json +51 -0
- direct_cli-0.4.1/direct_cli/translations/v4adimage.json +15 -0
- direct_cli-0.4.1/direct_cli/translations/v4events.json +19 -0
- direct_cli-0.4.1/direct_cli/translations/v4finance.json +37 -0
- direct_cli-0.4.1/direct_cli/translations/v4forecast.json +14 -0
- direct_cli-0.4.1/direct_cli/translations/v4goals.json +5 -0
- direct_cli-0.4.1/direct_cli/translations/v4keywords.json +6 -0
- direct_cli-0.4.1/direct_cli/translations/v4shells.json +3 -0
- direct_cli-0.4.1/direct_cli/translations/v4tags.json +30 -0
- direct_cli-0.4.1/direct_cli/translations/v4wordstat.json +10 -0
- direct_cli-0.4.1/direct_cli/translations/vcards.json +34 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/utils.py +1 -1
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/v4_contracts.py +1 -2
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli.egg-info/PKG-INFO +50 -4
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli.egg-info/SOURCES.txt +50 -1
- direct_cli-0.3.16/docs/audits/PROJECT_WIRE_SHAPE_AUDIT_2026-05-29.md → direct_cli-0.4.1/docs/audits/PROJECT_WIRE_SHAPE_AUDIT_2026-05-30.md +11 -64
- direct_cli-0.4.1/docs/audits/WIRE_SHAPE_TRIAGE_2026-05-30.md +85 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/docs/audits/wire_shape.json +17 -441
- {direct_cli-0.3.16 → direct_cli-0.4.1}/pyproject.toml +6 -1
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/audit_wire_shape.py +49 -10
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/build_api_coverage_report.py +5 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/check_all_docs_urls.py +15 -0
- direct_cli-0.4.1/scripts/probe_drift_urls.sh +59 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/api_coverage_payloads.py +4 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/conftest.py +45 -2
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_api_coverage.py +13 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_audit_wire_shape.py +40 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_auth_oauth.py +135 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_balance.py +20 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_cli.py +90 -9
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_comprehensive.py +2 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_dry_run.py +93 -0
- direct_cli-0.4.1/tests/test_i18n.py +388 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_reports_parsing.py +55 -0
- direct_cli-0.4.1/tests/test_v4adimage.py +176 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4finance_money.py +79 -6
- direct_cli-0.4.1/tests/test_v4keywords.py +112 -0
- direct_cli-0.3.16/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -14
- direct_cli-0.3.16/direct_cli/i18n.py +0 -107
- direct_cli-0.3.16/tests/test_i18n.py +0 -118
- {direct_cli-0.3.16 → direct_cli-0.4.1}/.env.example +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/.github/copilot-instructions.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/.github/workflows/api-coverage.yml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/.github/workflows/claude.yml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/.github/workflows/quality.yml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/.gitignore +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/AGENTS.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/CLAUDE.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/MANIFEST.in +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/__init__.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_bidding_strategy.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_deprecated.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_smoke_probes.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/__init__.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/api.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/businesses.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/dictionaries.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/turbopages.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/commands/v4shells.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/output.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/reports_coverage.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/v4/__init__.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/v4/money.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/wsdl_coverage.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli.egg-info/requires.txt +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/docs/audits/API_COVERAGE.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/docs/audits/issue-198-mutating-wsdl-audit.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/anonymize_cassettes.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/build_api_coverage_checklist.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/build_wsdl_optional_field_audit.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/check_reports_drift.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/check_wsdl_drift.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/patch_vendor_imports.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/preflight_check.sh +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/refresh_reports_cache.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/refresh_wsdl_cache.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/release_pypi.sh +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/sandbox_write_audit.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/sandbox_write_live.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/test_dangerous_commands.sh +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/test_safe_commands.sh +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/test_sandbox_write.sh +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/scripts/update_vendor.sh +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/setup.cfg +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/setup.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/API_COVERAGE.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/API_ISSUE_AUDIT.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/MANUAL_COVERAGE.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/WSDL_OPTIONAL_FIELD_AUDIT.md +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/__init__.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/_orphan_store.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[adextensions_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[adgroups_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[adimages_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[ads_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[advideos_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[audiencetargets_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[bidmodifiers_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[bids_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[businesses_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[campaigns_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_campaigns].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_dictionaries].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[clients_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[creatives_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[dictionaries_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[dynamicads_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[dynamicfeedadtargets_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[feeds_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordbids_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[keywords_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_deduplicate].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_has_search_volume].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[leads_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[negativekeywordsharedsets_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[reports_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[retargeting_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[sitelinks_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[smartadtargets_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[strategies_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[turbopages_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[v4events_get_events_log].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[v4finance_get_clients_units].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[v4forecast_list].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[v4goals_get_stat_goals].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[v4tags_get_campaigns].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[v4wordstat_list_reports].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_read_command[vcards_get].yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_read_cassettes/test_v4finance_check_payment_unknown_transaction.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/fixtures/test-video.mp4 +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/reports_cache/raw/fields-list.html +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/reports_cache/raw/headers.html +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/reports_cache/raw/period.html +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/reports_cache/raw/spec.html +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/reports_cache/raw/type.html +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/reports_cache/spec.json +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_auth_op.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_auth_write_json.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_changes.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_cli_contract.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_env_loading.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_integration.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_integration_write.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_low_coverage_payloads.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_read_cassettes.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_reports_drift.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_sandbox_write_audit.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_smoke_matrix.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_transport_contract.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_unknown_option_hints.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4_contracts.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4_exit_codes.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4_foundation.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4_live_contracts.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4_runtime_shape.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4_safety.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4account.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4events.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4finance_read.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4forecast.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4goals.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4tags.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v4wordstat.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_v5_live_write.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_vendor_imports.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/test_wsdl_parity_gate.py +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/adextensions.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/adgroups.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/adimages.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/ads.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/advideos.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/agencyclients.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/audiencetargets.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/bidmodifiers.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/bids.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/businesses.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/campaigns.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/changes.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/clients.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/creatives.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/dictionaries.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/feeds.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/imports/general.xsd +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/keywordbids.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/keywords.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/keywordsresearch.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/leads.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/retargetinglists.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/sitelinks.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/smartadtargets.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/strategies.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/turbopages.xml +0 -0
- {direct_cli-0.3.16 → direct_cli-0.4.1}/tests/wsdl_cache/vcards.xml +0 -0
|
@@ -1,5 +1,201 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.1
|
|
4
|
+
|
|
5
|
+
Russian-default CLI localization across all command modules (epic #466).
|
|
6
|
+
|
|
7
|
+
**Fixed — bug hunt (#483):**
|
|
8
|
+
|
|
9
|
+
- `bids get` / `keywordbids get`: refuse an empty `SelectionCriteria` before the
|
|
10
|
+
API call, raising a `UsageError` that asks for at least one filter
|
|
11
|
+
(`--campaign-ids` / `--adgroup-ids` / `--keyword-ids` / `--serving-statuses`)
|
|
12
|
+
instead of letting the API reject it with the opaque error 4001.
|
|
13
|
+
- `bids set-auto`: require exactly one of `--campaign-id`, `--adgroup-id`, or
|
|
14
|
+
`--keyword-id` via the shared `add_single_id_selector` (the three are mutually
|
|
15
|
+
exclusive per the API docs), matching `bids set`.
|
|
16
|
+
- `reports get`: reject a `--fields` value that parses to an empty list (for
|
|
17
|
+
example `",,,"`) before building the request, instead of sending an invalid
|
|
18
|
+
`FieldNames: []` (API error 8000).
|
|
19
|
+
- Error-handling consistency: `get`/lifecycle handlers across `bids`,
|
|
20
|
+
`keywordbids`, `negativekeywordsharedsets`, `balance`, `strategies`,
|
|
21
|
+
`retargeting`, `ads` (all 8 commands), and `advideos` now re-raise
|
|
22
|
+
`click.UsageError` / `click.ClickException` before the generic
|
|
23
|
+
`except Exception`, so validation errors keep their Click formatting and
|
|
24
|
+
exit code 2 instead of being downgraded to an `Abort`.
|
|
25
|
+
- Vendor `tapi_yandex_direct`: `to_columns()` no longer raises `IndexError` on
|
|
26
|
+
report rows shorter than the header (pads with `""`); the error handler reads
|
|
27
|
+
`error_detail` with `.get()` so an unfamiliar error structure no longer masks
|
|
28
|
+
the original API error with a `KeyError`.
|
|
29
|
+
- `utils.parse_priority_goals_spec`: corrected the item type annotation to
|
|
30
|
+
`List[Dict[str, Any]]` (items hold `"YES"/"NO"` strings, not only ints).
|
|
31
|
+
|
|
32
|
+
**Fixed — `--help` hung on a client-login network call (#480 follow-up):**
|
|
33
|
+
|
|
34
|
+
- After #480, `get_credentials` resolved the bare Client-Login via a network
|
|
35
|
+
`clients.get` on every CLI invocation — including `<group> --help` — whenever
|
|
36
|
+
an OAuth profile with an email login had not yet been migrated. That call had
|
|
37
|
+
no timeout, so a slow link or a Yandex SmartCaptcha gateway could hang the
|
|
38
|
+
CLI. Help/version passes now skip the resolver, the resolver is capped with a
|
|
39
|
+
hard timeout, and the unit suite neutralizes it so tests never touch the
|
|
40
|
+
network.
|
|
41
|
+
|
|
42
|
+
**Fixed — auth login saved Passport email, breaking v4 (#480):**
|
|
43
|
+
|
|
44
|
+
- `direct auth login` (OAuth / PKCE) stored the Passport email
|
|
45
|
+
(`<login>@yandex.ru`) in `auth.json`, which Direct v4 AccountManagement
|
|
46
|
+
rejects with `FaultCode 259` ("This client does not exist") — breaking
|
|
47
|
+
`direct balance` and `direct v4account ...`. Login now resolves the bare
|
|
48
|
+
**Client-Login** via a one-shot v5 `clients.get` (`resolve_account_login`),
|
|
49
|
+
falling back to the Passport login only if that call fails.
|
|
50
|
+
- `get_credentials` migrates older profiles in place: when a stored OAuth login
|
|
51
|
+
is an email whose local part matches the token owner's resolved Client-Login,
|
|
52
|
+
it is rewritten to the bare login (one-time, persisted). Agency profiles whose
|
|
53
|
+
login differs from the token owner are never clobbered, and an explicit
|
|
54
|
+
`--login` is never overridden.
|
|
55
|
+
|
|
56
|
+
**Localized — interpolated error messages (#478), completing epic #466:**
|
|
57
|
+
|
|
58
|
+
- Rewrote all 121 interpolated `click.UsageError` / `click.BadParameter` /
|
|
59
|
+
`print_*` messages (114 f-strings + 7 string concatenations) across the
|
|
60
|
+
command modules into the stable `t("<template>").format(**kwargs)` pattern,
|
|
61
|
+
with 96 unique English templates translated to Russian (6 shared templates,
|
|
62
|
+
e.g. `Provide a non-empty comma-separated {wsdl_key} list.`, in
|
|
63
|
+
`common.json`). Placeholder names, conversions, and format specs are
|
|
64
|
+
preserved verbatim in both locales, so the rendered English text is
|
|
65
|
+
byte-identical to before and the Russian render fills the same fields.
|
|
66
|
+
- `t()` gained `@overload` signatures (`str -> str`, `None -> None`) so
|
|
67
|
+
`t(...).format(...)` type-checks without touching call sites.
|
|
68
|
+
- `tests/test_i18n.py` now (a) flags f-string / concat — not just static —
|
|
69
|
+
bare literals in `_RUNTIME_MESSAGE_FUNCS` calls (also covering
|
|
70
|
+
`print_success`), accepting only `t(...)` / `t(...).format(...)`; and
|
|
71
|
+
(b) asserts placeholder parity between every English template key and its
|
|
72
|
+
Russian translation. This completes the error-message localization checklist
|
|
73
|
+
of epic #466.
|
|
74
|
+
|
|
75
|
+
**Localized — static error messages (#477):**
|
|
76
|
+
|
|
77
|
+
- Wrapped all 179 static `click.UsageError` / `click.BadParameter` string
|
|
78
|
+
literals (and the `auth` OAuth-code `click.prompt`) across 33 command
|
|
79
|
+
modules in `t(...)`, with Russian translations added to the per-module
|
|
80
|
+
`translations/*.json` catalogs; seven messages shared across modules
|
|
81
|
+
(e.g. `Provide at least one field to update`,
|
|
82
|
+
`--status and --statuses are mutually exclusive`) live in `common.json`.
|
|
83
|
+
Validation errors now render in Russian by default and in English under
|
|
84
|
+
`--locale en`. Flag names, enum values, and WSDL field names are unchanged.
|
|
85
|
+
- Extended `tests/test_i18n.py::test_localized_groups_wrap_runtime_messages`
|
|
86
|
+
to also scan `UsageError` / `BadParameter` / `prompt` / `confirm` (bare or
|
|
87
|
+
`click.<Name>`); a bare string literal first argument is rejected. Only
|
|
88
|
+
static `ast.Constant` literals are enforced for now — interpolated
|
|
89
|
+
(f-string / concat) messages are stage B (#478).
|
|
90
|
+
- Test suite defaults the CLI locale to English (`tests/conftest.py` autouse
|
|
91
|
+
fixture) so the existing English error-contract assertions stay valid; the
|
|
92
|
+
Russian default remains covered by `tests/test_i18n.py`.
|
|
93
|
+
|
|
94
|
+
**Added — scalable i18n mechanism (#467):**
|
|
95
|
+
|
|
96
|
+
- Source-string-keyed translation catalog: the English `help=` / docstring /
|
|
97
|
+
epilog text is the catalog key, with Russian translations in external
|
|
98
|
+
`direct_cli/translations/*.json` files (one per module, plus shared
|
|
99
|
+
`common.json`). No `cls=`/`help_key` edits in command modules —
|
|
100
|
+
`cli._apply_directcli_classes` retypes every plain `click.Option` to
|
|
101
|
+
`LocalizedOption` and localizes command/group docstrings and epilogs at
|
|
102
|
+
render time.
|
|
103
|
+
- `t()` is now source-keyed and context-free safe (`set_active_locale`), so
|
|
104
|
+
`print_*` runtime messages localize too. `--locale` is eager so the root
|
|
105
|
+
`--help` epilog honors an inline `--locale`.
|
|
106
|
+
- `tests/test_i18n.py` gains a `LOCALIZED_GROUPS` registry with two enforced
|
|
107
|
+
invariants per localized module: translation completeness (no silent English
|
|
108
|
+
leak under the Russian default) and `print_*` runtime-message wrapping.
|
|
109
|
+
- `v4finance` migrated to the new mechanism and fully localized as the
|
|
110
|
+
reference module.
|
|
111
|
+
|
|
112
|
+
**Localized — Core search (#468):**
|
|
113
|
+
|
|
114
|
+
- Russian help/docstrings for `campaigns`, `ads`, `adgroups`, `keywords`,
|
|
115
|
+
`keywordbids`, and `bids` (510 unique strings across the six modules).
|
|
116
|
+
WSDL field paths, enum values, and flag names are kept verbatim; only
|
|
117
|
+
human-readable text is translated. These groups join `LOCALIZED_GROUPS`,
|
|
118
|
+
so their translation completeness is now enforced by `test_i18n.py`.
|
|
119
|
+
|
|
120
|
+
**Localized — Targeting & creatives (#469):**
|
|
121
|
+
|
|
122
|
+
- Russian help/docstrings for `strategies`, `bidmodifiers`, `smartadtargets`,
|
|
123
|
+
`vcards`, `feeds`, `dynamicads`, `audiencetargets`, `dynamicfeedadtargets`,
|
|
124
|
+
`retargeting`, `negativekeywordsharedsets`, `adextensions`, `adimages`,
|
|
125
|
+
`sitelinks`, `creatives`, `advideos`, and `turbopages` (247 unique strings
|
|
126
|
+
across the sixteen modules). WSDL field paths, enum values, and flag names
|
|
127
|
+
are kept verbatim; only human-readable text is translated. These groups
|
|
128
|
+
join `LOCALIZED_GROUPS`, so their translation completeness is now enforced
|
|
129
|
+
by `test_i18n.py`.
|
|
130
|
+
|
|
131
|
+
**Localized — Account, clients, reporting (#470):**
|
|
132
|
+
|
|
133
|
+
- Russian help/docstrings for `clients`, `agencyclients`, `reports`, `changes`,
|
|
134
|
+
`auth`, `leads`, `dictionaries`, `keywordsresearch`, `businesses`, and
|
|
135
|
+
`balance` (142 source strings across the ten modules). WSDL field paths,
|
|
136
|
+
enum values, and flag names are kept verbatim; only human-readable text is
|
|
137
|
+
translated.
|
|
138
|
+
- First modules with localized **runtime messages**: `print_*` calls carrying a
|
|
139
|
+
human-readable literal (`auth` interactive prompts, the `agencyclients delete`
|
|
140
|
+
not-supported notice) are now wrapped in `t()` so they follow the active
|
|
141
|
+
locale. `print_error(str(e))` API-error passthroughs are unchanged. These
|
|
142
|
+
groups join `LOCALIZED_GROUPS`, enforcing both translation completeness and
|
|
143
|
+
the runtime-message wrapping invariant.
|
|
144
|
+
|
|
145
|
+
**Localized — v4 Live services (#471), completing epic #466:**
|
|
146
|
+
|
|
147
|
+
- Russian help/docstrings for `v4account`, `v4tags`, `v4forecast`,
|
|
148
|
+
`v4wordstat`, `v4events`, `v4adimage`, `v4goals`, `v4keywords`, and `v4meta`
|
|
149
|
+
(87 source strings across the nine modules). `v4finance` was already
|
|
150
|
+
localized as the reference module in #467.
|
|
151
|
+
- With these groups, **all 42 CLI command groups are in `LOCALIZED_GROUPS`**:
|
|
152
|
+
the completeness invariant now covers the entire command tree, so every
|
|
153
|
+
English help/docstring string ships with a Russian translation under the
|
|
154
|
+
Russian default. The `v4 Live` epilog (single-sourced docs URL) stays
|
|
155
|
+
verbatim per the URL-registry rule. This closes the localization epic #466.
|
|
156
|
+
|
|
157
|
+
## 0.4.0
|
|
158
|
+
|
|
159
|
+
Milestone release closing the 0.4.0 roadmap (#123): typed Yandex Direct
|
|
160
|
+
API **v4 Live** CLI support and completion of the post-0.3.0 write-command
|
|
161
|
+
coverage gates. All public v4 input stays typed and canonical — no `--json`
|
|
162
|
+
passthrough — and mirrors `dg-v4/live/*` wire shapes 1:1.
|
|
163
|
+
|
|
164
|
+
**Added — typed v4 Live CLI:**
|
|
165
|
+
|
|
166
|
+
- v4 Live command foundation with typed Click groups and a `--dry-run`
|
|
167
|
+
seam that prints the `{method, param}` body before token/locale
|
|
168
|
+
enrichment (#124, closes #111 — typed CLI, not raw JSON passthrough).
|
|
169
|
+
- `v4finance` and `v4account` typed finance and shared-account commands
|
|
170
|
+
(#125).
|
|
171
|
+
- `v4goals`, `v4events`, `v4wordstat`, and `v4forecast` typed commands
|
|
172
|
+
(#126).
|
|
173
|
+
- `v4events get-events-log` and `v4forecast create-new-forecast` now expose
|
|
174
|
+
every documented input field (#456).
|
|
175
|
+
- Russian-default CLI help with English opt-in, starting with `v4finance`
|
|
176
|
+
(#458).
|
|
177
|
+
|
|
178
|
+
**Changed — write-command coverage gates:**
|
|
179
|
+
|
|
180
|
+
- Extended the WSDL schema gate to mutating operations; `keywordbids.set`
|
|
181
|
+
is now enum-validated against its WSDL `*FieldEnum` (#118).
|
|
182
|
+
- Per-method `WRITE_SANDBOX` integration coverage completed — zero
|
|
183
|
+
unexplained `NOT_COVERED` commands in `direct_cli/smoke_matrix.py`
|
|
184
|
+
(#122).
|
|
185
|
+
- Closed the remaining mutating `DRY_RUN_PAYLOAD_EXCLUSIONS`; every
|
|
186
|
+
declared WSDL operation now has a `PAYLOAD_CASES` fixture or a
|
|
187
|
+
documented technical exclusion (#127).
|
|
188
|
+
|
|
189
|
+
**Tests / tooling:**
|
|
190
|
+
|
|
191
|
+
- Offline VCR cassettes for all v5 read-only commands (#455).
|
|
192
|
+
- v4 Live read cassettes and a fix for an unbounded retry-loop (#457,
|
|
193
|
+
closes #454).
|
|
194
|
+
- Docs/wire-shape scanner with the 2026-05-29 sweep (#451).
|
|
195
|
+
|
|
196
|
+
`strict_parity_ok`, `live_model_parity_ok`, and `schema_parity_ok` all
|
|
197
|
+
report `true` in `scripts/build_api_coverage_report.py`.
|
|
198
|
+
|
|
3
199
|
## 0.3.16
|
|
4
200
|
|
|
5
201
|
**BREAKING CHANGES (regression fix — reverts 0.3.15 wire-shape changes):**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: direct-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Command-line interface for Yandex Direct API
|
|
5
5
|
Author: axisrow
|
|
6
6
|
License: MIT
|
|
@@ -208,6 +208,31 @@ direct v4wordstat get-report --report-id 123 --format table
|
|
|
208
208
|
direct v4wordstat delete-report --report-id 123
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
+
### V4 Live Keyword Suggestions
|
|
212
|
+
|
|
213
|
+
`get-suggestion` returns up to 20 related phrases for the seed phrases. Repeat
|
|
214
|
+
`--keyword` for multiple seeds. The method consumes API points.
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
direct v4keywords get-suggestion --keyword холодильник --keyword камера
|
|
218
|
+
direct v4keywords get-suggestion --keyword "buy laptop" --format table
|
|
219
|
+
direct v4keywords get-suggestion --keyword холодильник --dry-run
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### V4 Live Ad-Image Associations
|
|
223
|
+
|
|
224
|
+
`AdImageAssociation` is exposed as two typed commands. `get` reads ad-to-image
|
|
225
|
+
associations via an optional selection filter (an empty filter returns up to
|
|
226
|
+
10000 associations). `set` attaches or detaches images: `--association AD_ID=HASH`
|
|
227
|
+
attaches an image, `--association AD_ID` (no hash) detaches the current image
|
|
228
|
+
(max 10000 associations per call).
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
direct v4adimage get --ad-ids 123,456 --status-moderate Yes --limit 20
|
|
232
|
+
direct v4adimage get --campaign-ids 789 --format table
|
|
233
|
+
direct v4adimage set --association 123=abc123hash --association 456 --dry-run
|
|
234
|
+
```
|
|
235
|
+
|
|
211
236
|
### V4 Live Budget Forecasts
|
|
212
237
|
|
|
213
238
|
Budget forecasts are asynchronous. Direct CLI makes exactly one API call per
|
|
@@ -233,9 +258,15 @@ per-request token from `--master-token`, `--operation-num`, and
|
|
|
233
258
|
Environment variables are
|
|
234
259
|
`YANDEX_DIRECT_MASTER_TOKEN`, `YANDEX_DIRECT_FINANCE_LOGIN`,
|
|
235
260
|
`YANDEX_DIRECT_FINANCE_TOKEN`, and `YANDEX_DIRECT_OPERATION_NUM`.
|
|
236
|
-
`transfer-money` and `pay-campaigns` are dry-run-only
|
|
237
|
-
always require `--dry-run`; `create-invoice` can be sent
|
|
238
|
-
is omitted. Dry-run output masks the financial token.
|
|
261
|
+
`transfer-money`, `pay-campaigns`, and `pay-campaigns-by-card` are dry-run-only
|
|
262
|
+
in this release and always require `--dry-run`; `create-invoice` can be sent
|
|
263
|
+
live when `--dry-run` is omitted. Dry-run output masks the financial token.
|
|
264
|
+
`pay-campaigns-by-card` has an undocumented request shape; its dry-run body
|
|
265
|
+
mirrors the documented `pay-campaigns` shape as a best-effort preview.
|
|
266
|
+
|
|
267
|
+
> ⚠ **Finance commands have not been tested against the live API.** Treat the
|
|
268
|
+
> request shapes as best-effort and always verify with `--dry-run` before
|
|
269
|
+
> sending anything that runs live (`create-invoice`).
|
|
239
270
|
|
|
240
271
|
```bash
|
|
241
272
|
direct v4finance get-clients-units --logins client-login,other-client --format table
|
|
@@ -244,6 +275,7 @@ direct v4finance create-invoice --payment 123=100.50 --payment 456=25 --currency
|
|
|
244
275
|
direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
|
|
245
276
|
direct v4finance transfer-money --from-campaign-id 123 --to-campaign-id 456 --amount 100.50 --currency RUB --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
|
|
246
277
|
direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --contract-id CONTRACT_ID --pay-method Bank --currency RUB --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
|
|
278
|
+
direct v4finance pay-campaigns-by-card --campaign-ids 123,456 --amount 100.50 --currency RUB --master-token MASTER_TOKEN --operation-num 128 --finance-login agency-login --dry-run
|
|
247
279
|
```
|
|
248
280
|
|
|
249
281
|
### V4 Live Shared Account
|
|
@@ -272,6 +304,20 @@ direct v4account account-management --action TransferMoney --from-account-id 132
|
|
|
272
304
|
direct --sandbox v4account enable-shared-account --client-login client-login
|
|
273
305
|
```
|
|
274
306
|
|
|
307
|
+
### V4 Live — Intentionally Omitted Methods
|
|
308
|
+
|
|
309
|
+
Some v4 Live methods present in the API registry are intentionally **not**
|
|
310
|
+
exposed as CLI commands:
|
|
311
|
+
|
|
312
|
+
- `DeleteReport` / `DeleteOfflineReport` — disabled by Yandex (the official
|
|
313
|
+
docs list them under "Отключенные методы" / "Метод отключен. Используйте API
|
|
314
|
+
версии 5"). Use the v5 reports API instead.
|
|
315
|
+
- `PingAPI`, `PingAPI_X`, `GetVersion`, `GetAvailableVersions` — service
|
|
316
|
+
diagnostics / version probes with no documented request shape; not useful as
|
|
317
|
+
user-facing CLI commands.
|
|
318
|
+
- `PayCampaignsByCard` is exposed but **dry-run-only** (undocumented,
|
|
319
|
+
financially sensitive — see V4 Live Finance above).
|
|
320
|
+
|
|
275
321
|
### CLI Convention
|
|
276
322
|
|
|
277
323
|
The current CLI convention is defined as follows.
|
|
@@ -165,6 +165,31 @@ direct v4wordstat get-report --report-id 123 --format table
|
|
|
165
165
|
direct v4wordstat delete-report --report-id 123
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
+
### V4 Live Keyword Suggestions
|
|
169
|
+
|
|
170
|
+
`get-suggestion` returns up to 20 related phrases for the seed phrases. Repeat
|
|
171
|
+
`--keyword` for multiple seeds. The method consumes API points.
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
direct v4keywords get-suggestion --keyword холодильник --keyword камера
|
|
175
|
+
direct v4keywords get-suggestion --keyword "buy laptop" --format table
|
|
176
|
+
direct v4keywords get-suggestion --keyword холодильник --dry-run
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### V4 Live Ad-Image Associations
|
|
180
|
+
|
|
181
|
+
`AdImageAssociation` is exposed as two typed commands. `get` reads ad-to-image
|
|
182
|
+
associations via an optional selection filter (an empty filter returns up to
|
|
183
|
+
10000 associations). `set` attaches or detaches images: `--association AD_ID=HASH`
|
|
184
|
+
attaches an image, `--association AD_ID` (no hash) detaches the current image
|
|
185
|
+
(max 10000 associations per call).
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
direct v4adimage get --ad-ids 123,456 --status-moderate Yes --limit 20
|
|
189
|
+
direct v4adimage get --campaign-ids 789 --format table
|
|
190
|
+
direct v4adimage set --association 123=abc123hash --association 456 --dry-run
|
|
191
|
+
```
|
|
192
|
+
|
|
168
193
|
### V4 Live Budget Forecasts
|
|
169
194
|
|
|
170
195
|
Budget forecasts are asynchronous. Direct CLI makes exactly one API call per
|
|
@@ -190,9 +215,15 @@ per-request token from `--master-token`, `--operation-num`, and
|
|
|
190
215
|
Environment variables are
|
|
191
216
|
`YANDEX_DIRECT_MASTER_TOKEN`, `YANDEX_DIRECT_FINANCE_LOGIN`,
|
|
192
217
|
`YANDEX_DIRECT_FINANCE_TOKEN`, and `YANDEX_DIRECT_OPERATION_NUM`.
|
|
193
|
-
`transfer-money` and `pay-campaigns` are dry-run-only
|
|
194
|
-
always require `--dry-run`; `create-invoice` can be sent
|
|
195
|
-
is omitted. Dry-run output masks the financial token.
|
|
218
|
+
`transfer-money`, `pay-campaigns`, and `pay-campaigns-by-card` are dry-run-only
|
|
219
|
+
in this release and always require `--dry-run`; `create-invoice` can be sent
|
|
220
|
+
live when `--dry-run` is omitted. Dry-run output masks the financial token.
|
|
221
|
+
`pay-campaigns-by-card` has an undocumented request shape; its dry-run body
|
|
222
|
+
mirrors the documented `pay-campaigns` shape as a best-effort preview.
|
|
223
|
+
|
|
224
|
+
> ⚠ **Finance commands have not been tested against the live API.** Treat the
|
|
225
|
+
> request shapes as best-effort and always verify with `--dry-run` before
|
|
226
|
+
> sending anything that runs live (`create-invoice`).
|
|
196
227
|
|
|
197
228
|
```bash
|
|
198
229
|
direct v4finance get-clients-units --logins client-login,other-client --format table
|
|
@@ -201,6 +232,7 @@ direct v4finance create-invoice --payment 123=100.50 --payment 456=25 --currency
|
|
|
201
232
|
direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
|
|
202
233
|
direct v4finance transfer-money --from-campaign-id 123 --to-campaign-id 456 --amount 100.50 --currency RUB --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
|
|
203
234
|
direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --contract-id CONTRACT_ID --pay-method Bank --currency RUB --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
|
|
235
|
+
direct v4finance pay-campaigns-by-card --campaign-ids 123,456 --amount 100.50 --currency RUB --master-token MASTER_TOKEN --operation-num 128 --finance-login agency-login --dry-run
|
|
204
236
|
```
|
|
205
237
|
|
|
206
238
|
### V4 Live Shared Account
|
|
@@ -229,6 +261,20 @@ direct v4account account-management --action TransferMoney --from-account-id 132
|
|
|
229
261
|
direct --sandbox v4account enable-shared-account --client-login client-login
|
|
230
262
|
```
|
|
231
263
|
|
|
264
|
+
### V4 Live — Intentionally Omitted Methods
|
|
265
|
+
|
|
266
|
+
Some v4 Live methods present in the API registry are intentionally **not**
|
|
267
|
+
exposed as CLI commands:
|
|
268
|
+
|
|
269
|
+
- `DeleteReport` / `DeleteOfflineReport` — disabled by Yandex (the official
|
|
270
|
+
docs list them under "Отключенные методы" / "Метод отключен. Используйте API
|
|
271
|
+
версии 5"). Use the v5 reports API instead.
|
|
272
|
+
- `PingAPI`, `PingAPI_X`, `GetVersion`, `GetAvailableVersions` — service
|
|
273
|
+
diagnostics / version probes with no documented request shape; not useful as
|
|
274
|
+
user-facing CLI commands.
|
|
275
|
+
- `PayCampaignsByCard` is exposed but **dry-run-only** (undocumented,
|
|
276
|
+
financially sensitive — see V4 Live Finance above).
|
|
277
|
+
|
|
232
278
|
### CLI Convention
|
|
233
279
|
|
|
234
280
|
The current CLI convention is defined as follows.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Runtime endpoints for Yandex Direct API transports.
|
|
2
|
+
|
|
3
|
+
The v5 JSON API historically uses the ``.com`` TLD while the v4 Live API uses
|
|
4
|
+
``.ru``; both hosts are valid Yandex Direct entrypoints. ``get_direct_api_root``
|
|
5
|
+
keeps that per-transport distinction via the ``tld`` argument so neither
|
|
6
|
+
adapter silently changes the domain its callers rely on.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
DIRECT_API_PRODUCTION_ROOT = "https://api.direct.yandex.{tld}/"
|
|
12
|
+
DIRECT_API_SANDBOX_ROOT = "https://api-sandbox.direct.yandex.{tld}/"
|
|
13
|
+
DIRECT_DEBUG_ROOT = "https://"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_direct_api_root(api_params: Dict[str, Any], tld: str = "com") -> str:
|
|
17
|
+
"""Return the Direct API root for production or sandbox requests.
|
|
18
|
+
|
|
19
|
+
``tld`` selects the top-level domain: ``"com"`` for the v5 JSON API
|
|
20
|
+
(default, preserving the historical host) and ``"ru"`` for the v4 Live API.
|
|
21
|
+
"""
|
|
22
|
+
template = (
|
|
23
|
+
DIRECT_API_SANDBOX_ROOT
|
|
24
|
+
if api_params.get("is_sandbox")
|
|
25
|
+
else DIRECT_API_PRODUCTION_ROOT
|
|
26
|
+
)
|
|
27
|
+
return template.format(tld=tld)
|
{direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py
RENAMED
|
@@ -250,29 +250,51 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
250
250
|
return True
|
|
251
251
|
|
|
252
252
|
if error_code == 152:
|
|
253
|
+
# "Not enough units" is an API-points quota error, distinct from the
|
|
254
|
+
# rate-limit codes below: points replenish on a sliding 60-minute
|
|
255
|
+
# window, so the retry waits 5 minutes and uses its own budget
|
|
256
|
+
# (retries_if_not_enough_units) rather than the rate-limit budget.
|
|
257
|
+
# Without a cap the loop would be infinite (sleep, retry, forever).
|
|
253
258
|
if api_params.get("retry_if_not_enough_units", False):
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
259
|
+
if repeat_number < api_params.get("retries_if_not_enough_units", 5):
|
|
260
|
+
logger.warning("Not enough units, re-request after 5 minutes")
|
|
261
|
+
time.sleep(60 * 5)
|
|
262
|
+
return True
|
|
263
|
+
logger.error("Not enough units to request, retries exhausted")
|
|
264
|
+
return False
|
|
257
265
|
else:
|
|
258
266
|
logger.error("Not enough units to request")
|
|
259
267
|
|
|
260
268
|
elif error_code == 506 and api_params.get("retry_if_exceeded_limit", True):
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
# Bound rate-limit retries with their own attempt budget. Without
|
|
270
|
+
# this cap the loop is infinite (sleep 10s, retry, forever) whenever
|
|
271
|
+
# the API keeps returning the limit code — which hangs the caller.
|
|
272
|
+
if repeat_number < api_params.get("retries_if_exceeded_limit", 5):
|
|
273
|
+
logger.warning("API requests exceeded, re-request after 10 seconds")
|
|
274
|
+
time.sleep(10)
|
|
275
|
+
return True
|
|
276
|
+
logger.error("API requests exceeded, retries exhausted")
|
|
277
|
+
return False
|
|
264
278
|
|
|
265
279
|
elif error_code == 56 and api_params.get("retry_if_exceeded_limit", True):
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
280
|
+
if repeat_number < api_params.get("retries_if_exceeded_limit", 5):
|
|
281
|
+
logger.warning(
|
|
282
|
+
"Method request limit exceeded. Re-request after 10 seconds"
|
|
283
|
+
)
|
|
284
|
+
time.sleep(10)
|
|
285
|
+
return True
|
|
286
|
+
logger.error("Method request limit exceeded, retries exhausted")
|
|
287
|
+
return False
|
|
269
288
|
|
|
270
289
|
elif error_code == 9000 and api_params.get("retry_if_exceeded_limit", True):
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
290
|
+
if repeat_number < api_params.get("retries_if_exceeded_limit", 5):
|
|
291
|
+
logger.warning(
|
|
292
|
+
"Created by max number of reports. Re-request after 10 seconds"
|
|
293
|
+
)
|
|
294
|
+
time.sleep(10)
|
|
295
|
+
return True
|
|
296
|
+
logger.error("Max number of reports limit exceeded, retries exhausted")
|
|
297
|
+
return False
|
|
276
298
|
|
|
277
299
|
elif error_code in (52, 1000, 1001, 1002) or status_code == 500:
|
|
278
300
|
if repeat_number < api_params.get("retries_if_server_error", 5):
|
{direct_cli-0.3.16 → direct_cli-0.4.1}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi
RENAMED
|
@@ -101,7 +101,9 @@ class YandexDirect:
|
|
|
101
101
|
login: str = None,
|
|
102
102
|
is_sandbox: bool = False,
|
|
103
103
|
retry_if_not_enough_units: bool = False,
|
|
104
|
+
retries_if_not_enough_units: int = 5,
|
|
104
105
|
retry_if_exceeded_limit: bool = True,
|
|
106
|
+
retries_if_exceeded_limit: int = 5,
|
|
105
107
|
retries_if_server_error: int = 5,
|
|
106
108
|
language: str = None,
|
|
107
109
|
processing_mode: str = "offline",
|
|
@@ -119,8 +121,10 @@ class YandexDirect:
|
|
|
119
121
|
:param login: If you are making inquiries from an agent account, you must be sure to specify the account login.
|
|
120
122
|
:param is_sandbox: Enable sandbox.
|
|
121
123
|
:param retry_if_not_enough_units: Repeat request when units run out
|
|
124
|
+
:param retries_if_not_enough_units: Maximum total request attempts when units run out (includes the initial attempt; default 5 = 1 initial + 4 retries).
|
|
122
125
|
:param retry_if_exceeded_limit: Repeat the request if the limits on the number of reports or requests are exceeded.
|
|
123
|
-
:param
|
|
126
|
+
:param retries_if_exceeded_limit: Maximum total request attempts when report/request limits are exceeded (includes the initial attempt; default 5 = 1 initial + 4 retries).
|
|
127
|
+
:param retries_if_server_error: Maximum total request attempts when server errors occur (includes the initial attempt; default 5 = 1 initial + 4 retries).
|
|
124
128
|
:param language: The language in which the data for directories and errors will be returned.
|
|
125
129
|
|
|
126
130
|
:param processing_mode: (report resource) Report generation mode: online, offline or auto.
|
|
@@ -34,7 +34,8 @@ class V4LiveClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
34
34
|
super().__init__(*args, **kwargs)
|
|
35
35
|
|
|
36
36
|
def get_api_root(self, api_params: dict, resource_name: str) -> str:
|
|
37
|
-
|
|
37
|
+
# v4 Live lives on the .ru host (the v5 JSON API uses .com).
|
|
38
|
+
return get_direct_api_root(api_params, tld="ru")
|
|
38
39
|
|
|
39
40
|
def get_request_kwargs(self, api_params: dict, *args, **kwargs) -> dict:
|
|
40
41
|
params = super().get_request_kwargs(api_params, *args, **kwargs)
|
|
@@ -176,11 +177,14 @@ class V4LiveClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
176
177
|
code = 0
|
|
177
178
|
|
|
178
179
|
if code in (54, 55) and api_params.get("retry_if_exceeded_limit", True):
|
|
179
|
-
# Bound the limit-exceeded retries with
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
|
|
180
|
+
# Bound the limit-exceeded retries with their own attempt budget.
|
|
181
|
+
# Without a cap the loop is infinite (sleep 10s, retry, forever)
|
|
182
|
+
# whenever the API keeps returning code 54/55 — which hangs the
|
|
183
|
+
# caller indefinitely. Rate-limit retries use a dedicated
|
|
184
|
+
# ``retries_if_exceeded_limit`` budget so they stay independent of
|
|
185
|
+
# the server-error budget (a caller may want, e.g., zero
|
|
186
|
+
# server-error retries but still tolerate rate limiting).
|
|
187
|
+
if repeat_number < api_params.get("retries_if_exceeded_limit", 5):
|
|
184
188
|
logger.warning("v4 Live limit exceeded (code=%s), retry in 10s", code)
|
|
185
189
|
time.sleep(10)
|
|
186
190
|
return True
|
|
@@ -48,6 +48,7 @@ class YandexDirectV4Live:
|
|
|
48
48
|
is_sandbox: bool = False,
|
|
49
49
|
language: str = "en",
|
|
50
50
|
retry_if_exceeded_limit: bool = True,
|
|
51
|
+
retries_if_exceeded_limit: int = 5,
|
|
51
52
|
retries_if_server_error: int = 5,
|
|
52
53
|
finance_token: Optional[str] = None,
|
|
53
54
|
operation_num: Optional[int] = None,
|
|
@@ -33,6 +33,9 @@ DEFAULT_OAUTH_CLIENT_ID = "dcf15d9625f6471d94d6d054d52017ba"
|
|
|
33
33
|
AUTH_STORE_PATH = Path.home() / ".direct-cli" / "auth.json"
|
|
34
34
|
OAUTH_REFRESH_SKEW_SECONDS = 60
|
|
35
35
|
PENDING_PKCE_TTL_SECONDS = 600
|
|
36
|
+
# Hard ceiling for the best-effort client-login resolution network call so it
|
|
37
|
+
# can never hang the credential-resolution path (see #480 follow-up).
|
|
38
|
+
LOGIN_RESOLVE_TIMEOUT_SECONDS = 8
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
def op_read(ref: str) -> str:
|
|
@@ -601,6 +604,58 @@ def resolve_login(token: str) -> Optional[str]:
|
|
|
601
604
|
return None
|
|
602
605
|
|
|
603
606
|
|
|
607
|
+
def _resolve_client_login_via_api(token: str) -> Optional[str]:
|
|
608
|
+
"""Return the bare Direct **Client-Login** for *token* via v5 ``clients.get``.
|
|
609
|
+
|
|
610
|
+
The Passport ``/info`` login can be a full email (``<login>@yandex.ru``);
|
|
611
|
+
Direct v4 AccountManagement rejects that with ``FaultCode 259`` (issue
|
|
612
|
+
#480). ``clients.get`` (no ``SelectionCriteria``) returns the authoritative
|
|
613
|
+
Client-Login for the token's own account, avoiding any unreliable
|
|
614
|
+
``split('@')`` guessing. Best-effort: returns ``None`` on any failure.
|
|
615
|
+
"""
|
|
616
|
+
try:
|
|
617
|
+
# Lazy import: api.py imports this module, so importing the vendored
|
|
618
|
+
# client at module load would create a cycle. The vendor never imports
|
|
619
|
+
# auth, so a function-local import is safe.
|
|
620
|
+
from ._vendor.tapi_yandex_direct import YandexDirect
|
|
621
|
+
|
|
622
|
+
# Best-effort, on the credential-resolution hot path: cap the wait so a
|
|
623
|
+
# slow network or a Yandex SmartCaptcha gateway can never hang the CLI
|
|
624
|
+
# indefinitely (requests defaults to no timeout). Retries are disabled
|
|
625
|
+
# so the ceiling stays a single connect+read window.
|
|
626
|
+
client = YandexDirect(
|
|
627
|
+
access_token=token,
|
|
628
|
+
retry_if_exceeded_limit=False,
|
|
629
|
+
retries_if_server_error=0,
|
|
630
|
+
)
|
|
631
|
+
result = client.clients().post(
|
|
632
|
+
data={"method": "get", "params": {"FieldNames": ["Login"]}},
|
|
633
|
+
timeout=LOGIN_RESOLVE_TIMEOUT_SECONDS,
|
|
634
|
+
)
|
|
635
|
+
data = result().extract()
|
|
636
|
+
clients = data.get("Clients") if isinstance(data, dict) else None
|
|
637
|
+
if clients and isinstance(clients[0], dict):
|
|
638
|
+
login = clients[0].get("Login")
|
|
639
|
+
if login:
|
|
640
|
+
return login
|
|
641
|
+
except Exception as exc: # best-effort; never block login on this
|
|
642
|
+
logging.debug("resolve client login via API failed: %s", exc)
|
|
643
|
+
return None
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def resolve_account_login(token: str) -> Optional[str]:
|
|
647
|
+
"""Resolve the bare Direct Client-Login for an OAuth *token*.
|
|
648
|
+
|
|
649
|
+
Prefers the authoritative v5 ``clients.get`` Login (which v4 accepts);
|
|
650
|
+
falls back to the Passport login only when the API call fails (that value
|
|
651
|
+
may be a full email and can break v4 — see :func:`_resolve_client_login_via_api`).
|
|
652
|
+
"""
|
|
653
|
+
login = _resolve_client_login_via_api(token)
|
|
654
|
+
if login:
|
|
655
|
+
return login
|
|
656
|
+
return resolve_login(token)
|
|
657
|
+
|
|
658
|
+
|
|
604
659
|
def get_credentials(
|
|
605
660
|
token: Optional[str] = None,
|
|
606
661
|
login: Optional[str] = None,
|
|
@@ -610,6 +665,7 @@ def get_credentials(
|
|
|
610
665
|
bw_token_ref: Optional[str] = None,
|
|
611
666
|
bw_login_ref: Optional[str] = None,
|
|
612
667
|
profile: Optional[str] = None,
|
|
668
|
+
allow_login_resolve: bool = True,
|
|
613
669
|
) -> Tuple[str, Optional[str]]:
|
|
614
670
|
"""
|
|
615
671
|
Get credentials with priority:
|
|
@@ -655,6 +711,41 @@ def get_credentials(
|
|
|
655
711
|
final_token = oauth_profile["token"]
|
|
656
712
|
if not final_login:
|
|
657
713
|
final_login = oauth_profile["login"]
|
|
714
|
+
stored_login = oauth_profile.get("login")
|
|
715
|
+
if (
|
|
716
|
+
allow_login_resolve
|
|
717
|
+
and not login
|
|
718
|
+
and oauth_profile.get("source") == "oauth"
|
|
719
|
+
and isinstance(stored_login, str)
|
|
720
|
+
and "@" in stored_login
|
|
721
|
+
and not oauth_profile.get("login_migration_checked")
|
|
722
|
+
):
|
|
723
|
+
# One-time migration (#480): older OAuth profiles saved the
|
|
724
|
+
# Passport email in `login`, which Direct v4 rejects with
|
|
725
|
+
# FaultCode 259. Resolve the bare Client-Login and rewrite the
|
|
726
|
+
# profile — but only when the stored email's local part matches
|
|
727
|
+
# the resolved login, so agency profiles whose login differs
|
|
728
|
+
# from the token owner are never clobbered. Either way we stamp
|
|
729
|
+
# `login_migration_checked` once the resolver answered, so the
|
|
730
|
+
# network round-trip never repeats per command for profiles that
|
|
731
|
+
# are intentionally left unmigrated (e.g. agency logins).
|
|
732
|
+
resolved = _resolve_client_login_via_api(final_token)
|
|
733
|
+
if resolved:
|
|
734
|
+
matches = (
|
|
735
|
+
stored_login.split("@", 1)[0].lower() == resolved.lower()
|
|
736
|
+
)
|
|
737
|
+
if matches:
|
|
738
|
+
final_login = resolved
|
|
739
|
+
try:
|
|
740
|
+
store = load_auth_store()
|
|
741
|
+
prof = store["profiles"].get(selected_profile)
|
|
742
|
+
if isinstance(prof, dict):
|
|
743
|
+
prof["login_migration_checked"] = True
|
|
744
|
+
if matches:
|
|
745
|
+
prof["login"] = resolved
|
|
746
|
+
save_auth_store(store)
|
|
747
|
+
except Exception as exc: # best-effort persistence
|
|
748
|
+
logging.debug("login migration persist failed: %s", exc)
|
|
658
749
|
|
|
659
750
|
if selected_profile and not final_token:
|
|
660
751
|
env_token, env_login = get_env_profile(selected_profile)
|