direct-cli 0.0.0__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.
Files changed (56) hide show
  1. direct_cli-0.0.0/.env.example +7 -0
  2. direct_cli-0.0.0/.github/copilot-instructions.md +91 -0
  3. direct_cli-0.0.0/.gitignore +57 -0
  4. direct_cli-0.0.0/AGENTS.md +382 -0
  5. direct_cli-0.0.0/MANIFEST.in +3 -0
  6. direct_cli-0.0.0/PKG-INFO +393 -0
  7. direct_cli-0.0.0/README.md +355 -0
  8. direct_cli-0.0.0/direct_cli/__init__.py +14 -0
  9. direct_cli-0.0.0/direct_cli/api.py +94 -0
  10. direct_cli-0.0.0/direct_cli/auth.py +58 -0
  11. direct_cli-0.0.0/direct_cli/cli.py +85 -0
  12. direct_cli-0.0.0/direct_cli/commands/__init__.py +61 -0
  13. direct_cli-0.0.0/direct_cli/commands/adextensions.py +96 -0
  14. direct_cli-0.0.0/direct_cli/commands/adgroups.py +189 -0
  15. direct_cli-0.0.0/direct_cli/commands/adimages.py +63 -0
  16. direct_cli-0.0.0/direct_cli/commands/ads.py +306 -0
  17. direct_cli-0.0.0/direct_cli/commands/agencyclients.py +64 -0
  18. direct_cli-0.0.0/direct_cli/commands/audiencetargets.py +187 -0
  19. direct_cli-0.0.0/direct_cli/commands/bidmodifiers.py +110 -0
  20. direct_cli-0.0.0/direct_cli/commands/bids.py +108 -0
  21. direct_cli-0.0.0/direct_cli/commands/businesses.py +61 -0
  22. direct_cli-0.0.0/direct_cli/commands/campaigns.py +311 -0
  23. direct_cli-0.0.0/direct_cli/commands/changes.py +97 -0
  24. direct_cli-0.0.0/direct_cli/commands/clients.py +98 -0
  25. direct_cli-0.0.0/direct_cli/commands/creatives.py +68 -0
  26. direct_cli-0.0.0/direct_cli/commands/dictionaries.py +64 -0
  27. direct_cli-0.0.0/direct_cli/commands/dynamicads.py +104 -0
  28. direct_cli-0.0.0/direct_cli/commands/feeds.py +99 -0
  29. direct_cli-0.0.0/direct_cli/commands/keywordbids.py +111 -0
  30. direct_cli-0.0.0/direct_cli/commands/keywords.py +309 -0
  31. direct_cli-0.0.0/direct_cli/commands/keywordsresearch.py +71 -0
  32. direct_cli-0.0.0/direct_cli/commands/leads.py +65 -0
  33. direct_cli-0.0.0/direct_cli/commands/negativekeywordsharedsets.py +97 -0
  34. direct_cli-0.0.0/direct_cli/commands/reports.py +128 -0
  35. direct_cli-0.0.0/direct_cli/commands/retargeting.py +104 -0
  36. direct_cli-0.0.0/direct_cli/commands/sitelinks.py +92 -0
  37. direct_cli-0.0.0/direct_cli/commands/smartadtargets.py +104 -0
  38. direct_cli-0.0.0/direct_cli/commands/turbopages.py +97 -0
  39. direct_cli-0.0.0/direct_cli/commands/vcards.py +93 -0
  40. direct_cli-0.0.0/direct_cli/output.py +143 -0
  41. direct_cli-0.0.0/direct_cli/utils.py +120 -0
  42. direct_cli-0.0.0/direct_cli.egg-info/PKG-INFO +393 -0
  43. direct_cli-0.0.0/direct_cli.egg-info/SOURCES.txt +54 -0
  44. direct_cli-0.0.0/direct_cli.egg-info/dependency_links.txt +1 -0
  45. direct_cli-0.0.0/direct_cli.egg-info/entry_points.txt +2 -0
  46. direct_cli-0.0.0/direct_cli.egg-info/not-zip-safe +1 -0
  47. direct_cli-0.0.0/direct_cli.egg-info/requires.txt +12 -0
  48. direct_cli-0.0.0/direct_cli.egg-info/top_level.txt +1 -0
  49. direct_cli-0.0.0/pyproject.toml +85 -0
  50. direct_cli-0.0.0/scripts/release_pypi.sh +116 -0
  51. direct_cli-0.0.0/setup.cfg +4 -0
  52. direct_cli-0.0.0/setup.py +65 -0
  53. direct_cli-0.0.0/tests/__init__.py +1 -0
  54. direct_cli-0.0.0/tests/test_cli.py +65 -0
  55. direct_cli-0.0.0/tests/test_comprehensive.py +187 -0
  56. direct_cli-0.0.0/tests/test_integration.py +239 -0
