direct-cli 0.3.3__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.3 → direct_cli-0.3.4}/PKG-INFO +14 -5
- {direct_cli-0.3.3 → direct_cli-0.3.4}/README.md +9 -4
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +102 -8
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/reports.py +6 -8
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/v4finance.py +169 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/smoke_matrix.py +2 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/v4_contracts.py +15 -4
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli.egg-info/PKG-INFO +14 -5
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli.egg-info/SOURCES.txt +2 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli.egg-info/requires.txt +4 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/pyproject.toml +27 -1
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/test_safe_commands.sh +12 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/API_COVERAGE.md +4 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/MANUAL_COVERAGE.md +4 -3
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/api_coverage_payloads.py +2 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_api_coverage.py +115 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_auth_oauth.py +46 -0
- {direct_cli-0.3.3 → 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.3 → direct_cli-0.3.4}/tests/test_smoke_matrix.py +3 -2
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4_contracts.py +13 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4_live_contracts.py +36 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4_safety.py +3 -1
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4finance_money.py +178 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4finance_read.py +59 -4
- {direct_cli-0.3.3 → direct_cli-0.3.4}/.env.example +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/.github/copilot-instructions.md +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/.github/workflows/api-coverage.yml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/.github/workflows/claude-code-review.yml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/.github/workflows/claude.yml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/.gitignore +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/AGENTS.md +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/CHANGELOG.md +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/CLAUDE.md +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/MANIFEST.in +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/__init__.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_deprecated.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_smoke_probes.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/__init__.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/api.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/auth.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/cli.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/adextensions.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/adgroups.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/adimages.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/ads.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/advideos.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/agencyclients.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/audiencetargets.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/auth.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/balance.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/bidmodifiers.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/bids.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/businesses.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/campaigns.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/changes.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/clients.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/creatives.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/dictionaries.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/dynamicads.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/dynamicfeedadtargets.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/feeds.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/keywordbids.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/keywords.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/negativekeywordsharedsets.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/retargeting.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/sitelinks.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/smartadtargets.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/strategies.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/turbopages.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/v4account.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/v4events.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/v4goals.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/v4shells.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/commands/vcards.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/output.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/reports_coverage.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/utils.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/v4/__init__.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/v4/money.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli/wsdl_coverage.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/anonymize_cassettes.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/build_api_coverage_checklist.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/build_api_coverage_report.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/check_reports_drift.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/check_wsdl_drift.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/patch_vendor_imports.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/refresh_reports_cache.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/refresh_wsdl_cache.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/release_pypi.sh +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/sandbox_write_live.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/test_dangerous_commands.sh +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/test_sandbox_write.sh +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/scripts/update_vendor.sh +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/setup.cfg +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/setup.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/API_ISSUE_AUDIT.md +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/__init__.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_adgroups_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_adimages_add_get_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_ads_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_advideos_add_get.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_add_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_audiencetargets_suspend_resume.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_bids_set.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_campaign_create_get_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_add_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_dynamicads_suspend_resume.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_keywordbids_set.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_keywords_suspend_resume.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_sitelinks_add_get_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_live_write/test_live_draft_smartadtargets_suspend_resume.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/conftest.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/fixtures/test-video.mp4 +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/reports_cache/raw/fields-list.html +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/reports_cache/raw/headers.html +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/reports_cache/raw/period.html +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/reports_cache/raw/spec.html +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/reports_cache/raw/type.html +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/reports_cache/spec.json +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_auth_op.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_balance.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_cli.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_comprehensive.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_integration.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_integration_live_write.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_integration_write.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_reports_drift.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_transport_contract.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4_foundation.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4account.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4events.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_v4goals.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/test_vendor_imports.py +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/adextensions.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/adgroups.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/adimages.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/ads.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/advideos.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/agencyclients.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/audiencetargets.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/bidmodifiers.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/bids.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/businesses.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/campaigns.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/changes.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/clients.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/creatives.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/dictionaries.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/feeds.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/imports/general.xsd +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/keywordbids.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/keywords.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/keywordsresearch.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/leads.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/retargetinglists.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/sitelinks.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/smartadtargets.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/strategies.xml +0 -0
- {direct_cli-0.3.3 → direct_cli-0.3.4}/tests/wsdl_cache/turbopages.xml +0 -0
- {direct_cli-0.3.3 → 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"
|
|
@@ -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
|
|
|
@@ -165,13 +169,16 @@ per-request token from `--master-token`, `--operation-num`, and
|
|
|
165
169
|
`--finance-login`; alternatively pass a precomputed token with `--finance-token`.
|
|
166
170
|
Environment variables are
|
|
167
171
|
`YANDEX_DIRECT_MASTER_TOKEN`, `YANDEX_DIRECT_FINANCE_LOGIN`,
|
|
168
|
-
`YANDEX_DIRECT_FINANCE_TOKEN`, and `YANDEX_DIRECT_OPERATION_NUM`.
|
|
169
|
-
|
|
170
|
-
|
|
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.
|
|
171
176
|
|
|
172
177
|
```bash
|
|
178
|
+
direct v4finance get-clients-units --logins client-login,other-client --format table
|
|
173
179
|
direct v4finance get-credit-limits --logins client-login --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login
|
|
174
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
|
|
175
182
|
direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
|
|
176
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
|
|
177
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
|
|
@@ -180,7 +187,9 @@ direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --currency
|
|
|
180
187
|
### V4 Live Shared Account
|
|
181
188
|
|
|
182
189
|
Shared-account mutations are dry-run-only in this release and always require
|
|
183
|
-
`--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`.
|
|
184
193
|
|
|
185
194
|
```bash
|
|
186
195
|
direct v4account enable-shared-account --client-login client-login --dry-run
|
|
@@ -126,13 +126,16 @@ per-request token from `--master-token`, `--operation-num`, and
|
|
|
126
126
|
`--finance-login`; alternatively pass a precomputed token with `--finance-token`.
|
|
127
127
|
Environment variables are
|
|
128
128
|
`YANDEX_DIRECT_MASTER_TOKEN`, `YANDEX_DIRECT_FINANCE_LOGIN`,
|
|
129
|
-
`YANDEX_DIRECT_FINANCE_TOKEN`, and `YANDEX_DIRECT_OPERATION_NUM`.
|
|
130
|
-
|
|
131
|
-
|
|
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.
|
|
132
133
|
|
|
133
134
|
```bash
|
|
135
|
+
direct v4finance get-clients-units --logins client-login,other-client --format table
|
|
134
136
|
direct v4finance get-credit-limits --logins client-login --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login
|
|
135
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
|
|
136
139
|
direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
|
|
137
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
|
|
138
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
|
|
@@ -141,7 +144,9 @@ direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --currency
|
|
|
141
144
|
### V4 Live Shared Account
|
|
142
145
|
|
|
143
146
|
Shared-account mutations are dry-run-only in this release and always require
|
|
144
|
-
`--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`.
|
|
145
150
|
|
|
146
151
|
```bash
|
|
147
152
|
direct v4account enable-shared-account --client-login client-login --dry-run
|
{direct_cli-0.3.3 → 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
|
|
|
@@ -63,6 +64,7 @@ RESULT_DICTIONARY_KEYS_OF_API_METHODS = {
|
|
|
63
64
|
},
|
|
64
65
|
}
|
|
65
66
|
REPORTS_RESOURCE_URL = "/json/v5/reports"
|
|
67
|
+
REPORT_SUMMARY_RE = re.compile(r"^Total rows: \d+$")
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
@@ -173,10 +175,15 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
173
175
|
data = super().process_response(response, request_kwargs, **kwargs)
|
|
174
176
|
|
|
175
177
|
if response.request.path_url == REPORTS_RESOURCE_URL:
|
|
176
|
-
|
|
177
|
-
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
|
+
)
|
|
178
182
|
else:
|
|
179
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)
|
|
180
187
|
|
|
181
188
|
return data
|
|
182
189
|
|
|
@@ -306,22 +313,109 @@ class YandexDirectClientAdapter(JSONAdapterMixin, TapiAdapter):
|
|
|
306
313
|
raise NotImplementedError("For reports resource only")
|
|
307
314
|
|
|
308
315
|
lines = io.StringIO(data)
|
|
309
|
-
iterator = (line.
|
|
316
|
+
iterator = (line.rstrip("\r\n") for line in lines)
|
|
310
317
|
|
|
311
318
|
return iterator
|
|
312
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
|
+
|
|
313
395
|
def iter_lines(self, **kwargs) -> Iterator[str]:
|
|
314
396
|
iterator = self._iter_lines(**kwargs)
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
317
407
|
|
|
318
408
|
def iter_values(self, **kwargs) -> Iterator[list]:
|
|
319
409
|
for line in self.iter_lines(**kwargs):
|
|
320
|
-
|
|
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
|
|
321
415
|
|
|
322
416
|
def iter_dicts(self, **kwargs) -> Iterator[dict]:
|
|
323
|
-
for
|
|
324
|
-
yield dict(zip(kwargs["store"]["columns"],
|
|
417
|
+
for values in self.iter_values(**kwargs):
|
|
418
|
+
yield dict(zip(kwargs["store"]["columns"], values))
|
|
325
419
|
|
|
326
420
|
def to_values(self, **kwargs) -> List[list]:
|
|
327
421
|
return list(self.iter_values(**kwargs))
|
|
@@ -323,10 +323,9 @@ def reports():
|
|
|
323
323
|
help="Processing mode: auto, online, offline",
|
|
324
324
|
)
|
|
325
325
|
@click.option(
|
|
326
|
-
"--skip-report-header",
|
|
327
|
-
|
|
328
|
-
default
|
|
329
|
-
help="Omit report header row",
|
|
326
|
+
"--skip-report-header/--no-skip-report-header",
|
|
327
|
+
default=True,
|
|
328
|
+
help="Omit report header row (default: yes)",
|
|
330
329
|
)
|
|
331
330
|
@click.option(
|
|
332
331
|
"--skip-column-header",
|
|
@@ -335,10 +334,9 @@ def reports():
|
|
|
335
334
|
help="Omit column header row",
|
|
336
335
|
)
|
|
337
336
|
@click.option(
|
|
338
|
-
"--skip-report-summary",
|
|
339
|
-
|
|
340
|
-
default
|
|
341
|
-
help="Omit report summary row",
|
|
337
|
+
"--skip-report-summary/--no-skip-report-summary",
|
|
338
|
+
default=True,
|
|
339
|
+
help="Omit report summary row (default: yes)",
|
|
342
340
|
)
|
|
343
341
|
@click.option(
|
|
344
342
|
"--return-money-in-micros",
|
|
@@ -34,6 +34,43 @@ def _logins_param(logins: str) -> list[str]:
|
|
|
34
34
|
return login_list
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def _invoice_payments_param(payments: tuple[str, ...], currency: str) -> dict:
|
|
38
|
+
"""Build the v4 Live CreateInvoice payment object parameter."""
|
|
39
|
+
if not payments:
|
|
40
|
+
raise click.UsageError("--payment is required")
|
|
41
|
+
|
|
42
|
+
parsed_payments = []
|
|
43
|
+
seen_campaign_ids = set()
|
|
44
|
+
normalized_currency = currency.upper()
|
|
45
|
+
for payment in payments:
|
|
46
|
+
spec = (payment or "").strip()
|
|
47
|
+
if "=" not in spec:
|
|
48
|
+
raise click.UsageError("--payment must use CAMPAIGN_ID=AMOUNT")
|
|
49
|
+
campaign_id_text, amount_text = spec.split("=", 1)
|
|
50
|
+
campaign_id_text = campaign_id_text.strip()
|
|
51
|
+
amount_text = amount_text.strip()
|
|
52
|
+
try:
|
|
53
|
+
campaign_id = int(campaign_id_text)
|
|
54
|
+
except ValueError as exc:
|
|
55
|
+
raise click.UsageError(
|
|
56
|
+
"--payment campaign ID must be a positive integer"
|
|
57
|
+
) from exc
|
|
58
|
+
if campaign_id <= 0:
|
|
59
|
+
raise click.UsageError("--payment campaign ID must be a positive integer")
|
|
60
|
+
if campaign_id in seen_campaign_ids:
|
|
61
|
+
raise click.UsageError("--payment campaign IDs must be unique")
|
|
62
|
+
seen_campaign_ids.add(campaign_id)
|
|
63
|
+
parsed_payments.append(
|
|
64
|
+
{
|
|
65
|
+
"CampaignID": campaign_id,
|
|
66
|
+
"Sum": parse_v4_money_sum(amount_text),
|
|
67
|
+
"Currency": normalized_currency,
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return {"Payments": parsed_payments}
|
|
72
|
+
|
|
73
|
+
|
|
37
74
|
def _finance_credentials(
|
|
38
75
|
finance_token: Optional[str],
|
|
39
76
|
master_token: Optional[str],
|
|
@@ -122,6 +159,41 @@ def v4finance():
|
|
|
122
159
|
"""Yandex Direct v4 Live finance commands."""
|
|
123
160
|
|
|
124
161
|
|
|
162
|
+
@v4_method_contract("GetClientsUnits")
|
|
163
|
+
@v4finance.command(name="get-clients-units")
|
|
164
|
+
@click.option("--logins", required=True, help="Comma-separated client logins")
|
|
165
|
+
@click.option(
|
|
166
|
+
"--format",
|
|
167
|
+
"output_format",
|
|
168
|
+
default="json",
|
|
169
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
170
|
+
help="Output format",
|
|
171
|
+
)
|
|
172
|
+
@click.option("--output", help="Output file")
|
|
173
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
174
|
+
@click.pass_context
|
|
175
|
+
def get_clients_units(ctx, logins, output_format, output, dry_run):
|
|
176
|
+
"""Get available API units for clients."""
|
|
177
|
+
login_list = _logins_param(logins)
|
|
178
|
+
|
|
179
|
+
if dry_run:
|
|
180
|
+
format_output(build_v4_body("GetClientsUnits", login_list), "json", None)
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
client = create_v4_client(
|
|
185
|
+
token=ctx.obj.get("token"),
|
|
186
|
+
login=ctx.obj.get("login"),
|
|
187
|
+
profile=ctx.obj.get("profile"),
|
|
188
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
189
|
+
)
|
|
190
|
+
data = call_v4(client, "GetClientsUnits", login_list)
|
|
191
|
+
format_output(data, output_format, output)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print_error(str(e))
|
|
194
|
+
raise click.Abort()
|
|
195
|
+
|
|
196
|
+
|
|
125
197
|
@v4_method_contract("GetCreditLimits")
|
|
126
198
|
@v4finance.command(name="get-credit-limits")
|
|
127
199
|
@click.option("--logins", required=True, help="Comma-separated client logins")
|
|
@@ -249,6 +321,103 @@ def check_payment(
|
|
|
249
321
|
raise click.Abort()
|
|
250
322
|
|
|
251
323
|
|
|
324
|
+
@v4_method_contract("CreateInvoice")
|
|
325
|
+
@v4finance.command(name="create-invoice")
|
|
326
|
+
@click.option(
|
|
327
|
+
"--payment",
|
|
328
|
+
"payments",
|
|
329
|
+
multiple=True,
|
|
330
|
+
required=True,
|
|
331
|
+
help="Invoice payment as CAMPAIGN_ID=AMOUNT; repeat for multiple campaigns",
|
|
332
|
+
)
|
|
333
|
+
@click.option(
|
|
334
|
+
"--currency",
|
|
335
|
+
default="RUB",
|
|
336
|
+
show_default=True,
|
|
337
|
+
type=click.Choice(V4_FINANCE_CURRENCIES, case_sensitive=False),
|
|
338
|
+
help="Payment currency",
|
|
339
|
+
)
|
|
340
|
+
@click.option(
|
|
341
|
+
"--finance-token",
|
|
342
|
+
envvar="YANDEX_DIRECT_FINANCE_TOKEN",
|
|
343
|
+
help="Precomputed financial token for this method",
|
|
344
|
+
)
|
|
345
|
+
@click.option(
|
|
346
|
+
"--master-token",
|
|
347
|
+
envvar="YANDEX_DIRECT_MASTER_TOKEN",
|
|
348
|
+
help=(
|
|
349
|
+
"Financial master token issued after enabling and saving financial "
|
|
350
|
+
"operations in Tools -> API -> Financial operations"
|
|
351
|
+
),
|
|
352
|
+
)
|
|
353
|
+
@click.option(
|
|
354
|
+
"--operation-num",
|
|
355
|
+
type=click.IntRange(min=1, max=9223372036854775807),
|
|
356
|
+
envvar="YANDEX_DIRECT_OPERATION_NUM",
|
|
357
|
+
help="Financial operation number",
|
|
358
|
+
)
|
|
359
|
+
@click.option(
|
|
360
|
+
"--finance-login",
|
|
361
|
+
envvar="YANDEX_DIRECT_FINANCE_LOGIN",
|
|
362
|
+
help="Login used in financial token generation",
|
|
363
|
+
)
|
|
364
|
+
@click.option(
|
|
365
|
+
"--format",
|
|
366
|
+
"output_format",
|
|
367
|
+
default="json",
|
|
368
|
+
type=click.Choice(["json", "table", "csv", "tsv"]),
|
|
369
|
+
help="Output format",
|
|
370
|
+
)
|
|
371
|
+
@click.option("--output", help="Output file")
|
|
372
|
+
@click.option("--dry-run", is_flag=True, help="Show request without sending")
|
|
373
|
+
@click.pass_context
|
|
374
|
+
def create_invoice(
|
|
375
|
+
ctx,
|
|
376
|
+
payments,
|
|
377
|
+
currency,
|
|
378
|
+
finance_token,
|
|
379
|
+
master_token,
|
|
380
|
+
operation_num,
|
|
381
|
+
finance_login,
|
|
382
|
+
output_format,
|
|
383
|
+
output,
|
|
384
|
+
dry_run,
|
|
385
|
+
):
|
|
386
|
+
"""Create a payment invoice for campaigns."""
|
|
387
|
+
finance_token, operation_num = _finance_credentials(
|
|
388
|
+
finance_token,
|
|
389
|
+
master_token,
|
|
390
|
+
operation_num,
|
|
391
|
+
finance_login,
|
|
392
|
+
"CreateInvoice",
|
|
393
|
+
ctx.obj.get("login"),
|
|
394
|
+
)
|
|
395
|
+
param = _invoice_payments_param(payments, currency)
|
|
396
|
+
|
|
397
|
+
if dry_run:
|
|
398
|
+
format_output(
|
|
399
|
+
_masked_finance_body("CreateInvoice", param, operation_num),
|
|
400
|
+
"json",
|
|
401
|
+
None,
|
|
402
|
+
)
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
try:
|
|
406
|
+
client = create_v4_client(
|
|
407
|
+
token=ctx.obj.get("token"),
|
|
408
|
+
login=ctx.obj.get("login"),
|
|
409
|
+
profile=ctx.obj.get("profile"),
|
|
410
|
+
sandbox=ctx.obj.get("sandbox"),
|
|
411
|
+
finance_token=finance_token,
|
|
412
|
+
operation_num=operation_num,
|
|
413
|
+
)
|
|
414
|
+
data = call_v4(client, "CreateInvoice", param)
|
|
415
|
+
format_output(data, output_format, output)
|
|
416
|
+
except Exception as e:
|
|
417
|
+
print_error(str(e))
|
|
418
|
+
raise click.Abort()
|
|
419
|
+
|
|
420
|
+
|
|
252
421
|
@v4_method_contract("TransferMoney")
|
|
253
422
|
@v4finance.command(name="transfer-money")
|
|
254
423
|
@click.option(
|
|
@@ -59,6 +59,7 @@ SMOKE_MATRIX = {
|
|
|
59
59
|
"turbopages.get",
|
|
60
60
|
"v4events.get-events-log",
|
|
61
61
|
"v4finance.check-payment",
|
|
62
|
+
"v4finance.get-clients-units",
|
|
62
63
|
"v4finance.get-credit-limits",
|
|
63
64
|
"v4goals.get-retargeting-goals",
|
|
64
65
|
"v4goals.get-stat-goals",
|
|
@@ -154,6 +155,7 @@ SMOKE_MATRIX = {
|
|
|
154
155
|
"auth.use",
|
|
155
156
|
"v4account.account-management",
|
|
156
157
|
"v4account.enable-shared-account",
|
|
158
|
+
"v4finance.create-invoice",
|
|
157
159
|
"v4finance.pay-campaigns",
|
|
158
160
|
"v4finance.transfer-money",
|
|
159
161
|
],
|
|
@@ -142,11 +142,22 @@ V4_METHOD_CONTRACTS: dict[str, V4MethodContract] = {
|
|
|
142
142
|
"CreateInvoice": V4MethodContract(
|
|
143
143
|
method="CreateInvoice",
|
|
144
144
|
group="finance",
|
|
145
|
-
param_shape=
|
|
146
|
-
login_placement=
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
param_shape=PARAM_OBJECT,
|
|
146
|
+
login_placement=(
|
|
147
|
+
"param contains Payments; finance_token and operation_num are "
|
|
148
|
+
"top-level v4 Live body fields"
|
|
149
|
+
),
|
|
150
|
+
safety=SAFETY_DANGEROUS,
|
|
151
|
+
source_status=SOURCE_DOCS,
|
|
149
152
|
live_probe_allowed=False,
|
|
153
|
+
example_param={
|
|
154
|
+
"Payments": [{"CampaignID": 123, "Sum": 100.5, "Currency": "RUB"}],
|
|
155
|
+
},
|
|
156
|
+
notes=(
|
|
157
|
+
"Official v4 docs define invoice creation with Payments[]."
|
|
158
|
+
"CampaignID/Sum/Currency and a URL response. The method has no "
|
|
159
|
+
"v5 equivalent and requires finance_token plus operation_num."
|
|
160
|
+
),
|
|
150
161
|
),
|
|
151
162
|
"AccountManagement": V4MethodContract(
|
|
152
163
|
method="AccountManagement",
|
|
@@ -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
|
|
|
@@ -165,13 +169,16 @@ per-request token from `--master-token`, `--operation-num`, and
|
|
|
165
169
|
`--finance-login`; alternatively pass a precomputed token with `--finance-token`.
|
|
166
170
|
Environment variables are
|
|
167
171
|
`YANDEX_DIRECT_MASTER_TOKEN`, `YANDEX_DIRECT_FINANCE_LOGIN`,
|
|
168
|
-
`YANDEX_DIRECT_FINANCE_TOKEN`, and `YANDEX_DIRECT_OPERATION_NUM`.
|
|
169
|
-
|
|
170
|
-
|
|
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.
|
|
171
176
|
|
|
172
177
|
```bash
|
|
178
|
+
direct v4finance get-clients-units --logins client-login,other-client --format table
|
|
173
179
|
direct v4finance get-credit-limits --logins client-login --master-token MASTER_TOKEN --operation-num 123 --finance-login agency-login
|
|
174
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
|
|
175
182
|
direct v4finance check-payment --custom-transaction-id A123456789012345678901234567890B
|
|
176
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
|
|
177
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
|
|
@@ -180,7 +187,9 @@ direct v4finance pay-campaigns --campaign-ids 123,456 --amount 100.50 --currency
|
|
|
180
187
|
### V4 Live Shared Account
|
|
181
188
|
|
|
182
189
|
Shared-account mutations are dry-run-only in this release and always require
|
|
183
|
-
`--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`.
|
|
184
193
|
|
|
185
194
|
```bash
|
|
186
195
|
direct v4account enable-shared-account --client-login client-login --dry-run
|
|
@@ -12,6 +12,7 @@ setup.py
|
|
|
12
12
|
.github/workflows/api-coverage.yml
|
|
13
13
|
.github/workflows/claude-code-review.yml
|
|
14
14
|
.github/workflows/claude.yml
|
|
15
|
+
.github/workflows/quality.yml
|
|
15
16
|
direct_cli/__init__.py
|
|
16
17
|
direct_cli/_deprecated.py
|
|
17
18
|
direct_cli/_smoke_probes.py
|
|
@@ -115,6 +116,7 @@ tests/test_integration.py
|
|
|
115
116
|
tests/test_integration_live_write.py
|
|
116
117
|
tests/test_integration_write.py
|
|
117
118
|
tests/test_reports_drift.py
|
|
119
|
+
tests/test_reports_parsing.py
|
|
118
120
|
tests/test_smoke_matrix.py
|
|
119
121
|
tests/test_transport_contract.py
|
|
120
122
|
tests/test_v4_contracts.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "direct-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.4"
|
|
8
8
|
description = "Command-line interface for Yandex Direct API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -43,9 +43,13 @@ dev = [
|
|
|
43
43
|
"vcrpy>=6.0",
|
|
44
44
|
"black>=22.0",
|
|
45
45
|
"flake8>=4.0",
|
|
46
|
+
"mypy>=1.8",
|
|
47
|
+
"ruff>=0.1",
|
|
46
48
|
"requests>=2.0",
|
|
47
49
|
"beautifulsoup4>=4.12",
|
|
48
50
|
"lxml>=4.9",
|
|
51
|
+
"types-setuptools",
|
|
52
|
+
"types-tabulate",
|
|
49
53
|
]
|
|
50
54
|
|
|
51
55
|
[project.scripts]
|
|
@@ -82,6 +86,28 @@ exclude = '''
|
|
|
82
86
|
[tool.ruff]
|
|
83
87
|
exclude = ["direct_cli/_vendor"]
|
|
84
88
|
|
|
89
|
+
[tool.mypy]
|
|
90
|
+
python_version = "3.9"
|
|
91
|
+
ignore_missing_imports = true
|
|
92
|
+
no_implicit_optional = false
|
|
93
|
+
exclude = [
|
|
94
|
+
"direct_cli/_vendor/",
|
|
95
|
+
"scripts/",
|
|
96
|
+
"tests/",
|
|
97
|
+
]
|
|
98
|
+
disable_error_code = [
|
|
99
|
+
"assignment",
|
|
100
|
+
"arg-type",
|
|
101
|
+
"attr-defined",
|
|
102
|
+
"import-untyped",
|
|
103
|
+
"index",
|
|
104
|
+
"list-item",
|
|
105
|
+
"misc",
|
|
106
|
+
"no-redef",
|
|
107
|
+
"truthy-function",
|
|
108
|
+
"var-annotated",
|
|
109
|
+
]
|
|
110
|
+
|
|
85
111
|
[tool.pytest.ini_options]
|
|
86
112
|
testpaths = ["tests"]
|
|
87
113
|
python_files = "test_*.py"
|