direct-cli 0.4.3__tar.gz → 0.5.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.4.3 → direct_cli-0.5.1}/.github/workflows/quality.yml +3 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/.gitignore +3 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/CHANGELOG.md +461 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/CLAUDE.md +16 -3
- {direct_cli-0.4.3/direct_cli.egg-info → direct_cli-0.5.1}/PKG-INFO +225 -4
- direct_cli-0.4.3/PKG-INFO → direct_cli-0.5.1/README.md +218 -44
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_bidding_strategy.py +62 -235
- direct_cli-0.5.1/direct_cli/_captcha.py +43 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/api.py +20 -0
- direct_cli-0.5.1/direct_cli/browser/__init__.py +24 -0
- direct_cli-0.5.1/direct_cli/browser/_chrome_crypto.py +458 -0
- direct_cli-0.5.1/direct_cli/browser/diagnostics.py +287 -0
- direct_cli-0.5.1/direct_cli/browser/masters.py +1497 -0
- direct_cli-0.5.1/direct_cli/browser/session.py +546 -0
- direct_cli-0.5.1/direct_cli/browser/store.py +214 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/cli.py +19 -2
- direct_cli-0.5.1/direct_cli/commands/_campaigns_base.py +798 -0
- direct_cli-0.5.1/direct_cli/commands/_campaigns_cpm_banner.py +127 -0
- direct_cli-0.5.1/direct_cli/commands/_campaigns_dynamic.py +290 -0
- direct_cli-0.5.1/direct_cli/commands/_campaigns_mobile_app.py +198 -0
- direct_cli-0.5.1/direct_cli/commands/_campaigns_smart.py +429 -0
- direct_cli-0.5.1/direct_cli/commands/_campaigns_text.py +276 -0
- direct_cli-0.5.1/direct_cli/commands/_campaigns_unified.py +270 -0
- direct_cli-0.5.1/direct_cli/commands/_execute.py +30 -0
- direct_cli-0.5.1/direct_cli/commands/_get.py +259 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/_lifecycle.py +15 -0
- direct_cli-0.5.1/direct_cli/commands/adextensions.py +81 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/adgroups.py +122 -182
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/adimages.py +23 -57
- direct_cli-0.5.1/direct_cli/commands/ads/__init__.py +22 -0
- direct_cli-0.5.1/direct_cli/commands/ads/_cli.py +1178 -0
- direct_cli-0.5.1/direct_cli/commands/ads/base.py +236 -0
- direct_cli-0.5.1/direct_cli/commands/ads/batch.py +467 -0
- direct_cli-0.5.1/direct_cli/commands/ads/builder.py +202 -0
- direct_cli-0.5.1/direct_cli/commands/ads/mobile_app.py +88 -0
- direct_cli-0.5.1/direct_cli/commands/ads/objects.py +823 -0
- direct_cli-0.5.1/direct_cli/commands/ads/responsive.py +96 -0
- direct_cli-0.5.1/direct_cli/commands/ads/shopping.py +142 -0
- direct_cli-0.5.1/direct_cli/commands/ads/text.py +153 -0
- direct_cli-0.5.1/direct_cli/commands/advideos.py +61 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/agencyclients.py +82 -146
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/audiencetargets.py +71 -92
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/bidmodifiers.py +141 -221
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/bids.py +45 -74
- direct_cli-0.5.1/direct_cli/commands/browser_session.py +175 -0
- direct_cli-0.5.1/direct_cli/commands/businesses.py +22 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/campaigns.py +203 -1884
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/clients.py +61 -120
- direct_cli-0.5.1/direct_cli/commands/creatives.py +88 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/dictionaries.py +4 -3
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/dynamicads.py +33 -96
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/dynamicfeedadtargets.py +41 -93
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/feeds.py +31 -105
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/keywordbids.py +3 -14
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/keywords.py +76 -134
- direct_cli-0.5.1/direct_cli/commands/masters.py +794 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/negativekeywordsharedsets.py +14 -62
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/retargeting.py +18 -61
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/sitelinks.py +23 -78
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/smartadtargets.py +34 -103
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/strategies.py +126 -256
- direct_cli-0.5.1/direct_cli/commands/trackingparams.py +24 -0
- direct_cli-0.5.1/direct_cli/commands/turbopages.py +44 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4account.py +19 -46
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4adimage.py +17 -27
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4finance.py +20 -55
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4goals.py +7 -9
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4tags.py +7 -25
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/vcards.py +12 -52
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/output.py +66 -26
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/reports_coverage.py +8 -9
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/smoke_matrix.py +43 -1
- direct_cli-0.5.1/direct_cli/tracking_params.py +123 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/ads.json +1 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/common.json +0 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4finance.json +0 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/utils.py +70 -7
- direct_cli-0.5.1/direct_cli/v4/parse.py +135 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/wsdl_coverage.py +19 -9
- direct_cli-0.4.3/README.md → direct_cli-0.5.1/direct_cli.egg-info/PKG-INFO +265 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/SOURCES.txt +54 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/requires.txt +7 -2
- {direct_cli-0.4.3 → direct_cli-0.5.1}/pyproject.toml +22 -3
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/build_api_coverage_report.py +20 -6
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/check_all_docs_urls.py +2 -0
- direct_cli-0.5.1/scripts/measure_criteria_limits.py +213 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/test_dangerous_commands.sh +19 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/conftest.py +42 -0
- direct_cli-0.5.1/tests/fixtures/masters_grid_campaigns.json +72 -0
- direct_cli-0.5.1/tests/fixtures/masters_wizard_create.html +279 -0
- direct_cli-0.5.1/tests/fixtures/masters_wizard_edit_stage_a.html +137 -0
- direct_cli-0.5.1/tests/fixtures/masters_wizard_overview.html +118 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_api_coverage.py +112 -31
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_audit_wire_shape.py +6 -13
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_cassette_integrity.py +3 -9
- direct_cli-0.5.1/tests/test_chrome_crypto.py +452 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_cli.py +1 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_cli_contract.py +8 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_comprehensive.py +3 -0
- direct_cli-0.5.1/tests/test_dry_run.py +132 -0
- direct_cli-0.5.1/tests/test_dry_run_adgroups.py +2070 -0
- direct_cli-0.5.1/tests/test_dry_run_ads.py +3968 -0
- direct_cli-0.5.1/tests/test_dry_run_bids.py +892 -0
- direct_cli-0.5.1/tests/test_dry_run_campaigns.py +3657 -0
- direct_cli-0.5.1/tests/test_dry_run_clients.py +691 -0
- direct_cli-0.5.1/tests/test_dry_run_common.py +768 -0
- direct_cli-0.5.1/tests/test_dry_run_extensions.py +940 -0
- direct_cli-0.5.1/tests/test_dry_run_keywords.py +970 -0
- direct_cli-0.5.1/tests/test_dry_run_shared.py +66 -0
- direct_cli-0.5.1/tests/test_dry_run_strategies.py +608 -0
- direct_cli-0.5.1/tests/test_dry_run_strategy_smart.py +1949 -0
- direct_cli-0.5.1/tests/test_dry_run_strategy_text.py +5149 -0
- direct_cli-0.5.1/tests/test_dry_run_strategy_unified.py +2359 -0
- direct_cli-0.5.1/tests/test_dry_run_targets.py +612 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_integration.py +69 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_integration_write.py +6 -6
- direct_cli-0.5.1/tests/test_masters.py +3426 -0
- direct_cli-0.5.1/tests/test_playwright_session.py +306 -0
- direct_cli-0.5.1/tests/test_reference_output.py +104 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_reports_parsing.py +0 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_smoke_matrix.py +1 -1
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_transport_contract.py +3 -3
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_live_contracts.py +6 -6
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v5_live_write.py +12 -12
- direct_cli-0.4.3/direct_cli/commands/adextensions.py +0 -124
- direct_cli-0.4.3/direct_cli/commands/ads.py +0 -3133
- direct_cli-0.4.3/direct_cli/commands/advideos.py +0 -96
- direct_cli-0.4.3/direct_cli/commands/businesses.py +0 -57
- direct_cli-0.4.3/direct_cli/commands/creatives.py +0 -150
- direct_cli-0.4.3/direct_cli/commands/turbopages.py +0 -67
- direct_cli-0.4.3/tests/test_dry_run.py +0 -24849
- {direct_cli-0.4.3 → direct_cli-0.5.1}/.env.example +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/.github/copilot-instructions.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/.github/workflows/api-coverage.yml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/.github/workflows/claude.yml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/AGENTS.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/MANIFEST.in +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/__init__.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_autotargeting.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_deprecated.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_flag_validation.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_smoke_probes.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/__init__.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/__init__.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/endpoints.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/exceptions.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/resource_mapping.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/tapi_yandex_direct.pyi +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/v4/__init__.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/v4/adapter.pyi +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/_vendor/tapi_yandex_direct/v4/resource_mapping.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/auth.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/__init__.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/_batch.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/auth.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/balance.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/changes.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/keywordsresearch.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/leads.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/reports.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4events.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4forecast.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4keywords.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4shells.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/commands/v4wordstat.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/i18n.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/adextensions.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/adgroups.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/adimages.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/advideos.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/agencyclients.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/audiencetargets.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/auth.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/balance.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/bidmodifiers.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/bids.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/businesses.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/campaigns.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/changes.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/clients.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/creatives.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/dictionaries.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/dynamicads.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/dynamicfeedadtargets.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/feeds.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/keywordbids.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/keywords.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/keywordsresearch.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/leads.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/negativekeywordsharedsets.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/reports.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/retargeting.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/sitelinks.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/smartadtargets.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/strategies.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/turbopages.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4account.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4adimage.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4events.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4forecast.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4goals.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4keywords.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4shells.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4tags.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/v4wordstat.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/translations/vcards.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/v4/__init__.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/v4/emit.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/v4/money.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli/v4_contracts.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/dependency_links.txt +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/entry_points.txt +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/direct_cli.egg-info/top_level.txt +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/API_COVERAGE.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/PROJECT_WIRE_SHAPE_AUDIT_2026-05-30.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/WIRE_SHAPE_TRIAGE_2026-05-30.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/issue-198-mutating-wsdl-audit.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/audits/wire_shape.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/superpowers/plans/2026-04-12-issue-32-completion.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/docs/superpowers/specs/2026-04-23-vendor-tapi-yandex-direct-design.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/anonymize_cassettes.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/audit_wire_shape.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/build_api_coverage_checklist.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/build_wsdl_optional_field_audit.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/check_reports_drift.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/check_wsdl_drift.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/patch_vendor_imports.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/preflight_check.sh +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/probe_drift_urls.sh +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/refresh_reports_cache.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/refresh_wsdl_cache.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/release_pypi.sh +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/sandbox_write_audit.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/sandbox_write_live.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/test_safe_commands.sh +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/test_sandbox_write.sh +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/scripts/update_vendor.sh +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/setup.cfg +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/setup.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/API_COVERAGE.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/API_ISSUE_AUDIT.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/MANUAL_COVERAGE.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/WSDL_OPTIONAL_FIELD_AUDIT.md +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/__init__.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/_orphan_store.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/api_coverage_payloads.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAdExtensions.test_add_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAdGroups.test_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAdImages.test_add_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAds.test_add_text_ad_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteAudienceTargets.test_add_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBidModifiersAdd.test_add_delete_mobile.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBidModifiersSet.test_set_without_id_is_rejected.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBids.test_set_bid.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_get.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteBidsRead.test_bids_set_auto.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteCampaignDraftLifecycle.test_draft_create_get_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteCampaigns.test_campaign_lifecycle.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteDynamicAds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteFeeds.test_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteKeywordBids.test_set_keyword_bid.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteKeywords.test_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteNegativeKeywordSharedSets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteRetargeting.test_add_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteRetargetingUpdate.test_retargeting_update.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteSitelinks.test_add_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteSmartAdTargets.test_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteStrategies.test_strategies_lifecycle.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_integration_write/TestWriteVCards.test_add_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[adextensions_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[adgroups_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[adimages_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[ads_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[advideos_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[audiencetargets_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[bidmodifiers_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[bids_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[businesses_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[campaigns_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_campaigns].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[changes_check_dictionaries].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[clients_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[creatives_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[dictionaries_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[dynamicads_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[dynamicfeedadtargets_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[feeds_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordbids_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[keywords_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_deduplicate].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[keywordsresearch_has_search_volume].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[leads_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[negativekeywordsharedsets_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[reports_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[retargeting_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[sitelinks_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[smartadtargets_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[strategies_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[turbopages_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4events_get_events_log].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4finance_get_clients_units].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4forecast_list].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4goals_get_stat_goals].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4tags_get_campaigns].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[v4wordstat_list_reports].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_read_command[vcards_get].yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_read_cassettes/test_v4finance_check_payment_unknown_transaction.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adgroups_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_adimages_add_get_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_ads_suspend_resume_archive_unarchive.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_advideos_add_get.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_add_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_audiencetargets_suspend_resume.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_bids_set.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_campaign_create_get_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_creatives_chain_advideo_to_creative.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_add_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_dynamicads_suspend_resume.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_feeds_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywordbids_set.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_keywords_suspend_resume.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_retargeting_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_sitelinks_add_get_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_add_update_delete.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_smartadtargets_suspend_resume.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/cassettes/test_v5_live_write/test_v5_live_draft_strategies_add_update_archive_unarchive.yaml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/fixtures/test-video.mp4 +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/fields-list.html +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/headers.html +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/period.html +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/spec.html +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/raw/type.html +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/reports_cache/spec.json +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_adgroups_build_adgroup_object.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_adgroups_build_adgroup_update_object.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_ads_build_ad_object.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_ads_build_ad_update_object.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_auth_bw.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_auth_oauth.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_auth_op.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_auth_write_json.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_autotargeting.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_balance.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_bidding_strategy_constants.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_changes.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_check_all_docs_urls.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_env_loading.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_field_names_option.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_flag_validation.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_handle_api_errors.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_i18n.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_low_coverage_payloads.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_read_cassettes.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_reports_drift.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_sandbox_write_audit.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_unknown_option_hints.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_contracts.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_exit_codes.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_foundation.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_output_options.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_runtime_shape.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4_safety.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4account.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4adimage.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4events.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4finance_money.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4finance_read.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4forecast.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4goals.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4keywords.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4meta.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4tags.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_v4wordstat.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_vendor_imports.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/test_wsdl_parity_gate.py +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/adextensions.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/adgroups.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/adimages.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/ads.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/advideos.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/agencyclients.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/audiencetargets.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/bidmodifiers.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/bids.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/businesses.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/campaigns.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/changes.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/clients.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/creatives.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/dictionaries.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/dynamicfeedadtargets.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/dynamictextadtargets.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/feeds.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/imports/adextensiontypes.xsd +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/imports/general.xsd +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/imports/generalclients.xsd +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/keywordbids.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/keywords.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/keywordsresearch.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/leads.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/negativekeywordsharedsets.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/retargetinglists.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/sitelinks.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/smartadtargets.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/strategies.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/turbopages.xml +0 -0
- {direct_cli-0.4.3 → direct_cli-0.5.1}/tests/wsdl_cache/vcards.xml +0 -0
|
@@ -1,7 +1,468 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
**Added — `direct masters archive` (#633):**
|
|
6
|
+
|
|
7
|
+
- New command that archives a Мастер кампаний. Live recon confirmed Yandex's
|
|
8
|
+
UI has **no separate "delete" action** for it — neither the campaigns
|
|
9
|
+
grid's row menu nor the overview page's own menu has a "Удалить" item,
|
|
10
|
+
only "Архивировать" (issue #633 comment documents the DOM structure
|
|
11
|
+
found live). This is the closest thing to delete Мастер кампаний has.
|
|
12
|
+
- Clicks the overview page's "⋮" menu then "Архивировать" — both selected
|
|
13
|
+
via stable, confirmed-live `data-testid` attributes
|
|
14
|
+
(`CampaignHeader.MenuTrigger`/`CampaignHeader.Menu.archive`), unlike
|
|
15
|
+
`suspend`/`resume`'s best-effort text-matched candidate buttons.
|
|
16
|
+
- Verifies success by re-reading the campaigns grid (`fetch_masters_list`)
|
|
17
|
+
and confirming the status actually became `ARCHIVED`, rather than
|
|
18
|
+
trusting the click alone. Idempotent: an already-archived campaign is a
|
|
19
|
+
no-op warning, not an error.
|
|
20
|
+
- **Irreversible from this CLI** — there is no `masters unarchive`.
|
|
21
|
+
Classified `DANGEROUS` in `direct_cli/smoke_matrix.py` (same as
|
|
22
|
+
`suspend`/`resume`): Мастер кампаний has no API and thus no `--sandbox`
|
|
23
|
+
isolation, so any exercise of this command hits a real account.
|
|
24
|
+
|
|
25
|
+
**Added — `direct masters update` (#631, Этап A):**
|
|
26
|
+
|
|
27
|
+
- New command that edits a single Мастер кампаний's settings page
|
|
28
|
+
(`/wizard/campaigns/{id}/edit/`). This first stage covers exactly three
|
|
29
|
+
simple scalar fields: `--weekly-budget` (integer, Недельный бюджет),
|
|
30
|
+
`--promotion-goal` (`max-conversions`/`max-clicks`, Цель продвижения), and
|
|
31
|
+
`--directs-helps`/`--no-directs-helps` (Директ помогает — auto-apply
|
|
32
|
+
Yandex recommendations). At least one flag is required.
|
|
33
|
+
- Live investigation (see `tests/fixtures/masters_wizard_edit_stage_a.html`)
|
|
34
|
+
found the edit page is a single form with one "Сохранить кампанию" button
|
|
35
|
+
— there is no per-section independent save. `update_master` therefore
|
|
36
|
+
submits the whole form on every call; fields not passed as flags are left
|
|
37
|
+
untouched (their current on-page value is preserved simply by never
|
|
38
|
+
filling/toggling their input).
|
|
39
|
+
- New `direct_cli/browser/masters.py` functions: `update_master` plus
|
|
40
|
+
private `_set_weekly_budget`/`_set_directs_helps`/`_set_promotion_goal`
|
|
41
|
+
setters. Each field setter follows the module's existing "click, then
|
|
42
|
+
verify the change actually took effect" convention (`_suspend_or_resume`)
|
|
43
|
+
rather than trusting a click alone. `update_master` itself also verifies:
|
|
44
|
+
after clicking save it re-navigates to the edit page and re-reads every
|
|
45
|
+
requested field (`_verify_saved`), raising `BrowserSessionError` on any
|
|
46
|
+
mismatch instead of reporting a false success — so a click that doesn't
|
|
47
|
+
actually persist (rejected validation, session/redirect failure) is a
|
|
48
|
+
hard error, not a silent success.
|
|
49
|
+
- `_click_save` and `_set_promotion_goal`'s option click use
|
|
50
|
+
`get_by_role(..., exact=True)`, not a substring `get_by_text` match — an
|
|
51
|
+
exact accessible-name match, scoped to the actual button/option role,
|
|
52
|
+
avoids clicking an ancestor container whose text merely contains the
|
|
53
|
+
target label.
|
|
54
|
+
- Later Этап (B/C/D) fields — headline/text variant lists, sitelinks,
|
|
55
|
+
audience, Metrika counters/goals, budget adaptation, images/video — are
|
|
56
|
+
tracked separately in issue #631 and not implemented here.
|
|
57
|
+
- No `--sandbox` equivalent exists for this browser-driven mutation (same
|
|
58
|
+
rationale as `masters suspend`/`resume`, #630) — classified `DANGEROUS` in
|
|
59
|
+
`direct_cli/smoke_matrix.py` and documented as manual-only in
|
|
60
|
+
`scripts/test_dangerous_commands.sh`.
|
|
61
|
+
|
|
62
|
+
**Added — `direct masters add` (#632):**
|
|
63
|
+
|
|
64
|
+
- New command that creates a brand-new "Конверсии и трафик" Мастер кампаний
|
|
65
|
+
by driving the same multi-field create wizard a human uses in the browser
|
|
66
|
+
(`https://direct.yandex.ru/wizard/campaigns/new/`) — Мастер кампаний has no
|
|
67
|
+
API surface at all, same as `list`/`get`/`suspend`/`resume`.
|
|
68
|
+
- Live recon (issue #632 step 0, see
|
|
69
|
+
`tests/fixtures/masters_wizard_create.html`) found the create flow is NOT
|
|
70
|
+
the multi-page "Далее" wizard the issue originally assumed: it is one
|
|
71
|
+
micro-step (a landing-page URL field with client-side format validation)
|
|
72
|
+
followed by a single long form, terminating in two buttons — "Запустить
|
|
73
|
+
кампанию" (launch) and "Сохранить как черновик" (save as draft) — instead
|
|
74
|
+
of `masters update`'s one "Сохранить кампанию".
|
|
75
|
+
- `--headline`/`--text`/`--region` are required (repeat the flag for
|
|
76
|
+
multiple values) even though Yandex's own wizard can auto-generate
|
|
77
|
+
headlines/texts by scanning the landing page — `add` refuses to silently
|
|
78
|
+
publish AI-written ad copy the caller never reviewed, given there is no
|
|
79
|
+
sandbox or rollback for Мастер кампаний mutations.
|
|
80
|
+
- `--weekly-budget` is optional. `--draft`/`--launch` (default `--launch`)
|
|
81
|
+
selects which terminal button is clicked.
|
|
82
|
+
- **NOT idempotent**: running this twice with the same arguments creates a
|
|
83
|
+
*second* campaign, not an update to the first — documented prominently in
|
|
84
|
+
the command's own `--help` and in the README, per the issue's explicit
|
|
85
|
+
"Риски" requirement.
|
|
86
|
+
- Classified `DANGEROUS` in `direct_cli/smoke_matrix.py` and documented as
|
|
87
|
+
manual-only (verify with `--draft` first) in
|
|
88
|
+
`scripts/test_dangerous_commands.sh` — no `--sandbox` equivalent exists for
|
|
89
|
+
Мастер кампаний.
|
|
90
|
+
- After clicking the terminal button, re-reads the headline/text/budget
|
|
91
|
+
fields to confirm the form actually reflects what was requested
|
|
92
|
+
(`_verify_created`) rather than trusting the click alone — ported from
|
|
93
|
+
`masters update`'s `_verify_saved` pattern (issue #631 review finding).
|
|
94
|
+
Dropdown/option clicks (`_fill_landing_url`'s "Далее", `_set_region`'s
|
|
95
|
+
suggestion, `_click_terminal_button`'s launch/draft button) use
|
|
96
|
+
`get_by_role(..., exact=True)`, not a substring `get_by_text` match — same
|
|
97
|
+
fix applied to `update_master`'s `_click_save`/`_set_promotion_goal`, to
|
|
98
|
+
avoid clicking an ancestor container whose text merely contains the
|
|
99
|
+
target label.
|
|
100
|
+
- Not end-to-end live-verified: step 0 recon was deliberately read-only (no
|
|
101
|
+
campaign was created or saved during recon) — see the browser module's
|
|
102
|
+
docstring for what remains to be confirmed against a real account before
|
|
103
|
+
relying on this in production. In particular, `_verify_created` cannot
|
|
104
|
+
re-navigate and reload the way `_verify_saved` does (the post-click
|
|
105
|
+
destination URL is unconfirmed), so it only re-reads the current page
|
|
106
|
+
immediately after the click — a strictly weaker check pending a live
|
|
107
|
+
pass.
|
|
108
|
+
|
|
109
|
+
**Added — `direct masters login` (#635):**
|
|
110
|
+
|
|
111
|
+
- New interactive command that opens a visible browser window on a
|
|
112
|
+
persistent Chromium profile owned by the CLI
|
|
113
|
+
(`~/.direct-cli/chrome-profile/` by default, overridable with
|
|
114
|
+
`--profile-dir`). Log in by hand once via Yandex Passport; the command
|
|
115
|
+
polls until the session is confirmed authenticated (or times out after
|
|
116
|
+
`--timeout` seconds, default 300) and exits.
|
|
117
|
+
- Independent of `direct playwright login`/the macOS Keychain entirely —
|
|
118
|
+
this never touches the user's real Chrome profile, so it works
|
|
119
|
+
identically on macOS/Linux/Windows at the cost of a one-time manual login
|
|
120
|
+
instead of a transparent cookie copy.
|
|
121
|
+
- `direct masters` (`list`/`get`/`suspend`/`resume`) prefers this persistent
|
|
122
|
+
profile automatically whenever it exists, ahead of the saved
|
|
123
|
+
`playwright login` session — see the new tier 1.5 in
|
|
124
|
+
`direct_cli/commands/masters.py`'s session-resolution docstring.
|
|
125
|
+
- Interactive by design (blocks waiting for a human); classified
|
|
126
|
+
`DANGEROUS` in `direct_cli/smoke_matrix.py` and documented as
|
|
127
|
+
manual-only in `scripts/test_dangerous_commands.sh` — it cannot run in
|
|
128
|
+
any automated smoke tier.
|
|
129
|
+
- The profile directory is chmod'd `0700` (it holds a live Yandex session in
|
|
130
|
+
plaintext-readable cookies).
|
|
131
|
+
- New `direct masters logout` deletes the profile — the only way to revoke
|
|
132
|
+
the on-disk session short of a manual `rm -rf`. A no-op with a warning
|
|
133
|
+
(not an error) if no profile exists. Also classified `DANGEROUS`
|
|
134
|
+
(local credential-store mutation, same category as `auth login`/`auth
|
|
135
|
+
use`).
|
|
136
|
+
- `masters login` refuses to run without a terminal. It waits for a human,
|
|
137
|
+
so in CI or a script it now fails immediately with a clear message instead
|
|
138
|
+
of blocking for the full `--timeout` on a browser window nobody can see.
|
|
139
|
+
- `masters login` polls for completion on a separate page instead of the
|
|
140
|
+
one the user is typing into. Previously the loop navigated the visible
|
|
141
|
+
Passport tab to the grid once a second, wiping a half-filled login form
|
|
142
|
+
or a pending 2FA prompt out from under the user.
|
|
143
|
+
- `masters login` refuses a `--profile-dir` that already exists without the
|
|
144
|
+
CLI's own marker. Previously the marker was planted into whatever directory
|
|
145
|
+
was named — so `masters login --profile-dir ~` marked the home directory as
|
|
146
|
+
CLI-owned, and `masters logout` on the same path then accepted that marker
|
|
147
|
+
as authorization to `shutil.rmtree` it. A failed login armed it just the
|
|
148
|
+
same, since the marker was written before Chromium even launched.
|
|
149
|
+
- `masters login --profile-dir X` is now honoured by the read commands. The
|
|
150
|
+
chosen directory is recorded, and `list`/`get`/`suspend`/`resume` resolve
|
|
151
|
+
the profile through that record. Previously tier 1.5 looked only at the
|
|
152
|
+
default location and passed no directory through, so a custom login path
|
|
153
|
+
reported "Login confirmed" and was then silently ignored by every read —
|
|
154
|
+
falling back to the Keychain the flag exists to avoid. `masters logout`
|
|
155
|
+
clears the record along with the profile.
|
|
156
|
+
- The recorded profile path is stored and read as absolute. A relative value
|
|
157
|
+
would have been re-resolved against whatever directory a later command ran
|
|
158
|
+
from, so `masters logout` acted on a different profile depending on where
|
|
159
|
+
the user stood.
|
|
160
|
+
- `masters` commands route through the persistent profile only when it holds
|
|
161
|
+
an actual browser session, not merely when the directory exists. An aborted
|
|
162
|
+
`masters login` leaves an empty profile behind; treating that as usable cost
|
|
163
|
+
a wasted browser launch on every later command and bypassed the user's
|
|
164
|
+
working saved session.
|
|
165
|
+
- `masters logout` refuses to delete anything `masters login` did not
|
|
166
|
+
create. Every profile now carries a `.direct-cli-profile` marker file,
|
|
167
|
+
and `logout` rejects a target that lacks it, is a symlink, or is not a
|
|
168
|
+
directory. Without this a mistyped or shell-expanded `--profile-dir`
|
|
169
|
+
(`.`, `~`) went straight into `shutil.rmtree` and recursively deleted an
|
|
170
|
+
arbitrary tree.
|
|
171
|
+
|
|
172
|
+
**Added — `direct masters suspend` / `direct masters resume` (#630):**
|
|
173
|
+
|
|
174
|
+
- First mutating `masters` commands — stop/resume a Мастер кампаний by
|
|
175
|
+
clicking its overview page's action button, then re-reading the status to
|
|
176
|
+
confirm the change actually took effect (the click alone is never treated
|
|
177
|
+
as success). Both are idempotent: a campaign already in the target status
|
|
178
|
+
is a no-op with a warning, not an error. Neither has a `--sandbox`
|
|
179
|
+
equivalent — Мастер кампаний has no API surface at all, so there is no
|
|
180
|
+
isolated test copy; every mutation hits the real account (classified
|
|
181
|
+
`DANGEROUS` in `direct_cli/smoke_matrix.py`, manual-only per
|
|
182
|
+
`scripts/test_dangerous_commands.sh`).
|
|
183
|
+
- **Not live-verified:** the resume button's text ("Возобновить кампанию")
|
|
184
|
+
is confirmed against a live account; the suspend/stop button's exact text
|
|
185
|
+
is not — `direct_cli/browser/masters.py` tries a short list of plausible
|
|
186
|
+
Russian labels and raises a clear error (suggesting `--headful`) if none
|
|
187
|
+
match, rather than clicking the wrong element.
|
|
188
|
+
|
|
189
|
+
**BREAKING CHANGES — `direct masters` no longer accepts `--login` (#639):**
|
|
190
|
+
|
|
191
|
+
- `direct masters list`/`get` dropped the `--login` option. Passing your own
|
|
192
|
+
login there built a `?ulogin=<login>` URL, but `ulogin` is Yandex's
|
|
193
|
+
*managed-client* (agency) parameter — passing your own login as the managed
|
|
194
|
+
client produced "Доступ ограничен" and HTTP 401 on the grid's data calls,
|
|
195
|
+
which is exactly why `masters list` found nothing. `direct masters` only
|
|
196
|
+
ever reads the logged-in browser session's own account now; there is no
|
|
197
|
+
agency/managed-client support and no replacement flag.
|
|
198
|
+
|
|
199
|
+
**Fixed — `direct masters list` found no Мастера кампаний at all (#639):**
|
|
200
|
+
|
|
201
|
+
- Three independent bugs, found via live diagnosis: (1) the `--login`/
|
|
202
|
+
`ulogin` issue above; (2) the DOM selector
|
|
203
|
+
(`a[href*='/wizard/campaigns/']`) never matched anything, because the
|
|
204
|
+
campaigns grid is a virtualized SPA that renders zero such anchors; (3) the
|
|
205
|
+
`status-filter` URL query parameter was silently ignored by the grid.
|
|
206
|
+
`list` now replays the grid's own JSON data call
|
|
207
|
+
(`POST /web-api/grid/api?operationName=GridCampaigns`) instead of scraping
|
|
208
|
+
the DOM, paginates past its 200-row page limit, and filters status on the
|
|
209
|
+
client side using `status.primaryStatus`.
|
|
210
|
+
|
|
211
|
+
**Added — `direct masters list --status`:**
|
|
212
|
+
|
|
213
|
+
- New `--status` option (`not-archived` default, plus `active`, `stopped`,
|
|
214
|
+
`archived`, `all`) lets you see only archived Мастера кампаний, or every
|
|
215
|
+
status at once — previously `list` had no status filtering at all.
|
|
216
|
+
|
|
217
|
+
**New — `direct playwright login` / `direct playwright doctor`:**
|
|
218
|
+
|
|
219
|
+
- `direct masters` previously re-decrypted Chrome's Yandex cookies via the
|
|
220
|
+
macOS Keychain on every single call. `direct playwright login` decrypts
|
|
221
|
+
once and persists the result as a Playwright `storage_state` session
|
|
222
|
+
(`~/.direct-cli/playwright/session.json`, `0600`) — `direct masters`
|
|
223
|
+
automatically prefers this saved session over a fresh Keychain round-trip,
|
|
224
|
+
falling back transparently when no session is saved, `--profile-dir`/
|
|
225
|
+
`--chrome-profile` was passed explicitly, or the saved session has expired.
|
|
226
|
+
Purely additive: `direct masters` continues to work with zero setup.
|
|
227
|
+
- `direct playwright doctor` reports the health of the whole pipeline
|
|
228
|
+
(playwright/cryptography installed, chromium downloaded, Chrome profile
|
|
229
|
+
found, Keychain key derivable, cookies decryptable, saved session
|
|
230
|
+
present/fresh) without ever logging in, launching a browser, or writing to
|
|
231
|
+
disk.
|
|
232
|
+
- Neither command requires a Yandex Direct API token/login.
|
|
233
|
+
|
|
234
|
+
**Fixed — `direct masters` didn't work on macOS at all (#634):**
|
|
235
|
+
|
|
236
|
+
- `direct masters list`/`get` previously copied `Cookies` + `Local State`
|
|
237
|
+
into a throwaway Chrome profile, assuming Chromium would decrypt the
|
|
238
|
+
cookies itself. On macOS this silently failed: the cookie AES key lives
|
|
239
|
+
only in the login Keychain (`Chrome Safe Storage`); `Local State`'s
|
|
240
|
+
`os_crypt.encrypted_key` is populated only on Windows. Every cookie was
|
|
241
|
+
discarded, producing an unauthenticated session that landed on Yandex's
|
|
242
|
+
login page and then timed out (`networkidle` never settles there, since
|
|
243
|
+
the login page holds long-poll connections).
|
|
244
|
+
- `direct masters` now reads the Keychain password itself, derives Chrome's
|
|
245
|
+
AES-128-CBC key, decrypts only the Yandex Direct cookies it needs, and
|
|
246
|
+
injects them into a fresh bundled-Chromium context via `add_cookies()` —
|
|
247
|
+
no more temp-profile copying, and `channel="chrome"` (which required a
|
|
248
|
+
real Google Chrome install) is no longer needed.
|
|
249
|
+
- Login-page and expired-session failures are now reported explicitly
|
|
250
|
+
(`BrowserAuthError`) instead of surfacing as an opaque 30s timeout.
|
|
251
|
+
- New optional dependency in the `browser` extra: `cryptography>=41`.
|
|
252
|
+
- Linux is supported (Chrome's basic/no-keyring password store only);
|
|
253
|
+
Windows is not yet.
|
|
254
|
+
|
|
255
|
+
**New — `direct masters` (#628):**
|
|
256
|
+
|
|
257
|
+
- Added `direct masters list` / `direct masters get <ids>` — read-only access
|
|
258
|
+
to Мастер кампаний (Campaign Wizard), which has **no Yandex Direct API
|
|
259
|
+
surface at all** (do not confuse with `UNIFIED_CAMPAIGN`, an unrelated v5
|
|
260
|
+
API campaign type already supported by `campaigns add/get --type
|
|
261
|
+
unified_campaign`). These commands drive a real Chrome session via
|
|
262
|
+
Playwright, decrypting and reusing the user's own Chrome cookies for
|
|
263
|
+
`yandex.ru` — no separate login flow (see the Fixed entry above for the
|
|
264
|
+
macOS Keychain decryption path added in #634). Requires the optional
|
|
265
|
+
`browser` extra: `pip install "direct-cli[browser]" && playwright install
|
|
266
|
+
chromium`. `direct masters` degrades gracefully (per-section warnings, not
|
|
267
|
+
a hard failure) when Yandex's page markup changes, since this data has no
|
|
268
|
+
API contract to rely on. See `direct_cli/browser/` for the scraping layer.
|
|
269
|
+
|
|
270
|
+
**Internal — campaigns.py split, step 1 — CPM_BANNER (#613, part of #602):**
|
|
271
|
+
|
|
272
|
+
- Extracted the `CPM_BANNER_CAMPAIGN` `add`/`update` subtype-block composition
|
|
273
|
+
(the former inline `elif campaign_type_norm == "CPM_BANNER_CAMPAIGN":`
|
|
274
|
+
branches) into a new sibling module
|
|
275
|
+
`direct_cli/commands/_campaigns_cpm_banner.py` (`build_add_block` /
|
|
276
|
+
`build_update_block`). Both commands now snapshot their CLI parameters once
|
|
277
|
+
(`p = dict(locals())`) and delegate. The CLI surface, every flag and every
|
|
278
|
+
`--dry-run` payload is byte-for-byte identical (28 `test_campaigns_*cpm*`
|
|
279
|
+
fixtures green).
|
|
280
|
+
|
|
281
|
+
**Internal — campaigns.py split, step 3 (#615):**
|
|
282
|
+
|
|
283
|
+
- Extracted the `SMART_CAMPAIGN` add/update subtype-block composition
|
|
284
|
+
(≈285 lines) into a new sibling module
|
|
285
|
+
`direct_cli/commands/_campaigns_smart.py`
|
|
286
|
+
(`build_add_block` / `build_update_block`). The `add` and `update`
|
|
287
|
+
commands now snapshot their CLI parameters once via `p = dict(locals())`
|
|
288
|
+
and delegate; the SmartCampaign builder pulls only the smart-relevant
|
|
289
|
+
flags from `p`. CLI surface, every flag and every `--dry-run` payload is
|
|
290
|
+
byte-for-byte identical (16 `test_campaigns_*smart*` fixtures green,
|
|
291
|
+
offline tier 2521 passed, WSDL parity + API coverage green).
|
|
292
|
+
|
|
293
|
+
**Internal — campaigns.py split, step 1 (#602):**
|
|
294
|
+
|
|
295
|
+
- Extracted all shared constants (`CAMPAIGNS_GET_CRITERIA_LIMITS`, `YES_NO`,
|
|
296
|
+
`ATTRIBUTION_MODELS`, …), typed-flag validators (`_validate_max_length`,
|
|
297
|
+
`_validate_sms_time`), payload builders (`_build_notification`,
|
|
298
|
+
`_build_time_targeting`, `_build_relevant_keywords`,
|
|
299
|
+
`_build_dynamic_placement_types`, `_build_frequency_cap`,
|
|
300
|
+
`_build_package_bidding_strategy`, `_build_smart_package_bidding_strategy`,
|
|
301
|
+
`_priority_goals_update_items`, `_route_cpa_flag`) and the reusable
|
|
302
|
+
TextCampaign strategy `click.option` groups (`_TEXT_*_STRATEGY_OPTIONS` and
|
|
303
|
+
their `*_UPDATE` variants) into a new sibling module
|
|
304
|
+
`direct_cli/commands/_campaigns_base.py`. `campaigns.py` re-imports every
|
|
305
|
+
name, so the CLI surface, every flag and every `--dry-run` payload is
|
|
306
|
+
byte-for-byte identical. First step of an incremental decomposition; the
|
|
307
|
+
per-campaign-type logic (`text`, `unified`, `dynamic`, `smart`,
|
|
308
|
+
`mobile_app`, `cpm_banner`) follows in subsequent PRs.
|
|
309
|
+
|
|
310
|
+
**Internal — `make_get_command` covers the complex Group-4 `get`s (#588):**
|
|
311
|
+
|
|
312
|
+
- `adgroups get`, `keywords get`, `creatives get`, `strategies get` and
|
|
313
|
+
`audiencetargets get` now register through the shared `make_get_command`
|
|
314
|
+
factory (net −345 lines). They reuse the existing `extra_options` /
|
|
315
|
+
`criteria_builder` / `criteria_limits` / `require_criteria_message` /
|
|
316
|
+
`nested_field_options` hooks — the `--status`/`--statuses` mutual-exclusion
|
|
317
|
+
guard (adgroups/keywords) lives in the per-module `criteria_builder`, not a
|
|
318
|
+
new factory flag. adgroups (8) and strategies (16) exercise the factory's
|
|
319
|
+
largest nested-`*FieldNames` sets to date.
|
|
320
|
+
- Factory ordering fix: nested `*FieldNames` are now parsed (and their
|
|
321
|
+
provided-but-empty-CSV `UsageError` raised) *before* both the criteria-limit
|
|
322
|
+
enforcement and the empty-criteria `require_criteria_message` guard, matching
|
|
323
|
+
the order every hand-rolled command used. So a `--<nested> ""` combined with
|
|
324
|
+
either no filter at all or an over-limit array reports the nested error, not
|
|
325
|
+
the require/limit one (pinned by a new `test_dry_run` regression test). The
|
|
326
|
+
parsed dict is still merged after the common params, so payload key order is
|
|
327
|
+
unchanged, and this also restores the pre-factory nested-before-limits order
|
|
328
|
+
for the already-migrated `bidmodifiers get`. No module lacking both a nested
|
|
329
|
+
option and a `criteria_limits`/`require_criteria_message` is affected.
|
|
330
|
+
- No CLI surface change: `--help` (option order, all nested-field options, the
|
|
331
|
+
`--is-archived` choice/default), `--dry-run` payloads, and the error-precedence
|
|
332
|
+
edge cases (empty-nested vs. no-filter and vs. over-limit) are byte-identical,
|
|
333
|
+
verified against 33 pre-migration baselines captured via CliRunner from the
|
|
334
|
+
pre-migration tree. As with every prior `make_get_command` migration, the
|
|
335
|
+
factory resolves the API client only on the live path, so these five `get`s
|
|
336
|
+
now honor `--dry-run` as a token-free test seam (no behavior change vs. the
|
|
337
|
+
other factory-backed commands).
|
|
338
|
+
- Two Group-4 `get`s stay hand-rolled as documented carve-outs (see `_get.py`):
|
|
339
|
+
`ads get` (its `TextAdFieldNames` is always emitted with a per-field default,
|
|
340
|
+
like the carved-out `keywordbids`) and `campaigns get` (its `--fields` rejects
|
|
341
|
+
an explicitly empty CSV instead of falling back to the default `FieldNames`).
|
|
342
|
+
|
|
343
|
+
**Internal — `make_get_command` covers the criteria-limit `get`s `bids` / `bidmodifiers` (completes #587):**
|
|
344
|
+
|
|
345
|
+
- `bids get` (criteria-limit + require-at-least-one-filter, no `--ids`) and
|
|
346
|
+
`bidmodifiers get` (criteria-limit + 13 optional nested `*FieldNames`
|
|
347
|
+
projections + a defaulted `--levels` criteria) now register through the
|
|
348
|
+
shared `make_get_command` factory, reusing its existing `criteria_limits`,
|
|
349
|
+
`require_criteria_message` and `nested_field_options` hooks (no new factory
|
|
350
|
+
parameters). Net −230 lines.
|
|
351
|
+
- No CLI surface change: `--help` (option order, the 13 nested-field options,
|
|
352
|
+
the `--levels` choice/default), `--dry-run` payloads (the criteria-limit
|
|
353
|
+
enforcement, the `bids get` require-filter `UsageError`, the always-present
|
|
354
|
+
`Levels` criteria and nested-field params) are byte-identical (verified
|
|
355
|
+
against pre-migration baselines).
|
|
356
|
+
- This closes the migratable surface of #587. The three remaining `get`s stay
|
|
357
|
+
hand-rolled as documented carve-outs, each for a structural reason the shared
|
|
358
|
+
factory deliberately does not encode: `leads get` (`--datetime-from` /
|
|
359
|
+
`--datetime-to` sit between `--limit` and `--fetch-all` — a bespoke option
|
|
360
|
+
order), `keywordbids get` (its nested `SearchFieldNames` / `NetworkFieldNames`
|
|
361
|
+
carry their own per-field defaults and are always emitted, unlike the factory's
|
|
362
|
+
provided-only nested projections), and `reports get` (a custom non-RPC TSV
|
|
363
|
+
stream, not a JSON-RPC `get`).
|
|
364
|
+
|
|
365
|
+
**Internal — `make_get_command` covers the no-`--ids` / wire-quirk `get`s (part of #587):**
|
|
366
|
+
|
|
367
|
+
- `agencyclients get` (no `--ids`; filters by `--logins` / `--archived`) and
|
|
368
|
+
`adextensions get` now register through the shared `make_get_command` factory.
|
|
369
|
+
The factory gained two more optional parameters: `include_ids` (set `False`
|
|
370
|
+
for resources with no id filter — the command supplies a custom
|
|
371
|
+
`criteria_builder` from its own options) and `adextensions_wire_layout` (a
|
|
372
|
+
single-command flag that reproduces adextensions' exact recorded wire layout:
|
|
373
|
+
always emit `SelectionCriteria` even when empty, and order nested
|
|
374
|
+
`*FieldNames` before `Page`). The empty `SelectionCriteria` is a recorded API
|
|
375
|
+
contract — the `adextensions_get` read cassette replays it and the API accepts
|
|
376
|
+
it, unlike adgroups/ads/keywords.
|
|
377
|
+
- No CLI surface change: `--help`, `--dry-run` payloads (incl. the empty-criteria
|
|
378
|
+
and nested-before-`Page` cases) and the read cassettes are byte-identical
|
|
379
|
+
(verified against pre-migration baselines). Remaining carve-out: `leads get`,
|
|
380
|
+
whose `--datetime-from` / `--datetime-to` options sit between `--limit` and
|
|
381
|
+
`--fetch-all` — a bespoke option order that doesn't fit the shared
|
|
382
|
+
`get_options` stack, so it stays hand-rolled.
|
|
383
|
+
|
|
384
|
+
**Internal — `make_get_command` covers the nested-`*FieldNames` `get`s (part of #587):**
|
|
385
|
+
|
|
386
|
+
- `adimages get`, `retargeting get`, `sitelinks get`, `feeds get` and
|
|
387
|
+
`clients get` now register through the shared `make_get_command` factory
|
|
388
|
+
(net −175 lines). The factory gained three optional, generic parameters:
|
|
389
|
+
`nested_field_options` (a tuple of `(flag, WSDL key, help)` for nested
|
|
390
|
+
`*FieldNames` projections, parsed via the existing `parse_nested_field_names`
|
|
391
|
+
and rendered between `--fields` and `--dry-run`), `ids_criteria_key` (the
|
|
392
|
+
SelectionCriteria key for `--ids`; `clients` uses `ClientIds`), and
|
|
393
|
+
`fields_help` (a resource-specific `--fields` wording, e.g. sitelinks'
|
|
394
|
+
"Comma-separated SitelinksSet FieldNames"). The shared `get_options` decorator
|
|
395
|
+
(`utils.py`) gained matching `nested_options` / `fields_help` hooks, both
|
|
396
|
+
defaulting to current behavior so its other callers are unchanged.
|
|
397
|
+
- No CLI surface change: `--help` (option order, the nested-field option
|
|
398
|
+
position, the custom `--fields` help), `--dry-run` payloads (including the
|
|
399
|
+
`ClientIds` key and nested-field params), and the provided-but-empty
|
|
400
|
+
`*FieldNames` `UsageError` are byte-identical (verified against
|
|
401
|
+
pre-migration baselines). Deferred to a follow-up: `adextensions` (emits an
|
|
402
|
+
empty `SelectionCriteria` + Page-before-nested key order), and `leads` /
|
|
403
|
+
`agencyclients` (no `--ids`; bespoke required criteria).
|
|
404
|
+
|
|
405
|
+
**Internal — `make_get_command` covers the criteria-limit ad-target `get`s (part of #587):**
|
|
406
|
+
|
|
407
|
+
- `dynamicads get`, `smartadtargets get` and `dynamicfeedadtargets get` — three
|
|
408
|
+
byte-for-byte identical command bodies — now register through the shared
|
|
409
|
+
`make_get_command` factory (`direct_cli/commands/_get.py`) instead of hand-rolled
|
|
410
|
+
copies (net −131 lines). The factory gained two optional, generic parameters:
|
|
411
|
+
`criteria_limits` (forwarded to `enforce_criteria_array_limits`, command name
|
|
412
|
+
derived as `"<group> get"`) and `require_criteria_message` (the "provide at
|
|
413
|
+
least one filter" guard), plus a shared `ids_adgroup_campaign_states_criteria`
|
|
414
|
+
builder for the `Ids`/`AdGroupIds`/`CampaignIds`/`States` selection shape.
|
|
415
|
+
- No CLI surface change: `--help`, `--dry-run` payloads, the over-limit and
|
|
416
|
+
empty-criteria errors, and module patchability are byte-identical (verified
|
|
417
|
+
against pre-migration baselines). Group 2 (nested `*FieldNames`) and the
|
|
418
|
+
no-`--ids` commands (`bids`, `keywordbids`, `bidmodifiers`) follow in a
|
|
419
|
+
separate PR.
|
|
420
|
+
|
|
421
|
+
**Features — human-readable `text` output for reference commands (#578):**
|
|
422
|
+
|
|
423
|
+
- Local reference commands now default to a human-readable `text` format instead
|
|
424
|
+
of raw JSON: `trackingparams` (dynamic tracking-parameter reference) and
|
|
425
|
+
`dictionaries list-names` (previously hardcoded JSON with no `--format`). Both
|
|
426
|
+
expose `--format {text,json,table,csv,tsv}` / `--output` via the new shared
|
|
427
|
+
`reference_output_options` decorator (sibling of `v4_output_options`, default
|
|
428
|
+
`text`). API-data commands keep their `json` default.
|
|
429
|
+
- Global `output.py` fix: `--format table/csv/tsv` now render a list of scalar
|
|
430
|
+
values (e.g. dictionary names) as a single `Value` column instead of a Python
|
|
431
|
+
`repr` (`['Currencies', …]`) / empty output. Affects any caller that passes a
|
|
432
|
+
scalar list, not just the reference commands.
|
|
433
|
+
|
|
434
|
+
**Features — preflight `SelectionCriteria` array caps on remaining read-get commands (#571):**
|
|
435
|
+
|
|
436
|
+
- `ads`, `adgroups`, `bids`, `bidmodifiers`, `campaigns`, `keywords`,
|
|
437
|
+
`dynamicfeedadtargets`, `audiencetargets` `get` now reject over-long
|
|
438
|
+
`SelectionCriteria` arrays before the request and surface the array name and
|
|
439
|
+
ceiling instead of the opaque API `error_code=4001`. Extends the
|
|
440
|
+
`enforce_criteria_array_limits` discipline added in #555 to the rest of the
|
|
441
|
+
read-get surface. Per-filter caps were measured live against the Yandex Direct
|
|
442
|
+
sandbox on 2026-06-17 (see `scripts/measure_criteria_limits.py` and the
|
|
443
|
+
`*_GET_CRITERIA_LIMITS` constant in each command module for the exact
|
|
444
|
+
transcript).
|
|
445
|
+
- Closes a downstream consistency gap: the MCP plugin
|
|
446
|
+
(`axisrow/yandex-direct-mcp-plugin#201`) can now drop its own batch-size
|
|
447
|
+
guard and forward the CLI error.
|
|
448
|
+
|
|
3
449
|
## 0.4.3
|
|
4
450
|
|
|
451
|
+
**Fixes — known limitation: clearing a carousel AdImageHash (#574):**
|
|
452
|
+
|
|
453
|
+
- `ads update --clear-image-hash` builds a valid scalar `AdImageHash: null`
|
|
454
|
+
(visible in `--dry-run`), but the live v5 API can reject it with `Error 5005`
|
|
455
|
+
(`adImageHash=<[<null>]>`). Root cause: the ad carries a **server-side
|
|
456
|
+
carousel image structure** that the documented `Ads.update` API does not
|
|
457
|
+
expose — `TextAd` has only a single `AdImageHash` (no `Carousel` field exists
|
|
458
|
+
anywhere in the v5 WSDL). Such a carousel image cannot be cleared through the
|
|
459
|
+
API; it can only be removed in the Direct web interface. The CLI now surfaces
|
|
460
|
+
a readable hint on `5005`/`AdImageHash` (explaining the carousel cause and the
|
|
461
|
+
workaround) instead of the opaque error. The request payload is unchanged —
|
|
462
|
+
the scalar null the CLI sends is correct; the rejection is the server's.
|
|
463
|
+
Workaround: replace the image with a single `--image-hash <other>`, which the
|
|
464
|
+
API accepts even on an active ad.
|
|
465
|
+
|
|
5
466
|
**Features — batch `ads add` via `--from-file` / `--ads-json` (#562, #558 follow-up):**
|
|
6
467
|
|
|
7
468
|
- `ads add` now accepts a batch of flag-form ad rows from a JSONL file
|
|
@@ -10,7 +10,8 @@ CLI for the Yandex Direct API, built with Python and Click. Installed via pip, p
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
pip install -e ".[dev]" # Install in dev mode
|
|
13
|
-
pytest #
|
|
13
|
+
pytest # Offline tests, parallel via xdist (no token needed)
|
|
14
|
+
pytest -n0 # Same offline tests, sequential (for pdb / -s / debugging)
|
|
14
15
|
pytest -m integration -v # Integration tests (needs .env with token)
|
|
15
16
|
pytest tests/test_cli.py::TestCLI::test_cli_help # Single test
|
|
16
17
|
pytest -k "campaigns" # Pattern match
|
|
@@ -22,6 +23,8 @@ flake8 direct_cli tests # Lint
|
|
|
22
23
|
|
|
23
24
|
Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli/commands/` with a Click group and subcommands (`get`, `add`, `update`, `delete`, `suspend`, `resume`, etc.). All groups registered in `direct_cli/cli.py` via `cli.add_command()`.
|
|
24
25
|
|
|
26
|
+
**Campaigns split (issue #602):** `campaigns.py` is being decomposed incrementally. Step 1 moved all shared constants, validators, payload builders and the reusable TextCampaign strategy option groups into a sibling `direct_cli/commands/_campaigns_base.py`; `campaigns.py` re-imports every name, so the CLI surface is unchanged. Subsequent steps will move per-campaign-type logic (`text`, `unified`, `dynamic`, `smart`, `mobile_app`, `cpm_banner`) into the same package — never edit shared helpers in `campaigns.py`, edit `_campaigns_base.py`.
|
|
27
|
+
|
|
25
28
|
**Request flow:** `cli.py` → `auth.py` (resolves token/login) → `api.py` (`create_client`) → `tapi_yandex_direct.YandexDirect` → Yandex API → `output.py` (format/print).
|
|
26
29
|
|
|
27
30
|
**Credentials priority (CLI):** explicit CLI flags (`--token`, `--login`, `--profile`) > base env/current working directory `.env` (`YANDEX_DIRECT_TOKEN`, `YANDEX_DIRECT_LOGIN`) > active profile from `direct auth login` / `direct auth use` > 1Password/Bitwarden refs. Explicit `--profile` is isolated and does not fall back to base `.env` login. See `direct_cli/auth.py` (`get_credentials`) and README table for the full chain.
|
|
@@ -30,7 +33,9 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
|
|
|
30
33
|
|
|
31
34
|
**Shared utilities** (`utils.py`): `parse_ids`, `parse_json`, `build_selection_criteria`, `build_common_params`, `get_default_fields`, `COMMON_FIELDS` dict. All command modules import from here — don't duplicate.
|
|
32
35
|
|
|
33
|
-
**Output** (`output.py`): `format_output()` supports json (default), table, csv, tsv. Colored helpers: `print_success`, `print_error`, `print_warning`, `print_info`.
|
|
36
|
+
**Output** (`output.py`): `format_output()` supports json (default), table, csv, tsv, and `text` (human-readable plain blocks via `format_text`). Colored helpers: `print_success`, `print_error`, `print_warning`, `print_info`.
|
|
37
|
+
|
|
38
|
+
**Reference commands** (local value lists / templates, e.g. `trackingparams`, `dictionaries list-names`) default to the human-readable `text` format and expose `--format {text,json,table,csv,tsv}` / `--output` via the shared `reference_output_options` decorator (`utils.py`, sibling of `v4_output_options`). API-data commands keep `json` default. New reference commands should use this decorator.
|
|
34
39
|
|
|
35
40
|
## Key Conventions
|
|
36
41
|
|
|
@@ -50,7 +55,7 @@ Click group-of-groups. Each Yandex Direct API resource = one file in `direct_cli
|
|
|
50
55
|
- runtime-deprecated methods (see `RUNTIME_DEPRECATED_METHODS`);
|
|
51
56
|
- v4 methods that have no v5 WSDL (covered by `direct_cli/v4_contracts.py`);
|
|
52
57
|
- custom non-RPC endpoints (e.g. `reports.get` — TSV stream);
|
|
53
|
-
- methods explicitly covered by `
|
|
58
|
+
- methods explicitly covered by a `test_<service>_<op>_payload` test in the `tests/test_dry_run_*.py` module suite.
|
|
54
59
|
|
|
55
60
|
A guard in `tests/test_api_coverage.py::test_dry_run_exclusions_have_no_helper_or_legacy_rationale` enforces this — any rationale outside those five categories that uses the banned phrasing is a mis-classification: write a `PAYLOAD_CASES` fixture instead. See post-mortem in issue #199.
|
|
56
61
|
|
|
@@ -78,6 +83,12 @@ Adding a new mutating command requires extending `COMMAND_WSDL_MAP` in `tests/te
|
|
|
78
83
|
|
|
79
84
|
**Docs/cache freshness guard.** `direct_cli.reports_coverage.fetch_reports_spec` and `direct_cli.wsdl_coverage.fetch_wsdl` both refuse responses that look like a Yandex SmartCaptcha gate (markers `showcaptcha`, `smartcaptcha`, `<title>Captcha`) or are suspiciously short (<30 KB for HTML, <3 KB for WSDL). The matching tests `test_reports_cache_files_are_real_content` and `test_wsdl_cache_files_are_real_content` enforce the same invariant on committed cache files.
|
|
80
85
|
|
|
86
|
+
**`direct masters` cookie decryption (#634).** On macOS, Chrome's cookie AES key lives *only* in the login Keychain (`Chrome Safe Storage`) — never in `Local State`, whose `os_crypt.encrypted_key` field is populated on Windows only. `direct_cli/browser/_chrome_crypto.py` reads the Keychain password itself, derives the AES-128-CBC key (PBKDF2-HMAC-SHA1, Chromium's `saltysalt`/1003-iteration constants), and decrypts only the Yandex Direct cookies it needs — it does not copy the user's Chrome profile or rely on a real Chrome to decrypt anything. `direct_cli/browser/session.py::open_chrome_session` injects the result via Playwright's `add_cookies()` into a bundled Chromium context. The same marker-registry pattern used for captchas (`_captcha.py`) covers Yandex's login-page markers (`session.py::assert_authenticated`), so an expired/wrong-account session is reported explicitly instead of surfacing as a `networkidle` timeout.
|
|
87
|
+
|
|
88
|
+
**`direct playwright login`/`doctor` — persisted browser session.** `open_chrome_session` above decrypts from Chrome on every call; `direct playwright login` (`direct_cli/commands/browser_session.py`) instead decrypts once via `session.py::capture_storage_state` and persists the result as a Playwright `storage_state` file (`direct_cli/browser/store.py`, `~/.direct-cli/playwright/session.json`, `0600` inside `0700`, sibling of `auth.py`'s `AUTH_STORE_PATH`). `direct masters`' `_open_session` (`commands/masters.py`) then prefers this saved session (`session.py::open_saved_session`) over a fresh Keychain decrypt, falling back automatically when no session is saved, an explicit `--profile-dir`/`--chrome-profile` was passed, or the saved session turns out to be stale (`BrowserAuthError` triggers one retry via fresh decrypt). `direct playwright doctor` (`direct_cli/browser/diagnostics.py`) is a **read-only** diagnostic over the whole chain (playwright/cryptography installed, chromium downloaded, Chrome profile found, Keychain key derivable, cookies decryptable, saved session present/fresh) — it never logs in, never launches a browser of its own, and never writes to disk; that invariant is enforced by contract tests in `tests/test_playwright_session.py`.
|
|
89
|
+
|
|
90
|
+
**`direct masters list` reads the grid's JSON API, not its DOM (#639).** Live diagnosis found the campaigns grid (`https://direct.yandex.ru/dna/grid/campaigns/`) is a virtualized SPA that never renders a `a[href*='/wizard/campaigns/']` anchor, so the original DOM-scraping approach could never have worked. `direct_cli/browser/masters.py::fetch_masters_list` instead intercepts the grid's own data call — `POST /web-api/grid/api?operationName=GridCampaigns` — via `page.on("response", ...)`, replays it through `page.request.post(...)` (with the captured headers) to paginate past its 200-row `GRID_PAGE_LIMIT`, and never hand-assembles the GraphQL query itself (it is several KB of fragments that would drift out of sync with Yandex's schema). Rows created via Мастер кампаний have no distinguishing `type`/`metaType`/`__typename` — the one field that does discriminate them, confirmed live, is `source == "UAC"` (`MASTERS_SOURCE`). `status.primaryStatus` is filtered client-side via `STATUS_FILTERS` (`masters list --status`, default `not-archived`) because the grid's `status-filter` URL query parameter is silently ignored server-side. `masters` has **no `--login`/agency support**: passing your own login as Yandex's managed-client `ulogin` parameter produces HTTP 401 "Доступ ограничен" on the grid's data calls (confirmed live) — both `list` and `get` build URLs without `ulogin`, and `masters` is in `cli.py`'s `_NO_CREDENTIALS_GROUPS`.
|
|
91
|
+
|
|
81
92
|
## PyPI Release
|
|
82
93
|
|
|
83
94
|
Release is two-phase on purpose. Yandex frequently rate-limits the docs host with a SmartCaptcha gateway, which is an external rate-limit on our IP — not evidence that an URL is gone. Mixing docs-health checks into the publish path made releases non-deterministic, so they live in a separate preflight script.
|
|
@@ -105,6 +116,8 @@ Builds dist artifacts, runs twine checks, uploads to TestPyPI + PyPI. Does **not
|
|
|
105
116
|
## Tests
|
|
106
117
|
|
|
107
118
|
- **Unit** (`test_cli.py`, `test_comprehensive.py`) — no API calls, no token needed.
|
|
119
|
+
- **Dry-run payload tests** (`tests/test_dry_run_*.py`) — split by command type (issue #604): `test_dry_run.py` is the guard module (rationale + a `test_dry_run_module_is_importable` sanity check); `test_dry_run_shared.py` holds the `_dry_run`/`_read_dry_run`/`_rejected`/`_failing_run`/`_ids_csv`/`_write_jsonl` invocation helpers; `test_dry_run_<resource>.py` modules hold the per-service payload assertions. Add a new dry-run test to the owning module and import the helpers from `test_dry_run_shared`.
|
|
120
|
+
- **Parallel by default:** `addopts` runs the offline tier with `-n auto` (pytest-xdist) across CPU cores; the suite is process-parallel-safe (env/cwd via `monkeypatch`, filesystem via `tmp_path`, the shared orphan store only in the excluded live tiers). Use `pytest -n0` to run sequentially for `pdb`/`-s`/debugging. The live tiers (`integration`/`v4_live_read`/`integration_live_write`) are **not** parallel-safe (real API + shared `~/.direct-cli` orphan store + ordered resource lifecycles); a `pytest_xdist_auto_num_workers` hook in `tests/conftest.py` auto-resolves `-n auto` to a single worker whenever a live marker is selected, so an explicit `pytest -m integration_live_write` stays serial.
|
|
108
121
|
- **Integration** (`test_integration.py`, `@pytest.mark.integration`) — require `.env` with `YANDEX_DIRECT_TOKEN` and `YANDEX_DIRECT_LOGIN`. Auto-skip if absent.
|
|
109
122
|
- **Credential resolution in tests:** env vars/current working directory `.env` first, then active `direct auth` profile, then skip. This matches the safe CLI default for base env vs. active profile: a developer machine with an active profile must not silently hit production on a plain `pytest`.
|
|
110
123
|
|