direct-cli 0.3.2__tar.gz → 0.3.4__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.4/.github/workflows/quality.yml +33 -0
- direct_cli-0.3.4/CHANGELOG.md +11 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/PKG-INFO +33 -14
- {direct_cli-0.3.2 → direct_cli-0.3.4}/README.md +28 -13
- direct_cli-0.3.4/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +14 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +110 -18
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +12 -6
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/auth.py +152 -16
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/auth.py +75 -9
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/reports.py +6 -8
- direct_cli-0.3.4/direct_cli/commands/v4finance.py +617 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/smoke_matrix.py +2 -0
- direct_cli-0.3.4/direct_cli/v4/money.py +78 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/v4_contracts.py +23 -10
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli.egg-info/PKG-INFO +33 -14
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli.egg-info/SOURCES.txt +4 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli.egg-info/requires.txt +4 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/pyproject.toml +27 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/sandbox_write_live.py +1 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/test_dangerous_commands.sh +2 -2
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/test_safe_commands.sh +12 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/API_COVERAGE.md +8 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/MANUAL_COVERAGE.md +5 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/api_coverage_payloads.py +2 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_adgroups_add_update_delete.yaml +6 -6
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_adimages_add_get_delete.yaml +1 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_ads_add_update_delete.yaml +8 -8
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_ads_suspend_resume_archive_unarchive.yaml +10 -10
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_advideos_add_get.yaml +2 -2
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_add_delete.yaml +5 -5
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_suspend_resume.yaml +5 -5
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_bids_set.yaml +7 -7
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_campaign_create_get_delete.yaml +4 -4
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_creatives_chain_advideo_to_creative.yaml +3 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_add_delete.yaml +1 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_suspend_resume.yaml +1 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_keywordbids_set.yaml +7 -7
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_add_update_delete.yaml +7 -7
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_suspend_resume.yaml +8 -8
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_sitelinks_add_get_delete.yaml +3 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_add_update_delete.yaml +3 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_suspend_resume.yaml +3 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +2 -2
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +5 -5
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +1 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +5 -5
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +7 -7
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +4 -4
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +3 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +5 -5
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +4 -4
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +6 -6
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +1 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +3 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +5 -5
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +5 -5
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +3 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +2 -2
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +1 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +3 -3
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +4 -4
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/conftest.py +1 -1
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_api_coverage.py +115 -0
- direct_cli-0.3.4/tests/test_auth_oauth.py +703 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_dry_run.py +29 -2
- direct_cli-0.3.4/tests/test_reports_parsing.py +245 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_smoke_matrix.py +3 -2
- direct_cli-0.3.4/tests/test_transport_contract.py +66 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_v4_contracts.py +17 -4
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_v4_live_contracts.py +36 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_v4_safety.py +3 -1
- direct_cli-0.3.4/tests/test_v4finance_money.py +667 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_v4finance_read.py +89 -7
- direct_cli-0.3.2/direct_cli/commands/v4finance.py +0 -298
- direct_cli-0.3.2/direct_cli/v4/money.py +0 -35
- direct_cli-0.3.2/tests/test_auth_oauth.py +0 -206
- direct_cli-0.3.2/tests/test_transport_contract.py +0 -23
- direct_cli-0.3.2/tests/test_v4finance_money.py +0 -320
- {direct_cli-0.3.2 → direct_cli-0.3.4}/.env.example +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/.github/copilot-instructions.md +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/.github/workflows/api-coverage.yml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/.github/workflows/claude-code-review.yml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/.github/workflows/claude.yml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/.gitignore +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/AGENTS.md +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/CLAUDE.md +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/MANIFEST.in +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/__init__.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_deprecated.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_smoke_probes.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/__init__.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/api.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/cli.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/adextensions.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/adgroups.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/adimages.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/ads.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/advideos.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/agencyclients.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/audiencetargets.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/balance.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/bidmodifiers.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/bids.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/businesses.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/campaigns.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/changes.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/clients.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/creatives.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/dictionaries.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/dynamicads.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/feeds.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/keywordbids.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/keywords.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/retargeting.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/sitelinks.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/smartadtargets.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/strategies.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/turbopages.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/v4account.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/v4events.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/v4goals.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/v4shells.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/commands/vcards.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/output.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/reports_coverage.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/utils.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/v4/__init__.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/wsdl_coverage.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/anonymize_cassettes.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/build_api_coverage_checklist.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/build_api_coverage_report.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/check_reports_drift.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/check_wsdl_drift.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/patch_vendor_imports.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/refresh_reports_cache.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/refresh_wsdl_cache.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/release_pypi.sh +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/test_sandbox_write.sh +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/scripts/update_vendor.sh +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/setup.cfg +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/setup.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/API_ISSUE_AUDIT.md +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/__init__.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/fixtures/test-video.mp4 +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/reports_cache/raw/fields-list.html +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/reports_cache/raw/headers.html +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/reports_cache/raw/period.html +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/reports_cache/raw/spec.html +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/reports_cache/raw/type.html +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/reports_cache/spec.json +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_auth_op.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_balance.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_cli.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_comprehensive.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_integration.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_integration_live_write.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_integration_write.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_reports_drift.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_v4_foundation.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_v4account.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_v4events.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_v4goals.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/test_vendor_imports.py +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/adextensions.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/adgroups.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/adimages.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/ads.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/advideos.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/agencyclients.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/audiencetargets.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/bidmodifiers.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/bids.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/businesses.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/campaigns.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/changes.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/clients.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/creatives.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/dictionaries.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/feeds.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/imports/general.xsd +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/keywordbids.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/keywords.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/keywordsresearch.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/leads.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/retargetinglists.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/sitelinks.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/smartadtargets.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/strategies.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/turbopages.xml +0 -0
- {direct_cli-0.3.2 → direct_cli-0.3.4}/tests/wsdl_cache/vcards.xml +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Quality
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
quality:
|
|
10
|
+
name: quality
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout repository
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
|
|
21
|
+
- name: Install package with dev dependencies
|
|
22
|
+
run: |
|
|
23
|
+
python -m pip install --upgrade pip
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
|
|
26
|
+
- name: Lint
|
|
27
|
+
run: ruff check .
|
|
28
|
+
|
|
29
|
+
- name: Type check
|
|
30
|
+
run: mypy .
|
|
31
|
+
|
|
32
|
+
- name: Run offline tests
|
|
33
|
+
run: pytest -m "not integration"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.3
|
|
4
|
+
|
|
5
|
+
**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.
|
|
6
|
+
|
|
7
|
+
- Added refresh token persistence for OAuth profiles.
|
|
8
|
+
- Added automatic OAuth access token refresh before expiry.
|
|
9
|
+
- Added `expires_in` details to `direct auth status`.
|
|
10
|
+
- Added JSON output for `direct auth status`.
|
|
11
|
+
- Kept `direct auth login --oauth-token` as a manual access-token import without auto-refresh.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: direct-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Command-line interface for Yandex Direct API
|
|
5
5
|
Author: axisrow
|
|
6
6
|
License: MIT
|
|
@@ -33,9 +33,13 @@ Requires-Dist: pytest-recording>=0.13; extra == "dev"
|
|
|
33
33
|
Requires-Dist: vcrpy>=6.0; extra == "dev"
|
|
34
34
|
Requires-Dist: black>=22.0; extra == "dev"
|
|
35
35
|
Requires-Dist: flake8>=4.0; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
36
38
|
Requires-Dist: requests>=2.0; extra == "dev"
|
|
37
39
|
Requires-Dist: beautifulsoup4>=4.12; extra == "dev"
|
|
38
40
|
Requires-Dist: lxml>=4.9; extra == "dev"
|
|
41
|
+
Requires-Dist: types-setuptools; extra == "dev"
|
|
42
|
+
Requires-Dist: types-tabulate; extra == "dev"
|
|
39
43
|
|
|
40
44
|
# Direct CLI
|
|
41
45
|
|
|
@@ -83,7 +87,6 @@ OAuth and profile commands:
|
|
|
83
87
|
direct auth login
|
|
84
88
|
direct auth login --profile agency1
|
|
85
89
|
direct auth login --code abc123 --profile agency1
|
|
86
|
-
direct auth login --oauth-token y0_example --profile agency1
|
|
87
90
|
direct auth list
|
|
88
91
|
direct auth use --profile agency1
|
|
89
92
|
direct auth status --profile agency1
|
|
@@ -95,6 +98,8 @@ Notes:
|
|
|
95
98
|
- Select credentials with `--profile`.
|
|
96
99
|
- `--login` remains Direct client login.
|
|
97
100
|
- Authorization is performed via `direct auth login`.
|
|
101
|
+
- OAuth profiles store refresh tokens and refresh access tokens automatically.
|
|
102
|
+
- `direct auth login --oauth-token TOKEN` is a manual access-token import and does not auto-refresh.
|
|
98
103
|
- Alias `auth_login` is not supported.
|
|
99
104
|
|
|
100
105
|
Credential resolution priority:
|
|
@@ -156,24 +161,35 @@ direct v4events get-events-log --from 2026-04-14T00:00:00 --to 2026-04-15T00:00:
|
|
|
156
161
|
|
|
157
162
|
### V4 Live Finance
|
|
158
163
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
Finance methods require an extra financial token for money operations. In the
|
|
165
|
+
Yandex Direct web UI, open Tools -> API -> Financial operations, enable the
|
|
166
|
+
financial operations checkbox, click Save, then issue the master token on the
|
|
167
|
+
same Financial operations page and confirm by SMS. Direct CLI can compute the
|
|
168
|
+
per-request token from `--master-token`, `--operation-num`, and
|
|
169
|
+
`--finance-login`; alternatively pass a precomputed token with `--finance-token`.
|
|
170
|
+
Environment variables are
|
|
171
|
+
`YANDEX_DIRECT_MASTER_TOKEN`, `YANDEX_DIRECT_FINANCE_LOGIN`,
|
|
172
|
+
`YANDEX_DIRECT_FINANCE_TOKEN`, and `YANDEX_DIRECT_OPERATION_NUM`.
|
|
173
|
+
`transfer-money` and `pay-campaigns` are dry-run-only in this release and
|
|
174
|
+
always require `--dry-run`; `create-invoice` can be sent live when `--dry-run`
|
|
175
|
+
is omitted. Dry-run output masks the financial token.
|
|
164
176
|
|
|
165
177
|
```bash
|
|
166
|
-
direct v4finance get-
|
|
178
|
+
direct v4finance get-clients-units --logins client-login,other-client --format table
|
|
179
|
+
direct v4finance get-credit-limits --logins client-login --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login
|
|
167
180
|
direct v4finance get-credit-limits --logins client-login,other-client --format table
|
|
181
|
+
direct v4finance create-invoice --payment 123=100.50 --payment 456=25 --currency RUB --master-token MASTER_TOKEN --operation-num 124 --finance-login agency-login --dry-run
|
|
168
182
|
direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
|
|
169
|
-
direct v4finance transfer-money --from-campaign-id 123 --to-campaign-id 456 --amount 100.50 --
|
|
170
|
-
direct v4finance pay-campaigns --campaign-
|
|
183
|
+
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
|
|
184
|
+
direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --currency RUB --contract-id CONTRACT_ID --pay-method Bank --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
|
|
171
185
|
```
|
|
172
186
|
|
|
173
187
|
### V4 Live Shared Account
|
|
174
188
|
|
|
175
189
|
Shared-account mutations are dry-run-only in this release and always require
|
|
176
|
-
`--dry-run`.
|
|
190
|
+
`--dry-run`. These commands follow the official v4 Live shared-account method
|
|
191
|
+
shapes: `EnableSharedAccount` accepts one client `Login`, and
|
|
192
|
+
`AccountManagement` updates shared-account settings through `Accounts`.
|
|
177
193
|
|
|
178
194
|
```bash
|
|
179
195
|
direct v4account enable-shared-account --client-login client-login --dry-run
|
|
@@ -583,7 +599,7 @@ CI runs a scheduled API coverage workflow that:
|
|
|
583
599
|
|
|
584
600
|
`WRITE_SANDBOX` smoke is a live check against the Yandex Direct **sandbox**.
|
|
585
601
|
It does not replay stored HTTP traffic and it does not create new recordings.
|
|
586
|
-
Run it only when you intentionally want to call `api-sandbox.direct.yandex.
|
|
602
|
+
Run it only when you intentionally want to call `api-sandbox.direct.yandex.ru`:
|
|
587
603
|
|
|
588
604
|
```bash
|
|
589
605
|
set -a && source .env && set +a
|
|
@@ -725,13 +741,16 @@ OAuth и profile-команды:
|
|
|
725
741
|
direct auth login
|
|
726
742
|
direct auth login --profile agency1
|
|
727
743
|
direct auth login --code abc123 --profile agency1
|
|
728
|
-
direct auth login --oauth-token y0_example --profile agency1
|
|
729
744
|
direct auth list
|
|
730
745
|
direct auth use --profile agency1
|
|
731
746
|
direct auth status --profile agency1
|
|
732
747
|
direct --profile agency1 campaigns get
|
|
733
748
|
```
|
|
734
749
|
|
|
750
|
+
Примечания:
|
|
751
|
+
- OAuth profiles сохраняют refresh token и автоматически обновляют access token.
|
|
752
|
+
- `direct auth login --oauth-token TOKEN` импортирует access token вручную и не включает auto-refresh.
|
|
753
|
+
|
|
735
754
|
Порядок выбора credentials:
|
|
736
755
|
|
|
737
756
|
| Приоритет | Источник | Пример |
|
|
@@ -1176,7 +1195,7 @@ YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rew
|
|
|
1176
1195
|
`WRITE_SANDBOX` smoke — это live-проверка против **sandbox-окружения**
|
|
1177
1196
|
Яндекс Директа. Она не воспроизводит сохранённый HTTP-трафик и не создаёт
|
|
1178
1197
|
новые записи. Запускайте её только когда намеренно хотите обратиться к
|
|
1179
|
-
`api-sandbox.direct.yandex.
|
|
1198
|
+
`api-sandbox.direct.yandex.ru`:
|
|
1180
1199
|
|
|
1181
1200
|
```bash
|
|
1182
1201
|
set -a && source .env && set +a
|
|
@@ -44,7 +44,6 @@ OAuth and profile commands:
|
|
|
44
44
|
direct auth login
|
|
45
45
|
direct auth login --profile agency1
|
|
46
46
|
direct auth login --code abc123 --profile agency1
|
|
47
|
-
direct auth login --oauth-token y0_example --profile agency1
|
|
48
47
|
direct auth list
|
|
49
48
|
direct auth use --profile agency1
|
|
50
49
|
direct auth status --profile agency1
|
|
@@ -56,6 +55,8 @@ Notes:
|
|
|
56
55
|
- Select credentials with `--profile`.
|
|
57
56
|
- `--login` remains Direct client login.
|
|
58
57
|
- Authorization is performed via `direct auth login`.
|
|
58
|
+
- OAuth profiles store refresh tokens and refresh access tokens automatically.
|
|
59
|
+
- `direct auth login --oauth-token TOKEN` is a manual access-token import and does not auto-refresh.
|
|
59
60
|
- Alias `auth_login` is not supported.
|
|
60
61
|
|
|
61
62
|
Credential resolution priority:
|
|
@@ -117,24 +118,35 @@ direct v4events get-events-log --from 2026-04-14T00:00:00 --to 2026-04-15T00:00:
|
|
|
117
118
|
|
|
118
119
|
### V4 Live Finance
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
Finance methods require an extra financial token for money operations. In the
|
|
122
|
+
Yandex Direct web UI, open Tools -> API -> Financial operations, enable the
|
|
123
|
+
financial operations checkbox, click Save, then issue the master token on the
|
|
124
|
+
same Financial operations page and confirm by SMS. Direct CLI can compute the
|
|
125
|
+
per-request token from `--master-token`, `--operation-num`, and
|
|
126
|
+
`--finance-login`; alternatively pass a precomputed token with `--finance-token`.
|
|
127
|
+
Environment variables are
|
|
128
|
+
`YANDEX_DIRECT_MASTER_TOKEN`, `YANDEX_DIRECT_FINANCE_LOGIN`,
|
|
129
|
+
`YANDEX_DIRECT_FINANCE_TOKEN`, and `YANDEX_DIRECT_OPERATION_NUM`.
|
|
130
|
+
`transfer-money` and `pay-campaigns` are dry-run-only in this release and
|
|
131
|
+
always require `--dry-run`; `create-invoice` can be sent live when `--dry-run`
|
|
132
|
+
is omitted. Dry-run output masks the financial token.
|
|
125
133
|
|
|
126
134
|
```bash
|
|
127
|
-
direct v4finance get-
|
|
135
|
+
direct v4finance get-clients-units --logins client-login,other-client --format table
|
|
136
|
+
direct v4finance get-credit-limits --logins client-login --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login
|
|
128
137
|
direct v4finance get-credit-limits --logins client-login,other-client --format table
|
|
138
|
+
direct v4finance create-invoice --payment 123=100.50 --payment 456=25 --currency RUB --master-token MASTER_TOKEN --operation-num 124 --finance-login agency-login --dry-run
|
|
129
139
|
direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
|
|
130
|
-
direct v4finance transfer-money --from-campaign-id 123 --to-campaign-id 456 --amount 100.50 --
|
|
131
|
-
direct v4finance pay-campaigns --campaign-
|
|
140
|
+
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
|
|
141
|
+
direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --currency RUB --contract-id CONTRACT_ID --pay-method Bank --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login --dry-run
|
|
132
142
|
```
|
|
133
143
|
|
|
134
144
|
### V4 Live Shared Account
|
|
135
145
|
|
|
136
146
|
Shared-account mutations are dry-run-only in this release and always require
|
|
137
|
-
`--dry-run`.
|
|
147
|
+
`--dry-run`. These commands follow the official v4 Live shared-account method
|
|
148
|
+
shapes: `EnableSharedAccount` accepts one client `Login`, and
|
|
149
|
+
`AccountManagement` updates shared-account settings through `Accounts`.
|
|
138
150
|
|
|
139
151
|
```bash
|
|
140
152
|
direct v4account enable-shared-account --client-login client-login --dry-run
|
|
@@ -544,7 +556,7 @@ CI runs a scheduled API coverage workflow that:
|
|
|
544
556
|
|
|
545
557
|
`WRITE_SANDBOX` smoke is a live check against the Yandex Direct **sandbox**.
|
|
546
558
|
It does not replay stored HTTP traffic and it does not create new recordings.
|
|
547
|
-
Run it only when you intentionally want to call `api-sandbox.direct.yandex.
|
|
559
|
+
Run it only when you intentionally want to call `api-sandbox.direct.yandex.ru`:
|
|
548
560
|
|
|
549
561
|
```bash
|
|
550
562
|
set -a && source .env && set +a
|
|
@@ -686,13 +698,16 @@ OAuth и profile-команды:
|
|
|
686
698
|
direct auth login
|
|
687
699
|
direct auth login --profile agency1
|
|
688
700
|
direct auth login --code abc123 --profile agency1
|
|
689
|
-
direct auth login --oauth-token y0_example --profile agency1
|
|
690
701
|
direct auth list
|
|
691
702
|
direct auth use --profile agency1
|
|
692
703
|
direct auth status --profile agency1
|
|
693
704
|
direct --profile agency1 campaigns get
|
|
694
705
|
```
|
|
695
706
|
|
|
707
|
+
Примечания:
|
|
708
|
+
- OAuth profiles сохраняют refresh token и автоматически обновляют access token.
|
|
709
|
+
- `direct auth login --oauth-token TOKEN` импортирует access token вручную и не включает auto-refresh.
|
|
710
|
+
|
|
696
711
|
Порядок выбора credentials:
|
|
697
712
|
|
|
698
713
|
| Приоритет | Источник | Пример |
|
|
@@ -1137,7 +1152,7 @@ YANDEX_DIRECT_LIVE_WRITE=1 pytest -m integration_live_write -v --record-mode=rew
|
|
|
1137
1152
|
`WRITE_SANDBOX` smoke — это live-проверка против **sandbox-окружения**
|
|
1138
1153
|
Яндекс Директа. Она не воспроизводит сохранённый HTTP-трафик и не создаёт
|
|
1139
1154
|
новые записи. Запускайте её только когда намеренно хотите обратиться к
|
|
1140
|
-
`api-sandbox.direct.yandex.
|
|
1155
|
+
`api-sandbox.direct.yandex.ru`:
|
|
1141
1156
|
|
|
1142
1157
|
```bash
|
|
1143
1158
|
set -a && source .env && set +a
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Runtime endpoints for Yandex Direct API transports."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
DIRECT_API_PRODUCTION_ROOT = "https://api.direct.yandex.ru/"
|
|
6
|
+
DIRECT_API_SANDBOX_ROOT = "https://api-sandbox.direct.yandex.ru/"
|
|
7
|
+
DIRECT_DEBUG_ROOT = "https://"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_direct_api_root(api_params: Dict[str, Any]) -> str:
|
|
11
|
+
"""Return the Direct API root for production or sandbox requests."""
|
|
12
|
+
if api_params.get("is_sandbox"):
|
|
13
|
+
return DIRECT_API_SANDBOX_ROOT
|
|
14
|
+
return DIRECT_API_PRODUCTION_ROOT
|
{direct_cli-0.3.2 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import logging
|
|
3
|
+
import re
|
|
3
4
|
import time
|
|
4
5
|
from typing import Union, Optional, Dict, List, Iterator
|
|
5
6
|
|
|
@@ -9,6 +10,7 @@ from tapi2 import TapiAdapter, generate_wrapper_from_adapter, JSONAdapterMixin
|
|
|
9
10
|
from tapi2.exceptions import ResponseProcessException, ClientError, TapiException
|
|
10
11
|
|
|
11
12
|
from . import exceptions
|
|
13
|
+
from .endpoints import DIRECT_DEBUG_ROOT, get_direct_api_root
|
|
12
14
|
from .resource_mapping import RESOURCE_MAPPING_V5
|
|
13
15
|
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
@@ -62,6 +64,7 @@ RESULT_DICTIONARY_KEYS_OF_API_METHODS = {
|
|
|
62
64
|
},
|
|
63
65
|
}
|
|
64
66
|
REPORTS_RESOURCE_URL = "/json/v5/reports"
|
|
67
|
+
REPORT_SUMMARY_RE = re.compile(r"^Total rows: \d+$")
|
|
65
68
|
|
|
66
69
|
|
|
67
70
|
class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
@@ -72,11 +75,8 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
72
75
|
|
|
73
76
|
def get_api_root(self, api_params: dict, resource_name: str) -> str:
|
|
74
77
|
if resource_name == "debugtoken":
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
return "https://api-sandbox.direct.yandex.com/"
|
|
78
|
-
else:
|
|
79
|
-
return "https://api.direct.yandex.com/"
|
|
78
|
+
return DIRECT_DEBUG_ROOT
|
|
79
|
+
return get_direct_api_root(api_params)
|
|
80
80
|
|
|
81
81
|
def get_request_kwargs(self, api_params: dict, *args, **kwargs) -> dict:
|
|
82
82
|
"""Обогащение запроса, параметрами"""
|
|
@@ -154,7 +154,7 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
154
154
|
"The report generation time has exceeded the server limit. "
|
|
155
155
|
"Please try to change the request parameters, "
|
|
156
156
|
"reduce the period or the amount of requested data.",
|
|
157
|
-
**kwargs
|
|
157
|
+
**kwargs,
|
|
158
158
|
)
|
|
159
159
|
elif response.status_code == 405:
|
|
160
160
|
raise exceptions.YandexDirectApiError(
|
|
@@ -162,7 +162,7 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
162
162
|
"This resource does not support the HTTP method {}\n".format(
|
|
163
163
|
response.request.method
|
|
164
164
|
),
|
|
165
|
-
**kwargs
|
|
165
|
+
**kwargs,
|
|
166
166
|
)
|
|
167
167
|
|
|
168
168
|
data = self.response_to_native(response)
|
|
@@ -175,10 +175,15 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
175
175
|
data = super().process_response(response, request_kwargs, **kwargs)
|
|
176
176
|
|
|
177
177
|
if response.request.path_url == REPORTS_RESOURCE_URL:
|
|
178
|
-
|
|
179
|
-
kwargs["store"]["
|
|
178
|
+
self._store_report_columns(data, response, request_kwargs, **kwargs)
|
|
179
|
+
kwargs["store"]["skip_report_summary"] = self._is_skip_report_summary(
|
|
180
|
+
request_kwargs
|
|
181
|
+
)
|
|
180
182
|
else:
|
|
181
183
|
kwargs["store"].pop("columns", None)
|
|
184
|
+
kwargs["store"].pop("report_data_start_line", None)
|
|
185
|
+
kwargs["store"].pop("report_header_offset", None)
|
|
186
|
+
kwargs["store"].pop("skip_report_summary", None)
|
|
182
187
|
|
|
183
188
|
return data
|
|
184
189
|
|
|
@@ -190,7 +195,7 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
190
195
|
response: Response,
|
|
191
196
|
request_kwargs: dict,
|
|
192
197
|
api_params: dict,
|
|
193
|
-
**kwargs
|
|
198
|
+
**kwargs,
|
|
194
199
|
) -> None:
|
|
195
200
|
if response.status_code in (201, 202):
|
|
196
201
|
pass
|
|
@@ -230,7 +235,7 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
230
235
|
response: Response,
|
|
231
236
|
request_kwargs: dict,
|
|
232
237
|
api_params: dict,
|
|
233
|
-
**kwargs
|
|
238
|
+
**kwargs,
|
|
234
239
|
) -> bool:
|
|
235
240
|
status_code = response.status_code
|
|
236
241
|
error_data = error_message.get("error", {})
|
|
@@ -283,7 +288,7 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
283
288
|
response: Response,
|
|
284
289
|
request_kwargs: dict,
|
|
285
290
|
api_params: dict,
|
|
286
|
-
**kwargs
|
|
291
|
+
**kwargs,
|
|
287
292
|
) -> Optional[dict]:
|
|
288
293
|
limit = response_data["result"].get("LimitedBy")
|
|
289
294
|
if limit:
|
|
@@ -308,22 +313,109 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
308
313
|
raise NotImplementedError("For reports resource only")
|
|
309
314
|
|
|
310
315
|
lines = io.StringIO(data)
|
|
311
|
-
iterator = (line.
|
|
316
|
+
iterator = (line.rstrip("\r\n") for line in lines)
|
|
312
317
|
|
|
313
318
|
return iterator
|
|
314
319
|
|
|
320
|
+
def _store_report_columns(
|
|
321
|
+
self,
|
|
322
|
+
data: str,
|
|
323
|
+
response: Response,
|
|
324
|
+
request_kwargs: dict,
|
|
325
|
+
**kwargs,
|
|
326
|
+
) -> None:
|
|
327
|
+
field_names = self._get_report_field_names(request_kwargs)
|
|
328
|
+
if self._is_skip_column_header(request_kwargs):
|
|
329
|
+
if not field_names:
|
|
330
|
+
raise ValueError("Report response has no column header or FieldNames")
|
|
331
|
+
data_start_line = self._find_first_report_data_line(
|
|
332
|
+
data, response, **kwargs
|
|
333
|
+
)
|
|
334
|
+
kwargs["store"]["columns"] = field_names
|
|
335
|
+
kwargs["store"]["report_data_start_line"] = data_start_line
|
|
336
|
+
kwargs["store"]["report_header_offset"] = data_start_line
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
for index, line in enumerate(
|
|
340
|
+
self._iter_lines(data=data, response=response, **kwargs)
|
|
341
|
+
):
|
|
342
|
+
if self._is_report_prelude_line(line):
|
|
343
|
+
continue
|
|
344
|
+
values = line.split("\t")
|
|
345
|
+
if field_names and values == field_names:
|
|
346
|
+
kwargs["store"]["columns"] = values
|
|
347
|
+
kwargs["store"]["report_data_start_line"] = index + 1
|
|
348
|
+
kwargs["store"]["report_header_offset"] = index
|
|
349
|
+
return
|
|
350
|
+
if "\t" in line:
|
|
351
|
+
kwargs["store"]["columns"] = values
|
|
352
|
+
kwargs["store"]["report_data_start_line"] = index + 1
|
|
353
|
+
kwargs["store"]["report_header_offset"] = index
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
raise ValueError("Report response has no column header")
|
|
357
|
+
|
|
358
|
+
def _find_first_report_data_line(
|
|
359
|
+
self, data: str, response: Response, **kwargs
|
|
360
|
+
) -> int:
|
|
361
|
+
for index, line in enumerate(
|
|
362
|
+
self._iter_lines(data=data, response=response, **kwargs)
|
|
363
|
+
):
|
|
364
|
+
if self._is_report_prelude_line(line) or self._is_report_summary_line(line):
|
|
365
|
+
continue
|
|
366
|
+
return index
|
|
367
|
+
return index + 1 if "index" in locals() else 0
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def _get_report_field_names(request_kwargs: dict) -> List[str]:
|
|
371
|
+
params = request_kwargs.get("data", {}).get("params", {})
|
|
372
|
+
return list(params.get("FieldNames") or [])
|
|
373
|
+
|
|
374
|
+
@staticmethod
|
|
375
|
+
def _is_skip_column_header(request_kwargs: dict) -> bool:
|
|
376
|
+
value = request_kwargs.get("headers", {}).get("skipColumnHeader")
|
|
377
|
+
return str(value).lower() == "true"
|
|
378
|
+
|
|
379
|
+
@staticmethod
|
|
380
|
+
def _is_skip_report_summary(request_kwargs: dict) -> bool:
|
|
381
|
+
value = request_kwargs.get("headers", {}).get("skipReportSummary", True)
|
|
382
|
+
return str(value).lower() == "true"
|
|
383
|
+
|
|
384
|
+
@staticmethod
|
|
385
|
+
def _is_report_title_line(line: str) -> bool:
|
|
386
|
+
return "\t" not in line and line.strip().startswith('"')
|
|
387
|
+
|
|
388
|
+
@staticmethod
|
|
389
|
+
def _is_report_summary_line(line: str) -> bool:
|
|
390
|
+
return bool(REPORT_SUMMARY_RE.match(line.strip()))
|
|
391
|
+
|
|
392
|
+
def _is_report_prelude_line(self, line: str) -> bool:
|
|
393
|
+
return not line.strip() or self._is_report_title_line(line)
|
|
394
|
+
|
|
315
395
|
def iter_lines(self, **kwargs) -> Iterator[str]:
|
|
316
396
|
iterator = self._iter_lines(**kwargs)
|
|
317
|
-
|
|
318
|
-
|
|
397
|
+
data_start_line = kwargs["store"].get("report_data_start_line", 1)
|
|
398
|
+
skip_report_summary = kwargs["store"].get("skip_report_summary", True)
|
|
399
|
+
for index, line in enumerate(iterator):
|
|
400
|
+
if index < data_start_line:
|
|
401
|
+
continue
|
|
402
|
+
if not line.strip():
|
|
403
|
+
continue
|
|
404
|
+
if skip_report_summary and self._is_report_summary_line(line):
|
|
405
|
+
continue
|
|
406
|
+
yield line
|
|
319
407
|
|
|
320
408
|
def iter_values(self, **kwargs) -> Iterator[list]:
|
|
321
409
|
for line in self.iter_lines(**kwargs):
|
|
322
|
-
|
|
410
|
+
values = line.split("\t")
|
|
411
|
+
if self._is_report_summary_line(line):
|
|
412
|
+
columns_count = len(kwargs["store"]["columns"])
|
|
413
|
+
values.extend([""] * (columns_count - len(values)))
|
|
414
|
+
yield values
|
|
323
415
|
|
|
324
416
|
def iter_dicts(self, **kwargs) -> Iterator[dict]:
|
|
325
|
-
for
|
|
326
|
-
yield dict(zip(kwargs["store"]["columns"],
|
|
417
|
+
for values in self.iter_values(**kwargs):
|
|
418
|
+
yield dict(zip(kwargs["store"]["columns"], values))
|
|
327
419
|
|
|
328
420
|
def to_values(self, **kwargs) -> List[list]:
|
|
329
421
|
return list(self.iter_values(**kwargs))
|
|
@@ -18,6 +18,7 @@ from tapi2 import JSONAdapterMixin, TapiAdapter, generate_wrapper_from_adapter
|
|
|
18
18
|
from tapi2.exceptions import ClientError, ResponseProcessException, TapiException
|
|
19
19
|
|
|
20
20
|
from .. import exceptions
|
|
21
|
+
from ..endpoints import get_direct_api_root
|
|
21
22
|
from .resource_mapping import (
|
|
22
23
|
RESOURCE_MAPPING_V4_LIVE,
|
|
23
24
|
SUPPORTED_V4_METHODS,
|
|
@@ -33,9 +34,7 @@ class V4LiveClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
33
34
|
super().__init__(*args, **kwargs)
|
|
34
35
|
|
|
35
36
|
def get_api_root(self, api_params: dict, resource_name: str) -> str:
|
|
36
|
-
|
|
37
|
-
return "https://api-sandbox.direct.yandex.ru/"
|
|
38
|
-
return "https://api.direct.yandex.ru/"
|
|
37
|
+
return get_direct_api_root(api_params)
|
|
39
38
|
|
|
40
39
|
def get_request_kwargs(self, api_params: dict, *args, **kwargs) -> dict:
|
|
41
40
|
params = super().get_request_kwargs(api_params, *args, **kwargs)
|
|
@@ -107,7 +106,9 @@ class V4LiveClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
107
106
|
data = None
|
|
108
107
|
return data
|
|
109
108
|
|
|
110
|
-
def process_response(
|
|
109
|
+
def process_response(
|
|
110
|
+
self, response: Response, request_kwargs: dict, **kwargs
|
|
111
|
+
) -> dict:
|
|
111
112
|
# Mirror the v5 behaviour: turn the serialised body back into a dict so
|
|
112
113
|
# downstream hooks (extract, retry) can read it.
|
|
113
114
|
if isinstance(request_kwargs.get("data"), (bytes, bytearray, str)):
|
|
@@ -187,8 +188,13 @@ class V4LiveClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
187
188
|
|
|
188
189
|
return False
|
|
189
190
|
|
|
190
|
-
def extract(
|
|
191
|
-
|
|
191
|
+
def extract(
|
|
192
|
+
self,
|
|
193
|
+
data,
|
|
194
|
+
response: Optional[Response] = None,
|
|
195
|
+
request_kwargs: Optional[dict] = None,
|
|
196
|
+
**kwargs,
|
|
197
|
+
):
|
|
192
198
|
# v4 Live always nests payload under "data". For methods returning a
|
|
193
199
|
# bare scalar (TransferMoney → 1), the scalar comes through unchanged.
|
|
194
200
|
# response / request_kwargs are accepted but unused — they are kept
|