@@ -0,0 +1,7 @@
1
+ YANDEX_DIRECT_TOKEN=your_access_token_here
2
+ YANDEX_DIRECT_LOGIN=your_yandex_login_here
3
+
4
+ # PyPI publishing (used by scripts/release_pypi.sh)
5
+ TWINE_USERNAME=__token__
6
+ TEST_PYPI_TOKEN=pypi-...
7
+ PYPI_TOKEN=pypi-...
@@ -0,0 +1,91 @@
1
+ # Copilot Instructions — Direct CLI
2
+
3
+ Command-line interface for the Yandex Direct API, built with Python and Click.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ # Install in dev mode
9
+ pip install -e ".[dev]"
10
+
11
+ # Run all unit tests
12
+ pytest
13
+
14
+ # Run integration tests (requires .env with real token)
15
+ pytest -m integration -v
16
+
17
+ # Run a single test
18
+ pytest tests/test_cli.py::TestCLI::test_cli_help
19
+
20
+ # Run tests matching pattern
21
+ pytest -k "campaigns"
22
+
23
+ # Format
24
+ black .
25
+
26
+ # Lint
27
+ flake8 direct_cli tests
28
+ ```
29
+
30
+ ## Architecture
31
+
32
+ The CLI is a Click group-of-groups. Each API resource maps to one file in `direct_cli/commands/` that defines a Click group (e.g. `campaigns`) with subcommands (`get`, `add`, `update`, `delete`, etc.). All groups are imported and registered in `direct_cli/cli.py`.
33
+
34
+ Request flow: `cli.py` → `auth.py` (resolves token/login) → `api.py` (`create_client`) → `tapi_yandex_direct.YandexDirect` → Yandex Direct API → `output.py` (format and print).
35
+
36
+ `utils.py` holds shared helpers: `parse_ids`, `parse_json`, `build_selection_criteria`, `build_common_params`, `get_default_fields`. All command modules import from these; don't duplicate logic there.
37
+
38
+ ## Key Conventions
39
+
40
+ **Adding a new command module:**
41
+ 1. Create `direct_cli/commands/<resource>.py` with a `@click.group()` and subcommands.
42
+ 2. Import and register it in `direct_cli/cli.py` with `cli.add_command(...)`.
43
+ 3. Add the command name to `TestCommandsRegistered.EXPECTED_COMMANDS` in `tests/test_comprehensive.py`.
44
+
45
+ **`--dry-run` flag:** `add` and `update` commands support `--dry-run` — they print the request body as JSON without making an API call. Use this as a test seam: these subcommands can be tested without mocking the HTTP layer.
46
+
47
+ **SelectionCriteria requirements:** Some resources (`adgroups`, `ads`, `keywords`) require at least one of `Ids`, `CampaignIds`, or `AdGroupIds` in `SelectionCriteria`. Calling `get` without any filter raises API error 4001. Integration tests handle this by fetching a campaign ID first via `get_first_campaign_id()`.
48
+
49
+ **Default fields in `utils.py`:** `COMMON_FIELDS` maps resource names to default `FieldNames`. Not all fields are valid for all resources — for example, `adimages` uses `AdImageHash` (not `Id`) as its key, and neither `clients` nor `creatives` accept `Status`. When adding a resource, verify field names against the Yandex Direct API docs.
50
+
51
+ **Error handling in commands:** All command functions wrap API calls in `try/except Exception` and call `print_error(str(e))` + `raise click.Abort()`. Keep this pattern consistent.
52
+
53
+ **Test types:**
54
+ - Unit tests (`tests/test_cli.py`, `tests/test_comprehensive.py`) — no API calls, run without a token.
55
+ - Integration tests (`tests/test_integration.py`, `@pytest.mark.integration`) — require `.env` with `YANDEX_DIRECT_TOKEN` and `YANDEX_DIRECT_LOGIN`. Auto-skip if token is absent.
56
+
57
+ **Credentials:** `.env` in the project root. `YANDEX_DIRECT_LOGIN` is the Yandex advertiser login (required). `load_dotenv()` is called at `cli.py` module import, so it is loaded before any Click invocation.
58
+
59
+ ## Dangerous Commands — Do Not Auto-Test
60
+
61
+ Never invoke these commands in automated tests against a real account.
62
+
63
+ ### 🔴 Irreversible — permanently destroy data
64
+ | Command | Risk |
65
+ |---------|------|
66
+ | `campaigns delete` | Permanently deletes a campaign and all its content |
67
+ | `adgroups delete` | Permanently deletes an ad group and its ads/keywords |
68
+ | `ads delete` | Permanently deletes an ad |
69
+ | `keywords delete` | Permanently deletes a keyword |
70
+ | `audiencetargets delete` | Permanently deletes an audience target |
71
+
72
+ ### 🟠 Financial impact — change bids or spending
73
+ | Command | Risk |
74
+ |---------|------|
75
+ | `bids set` | Changes search/network bids on campaigns — direct cost impact |
76
+ | `keywordbids set` | Changes per-keyword bids |
77
+ | `bidmodifiers set` | Changes bid multipliers (device, region, time, etc.) |
78
+
79
+ ### 🟡 Reversible but affect live traffic
80
+ `campaigns suspend/resume/archive/unarchive`, `ads suspend/resume/archive/unarchive`, `keywords suspend/resume/archive/unarchive`, `audiencetargets suspend/resume`
81
+
82
+ ### 🟡 Account-wide mutations
83
+ `clients update` — modifies account-level settings.
84
+
85
+ ### 🟡 Content creation (hard to clean up in bulk)
86
+ All `add` and `update` subcommands across: `campaigns`, `adgroups`, `ads`, `keywords`, `feeds`, `retargeting`, `sitelinks`, `turbopages`, `vcards`, `adextensions`, `negativekeywordsharedsets`, `smartadtargets`, `dynamicads`, `audiencetargets`.
87
+
88
+ These can be safely tested using `--dry-run` (outputs the request body as JSON without sending it).
89
+
90
+ ### ✅ Safe to auto-test (read-only, no side effects)
91
+ All `get` subcommands, plus: `changes check*`, `dictionaries list-names`, `keywordsresearch has-search-volume`, `reports list-types`.
@@ -0,0 +1,57 @@
1
+ # Byte-compiled / optimized / DLLs
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64
19
+ parts/
20
+ sdist/
21
+ *.egg-info/
22
+ .installed.txt
23
+ pip-log.txt
24
+ Pipfile
25
+ MANIFEST
26
+
27
+ # Unit test / coverage reports
28
+ htmlcov/
29
+ .tox/
30
+ .nox
31
+ .coverate
32
+ .cache
33
+ nosetests.xml
34
+ coverage.xml
35
+ *.cover
36
+
37
+ # Environments
38
+ .env
39
+ .venv
40
+
41
+ # IDE
42
+ .vscode/
43
+ .idea/
44
+ *.swp
45
+ *.swo
46
+ *~
47
+
48
+ # OS
49
+ .DS_Store
50
+ Thumbs.db
51
+
52
+ # Temporary files
53
+ *.tmp
54
+ *.temp
55
+ *.log
56
+ *.bak
57
+ *.orig
@@ -0,0 +1,382 @@
1
+ # AGENTS.md - Guidelines for AI Coding Agents
2
+
3
+ This document provides essential information for AI coding agents working on the Direct CLI codebase.
4
+
5
+ ## Project Overview
6
+
7
+ Direct CLI is a command-line interface for the Yandex Direct API, built with Python and Click. It provides commands for managing campaigns, ad groups, ads, keywords, reports, and other Yandex Direct resources.
8
+
9
+ ## Build, Test, and Lint Commands
10
+
11
+ ### Installation
12
+
13
+ ```bash
14
+ # Install package in development mode
15
+ pip install -e .
16
+
17
+ # Install with development dependencies
18
+ pip install -e ".[dev]"
19
+ ```
20
+
21
+ ### Testing
22
+
23
+ ```bash
24
+ # Run all tests
25
+ pytest
26
+
27
+ # Run all tests with verbose output
28
+ pytest -v
29
+
30
+ # Run a single test file
31
+ pytest tests/test_cli.py
32
+
33
+ # Run a single test by name
34
+ pytest tests/test_cli.py::TestCLI::test_cli_help
35
+
36
+ # Run tests matching a pattern
37
+ pytest -k "campaigns"
38
+
39
+ # Run tests with coverage
40
+ pytest --cov=direct_cli
41
+
42
+ # Run tests with coverage report
43
+ pytest --cov=direct_cli --cov-report=html
44
+ ```
45
+
46
+ ### Code Quality
47
+
48
+ ```bash
49
+ # Format code with Black
50
+ black .
51
+
52
+ # Format specific files
53
+ black direct_cli/ tests/
54
+
55
+ # Check formatting without changes
56
+ black --check .
57
+
58
+ # Lint with flake8
59
+ flake8 direct_cli tests
60
+
61
+ # Lint specific file
62
+ flake8 direct_cli/cli.py
63
+ ```
64
+
65
+ ### Building
66
+
67
+ ```bash
68
+ # Build package
69
+ python -m build
70
+
71
+ # Build wheel only
72
+ pip wheel . --no-deps -w dist/
73
+ ```
74
+
75
+ ## Code Style Guidelines
76
+
77
+ ### Import Organization
78
+
79
+ Organize imports in three groups, separated by blank lines:
80
+
81
+ ```python
82
+ # 1. Standard library imports (alphabetical)
83
+ import json
84
+ import os
85
+ from datetime import datetime
86
+ from typing import Any, Dict, List, Optional, Tuple
87
+
88
+ # 2. Third-party imports (alphabetical)
89
+ import click
90
+ from dotenv import load_dotenv
91
+
92
+ # 3. Local imports (alphabetical)
93
+ from .api import create_client
94
+ from .auth import get_credentials
95
+ from .output import format_output, print_error
96
+ from .utils import parse_ids, build_selection_criteria
97
+ ```
98
+
99
+ ### Type Hints
100
+
101
+ Always use type hints for function parameters and return values:
102
+
103
+ ```python
104
+ def parse_ids(ids_str: Optional[str]) -> Optional[List[int]]:
105
+ """Parse comma-separated IDs"""
106
+ if not ids_str:
107
+ return None
108
+ return [int(x.strip()) for x in ids_str.split(",")]
109
+
110
+
111
+ def create_client(
112
+ token: Optional[str] = None,
113
+ login: Optional[str] = None,
114
+ sandbox: bool = False,
115
+ ) -> YandexDirect:
116
+ """Create YandexDirect client"""
117
+ ...
118
+ ```
119
+
120
+ ### Docstrings
121
+
122
+ Use descriptive docstrings with Args, Returns, and Raises sections:
123
+
124
+ ```python
125
+ def get_credentials(
126
+ token: Optional[str] = None,
127
+ login: Optional[str] = None,
128
+ env_path: Optional[str] = None,
129
+ ) -> Tuple[str, Optional[str]]:
130
+ """
131
+ Get credentials with priority:
132
+ 1. Direct arguments
133
+ 2. Environment variables
134
+ 3. .env file
135
+
136
+ Args:
137
+ token: API access token
138
+ login: Client login (for agency accounts)
139
+ env_path: Path to .env file
140
+
141
+ Returns:
142
+ Tuple of (token, login)
143
+
144
+ Raises:
145
+ ValueError: If token is not provided
146
+ """
147
+ ...
148
+ ```
149
+
150
+ ### Naming Conventions
151
+
152
+ - **Functions and variables**: `snake_case`
153
+ - **Classes**: `PascalCase`
154
+ - **Constants**: `UPPER_SNAKE_CASE`
155
+ - **CLI command names**: `kebab-case` (e.g., `get-campaigns`, `add-adgroup`)
156
+
157
+ ### Error Handling
158
+
159
+ Use try/except blocks with `print_error()` and `raise click.Abort()`:
160
+
161
+ ```python
162
+ try:
163
+ client = create_client(
164
+ token=ctx.obj.get("token"),
165
+ login=ctx.obj.get("login"),
166
+ sandbox=ctx.obj.get("sandbox"),
167
+ )
168
+ result = client.campaigns().post(data=body)
169
+ format_output(result().extract(), output_format, output)
170
+ except Exception as e:
171
+ print_error(str(e))
172
+ raise click.Abort()
173
+ ```
174
+
175
+ ### Click Command Structure
176
+
177
+ Use Click decorators consistently:
178
+
179
+ ```python
180
+ @click.group()
181
+ def campaigns():
182
+ """Manage campaigns"""
183
+ pass
184
+
185
+
186
+ @campaigns.command()
187
+ @click.option("--ids", help="Comma-separated campaign IDs")
188
+ @click.option("--status", help="Filter by status")
189
+ @click.option("--limit", type=int, help="Limit number of results")
190
+ @click.pass_context
191
+ def get(ctx, ids, status, limit):
192
+ """Get campaigns"""
193
+ ...
194
+ ```
195
+
196
+ ## Project Structure
197
+
198
+ ```
199
+ direct-cli/
200
+ ├── direct_cli/ # Main package
201
+ │ ├── __init__.py # Package initialization
202
+ │ ├── cli.py # Main CLI entry point
203
+ │ ├── api.py # YandexDirect API client wrapper
204
+ │ ├── auth.py # Authentication module
205
+ │ ├── utils.py # Utility functions
206
+ │ ├── output.py # Output formatting (json, table, csv, tsv)
207
+ │ └── commands/ # Command modules by resource
208
+ │ ├── __init__.py
209
+ │ ├── campaigns.py
210
+ │ ├── adgroups.py
211
+ │ ├── ads.py
212
+ │ ├── keywords.py
213
+ │ ├── reports.py
214
+ │ └── ... # Other resource commands
215
+ ├── tests/ # Test directory
216
+ │ ├── __init__.py
217
+ │ └── test_cli.py
218
+ ├── pyproject.toml # Project configuration
219
+ └── README.md # User documentation
220
+ ```
221
+
222
+ ## Key Patterns
223
+
224
+ ### Creating a New Command Module
225
+
226
+ 1. Create a new file in `direct_cli/commands/`
227
+ 2. Define a Click group with commands
228
+ 3. Register in `direct_cli/cli.py`
229
+
230
+ ```python
231
+ # direct_cli/commands/newresource.py
232
+ import click
233
+ from ..api import create_client
234
+ from ..output import format_output, print_error
235
+
236
+
237
+ @click.group()
238
+ def newresource():
239
+ """Manage new resource"""
240
+ pass
241
+
242
+
243
+ @newresource.command()
244
+ @click.pass_context
245
+ def get(ctx):
246
+ """Get new resource"""
247
+ try:
248
+ client = create_client(
249
+ token=ctx.obj.get("token"),
250
+ login=ctx.obj.get("login"),
251
+ sandbox=ctx.obj.get("sandbox"),
252
+ )
253
+ # ... API call
254
+ format_output(data, "json", None)
255
+ except Exception as e:
256
+ print_error(str(e))
257
+ raise click.Abort()
258
+ ```
259
+
260
+ Then register in `cli.py`:
261
+
262
+ ```python
263
+ from .commands.newresource import newresource
264
+ cli.add_command(newresource)
265
+ ```
266
+
267
+ ### Output Formatting
268
+
269
+ Use `format_output()` for consistent output:
270
+
271
+ ```python
272
+ from ..output import format_output
273
+
274
+ # JSON output (default)
275
+ format_output(data, "json", None)
276
+
277
+ # Table output
278
+ format_output(data, "table", None)
279
+
280
+ # CSV/TSV to file
281
+ format_output(data, "csv", "output.csv")
282
+ format_output(data, "tsv", "output.tsv")
283
+ ```
284
+
285
+ ### Pagination
286
+
287
+ For large result sets, use `--fetch-all` flag:
288
+
289
+ ```python
290
+ if fetch_all:
291
+ items = []
292
+ for item in result().iter_items():
293
+ items.append(item)
294
+ format_output(items, output_format, output)
295
+ else:
296
+ data = result().extract()
297
+ format_output(data, output_format, output)
298
+ ```
299
+
300
+ ## Code Formatting Standards
301
+
302
+ - **Line length**: 88 characters (Black default)
303
+ - **String quotes**: Prefer double quotes (`"`)
304
+ - **Indentation**: 4 spaces (no tabs)
305
+ - **Blank lines**:
306
+ - 2 blank lines before class/function definitions at module level
307
+ - 1 blank line between methods
308
+ - 1 blank line between logical sections within functions
309
+
310
+ ## Testing Guidelines
311
+
312
+ ### Test Structure
313
+
314
+ ```python
315
+ import unittest
316
+ from click.testing import CliRunner
317
+ from direct_cli.cli import cli
318
+
319
+
320
+ class TestCLI(unittest.TestCase):
321
+ """Test CLI commands"""
322
+
323
+ def setUp(self):
324
+ self.runner = CliRunner()
325
+
326
+ def test_command_help(self):
327
+ """Test command help"""
328
+ result = self.runner.invoke(cli, ["command", "--help"])
329
+ self.assertEqual(result.exit_code, 0)
330
+ self.assertIn("expected text", result.output)
331
+ ```
332
+
333
+ ### Test Naming
334
+
335
+ - Test files: `test_*.py`
336
+ - Test classes: `Test*`
337
+ - Test methods: `test_*`
338
+
339
+ ## Dependencies
340
+
341
+ Main dependencies (see `pyproject.toml`):
342
+ - `tapi-yandex-direct` - Yandex Direct API wrapper
343
+ - `click` - CLI framework
344
+ - `python-dotenv` - Environment variable management
345
+ - `tabulate` - Table formatting
346
+ - `colorama` - Terminal colors
347
+ - `tqdm` - Progress bars
348
+
349
+ Development dependencies:
350
+ - `pytest` - Testing framework
351
+ - `pytest-cov` - Coverage plugin
352
+ - `black` - Code formatter
353
+ - `flake8` - Linter
354
+
355
+ ## Common Tasks
356
+
357
+ ### Adding a New CLI Option
358
+
359
+ ```python
360
+ @campaigns.command()
361
+ @click.option("--new-option", help="Description of new option")
362
+ @click.pass_context
363
+ def get(ctx, new_option):
364
+ """Get campaigns"""
365
+ if new_option:
366
+ # Handle option
367
+ pass
368
+ ```
369
+
370
+ ### Adding a New Output Format
371
+
372
+ 1. Add format function in `output.py`
373
+ 2. Update `format_output()` to handle new format type
374
+ 3. Update `--format` option help text in commands
375
+
376
+ ## Important Notes
377
+
378
+ - Always handle missing credentials gracefully with helpful error messages
379
+ - Use `--dry-run` flag for operations that modify data
380
+ - Support pagination for list operations with `--fetch-all` and `--limit`
381
+ - Maintain backward compatibility when changing command signatures
382
+ - Test changes with both sandbox and production API when possible
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include .env.example
3
+ recursive-include direct_cli/commands *.py