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.
- direct_cli-0.0.0/.env.example +7 -0
- direct_cli-0.0.0/.github/copilot-instructions.md +91 -0
- direct_cli-0.0.0/.gitignore +57 -0
- direct_cli-0.0.0/AGENTS.md +382 -0
- direct_cli-0.0.0/MANIFEST.in +3 -0
- direct_cli-0.0.0/PKG-INFO +393 -0
- direct_cli-0.0.0/README.md +355 -0
- direct_cli-0.0.0/direct_cli/__init__.py +14 -0
- direct_cli-0.0.0/direct_cli/api.py +94 -0
- direct_cli-0.0.0/direct_cli/auth.py +58 -0
- direct_cli-0.0.0/direct_cli/cli.py +85 -0
- direct_cli-0.0.0/direct_cli/commands/__init__.py +61 -0
- direct_cli-0.0.0/direct_cli/commands/adextensions.py +96 -0
- direct_cli-0.0.0/direct_cli/commands/adgroups.py +189 -0
- direct_cli-0.0.0/direct_cli/commands/adimages.py +63 -0
- direct_cli-0.0.0/direct_cli/commands/ads.py +306 -0
- direct_cli-0.0.0/direct_cli/commands/agencyclients.py +64 -0
- direct_cli-0.0.0/direct_cli/commands/audiencetargets.py +187 -0
- direct_cli-0.0.0/direct_cli/commands/bidmodifiers.py +110 -0
- direct_cli-0.0.0/direct_cli/commands/bids.py +108 -0
- direct_cli-0.0.0/direct_cli/commands/businesses.py +61 -0
- direct_cli-0.0.0/direct_cli/commands/campaigns.py +311 -0
- direct_cli-0.0.0/direct_cli/commands/changes.py +97 -0
- direct_cli-0.0.0/direct_cli/commands/clients.py +98 -0
- direct_cli-0.0.0/direct_cli/commands/creatives.py +68 -0
- direct_cli-0.0.0/direct_cli/commands/dictionaries.py +64 -0
- direct_cli-0.0.0/direct_cli/commands/dynamicads.py +104 -0
- direct_cli-0.0.0/direct_cli/commands/feeds.py +99 -0
- direct_cli-0.0.0/direct_cli/commands/keywordbids.py +111 -0
- direct_cli-0.0.0/direct_cli/commands/keywords.py +309 -0
- direct_cli-0.0.0/direct_cli/commands/keywordsresearch.py +71 -0
- direct_cli-0.0.0/direct_cli/commands/leads.py +65 -0
- direct_cli-0.0.0/direct_cli/commands/negativekeywordsharedsets.py +97 -0
- direct_cli-0.0.0/direct_cli/commands/reports.py +128 -0
- direct_cli-0.0.0/direct_cli/commands/retargeting.py +104 -0
- direct_cli-0.0.0/direct_cli/commands/sitelinks.py +92 -0
- direct_cli-0.0.0/direct_cli/commands/smartadtargets.py +104 -0
- direct_cli-0.0.0/direct_cli/commands/turbopages.py +97 -0
- direct_cli-0.0.0/direct_cli/commands/vcards.py +93 -0
- direct_cli-0.0.0/direct_cli/output.py +143 -0
- direct_cli-0.0.0/direct_cli/utils.py +120 -0
- direct_cli-0.0.0/direct_cli.egg-info/PKG-INFO +393 -0
- direct_cli-0.0.0/direct_cli.egg-info/SOURCES.txt +54 -0
- direct_cli-0.0.0/direct_cli.egg-info/dependency_links.txt +1 -0
- direct_cli-0.0.0/direct_cli.egg-info/entry_points.txt +2 -0
- direct_cli-0.0.0/direct_cli.egg-info/not-zip-safe +1 -0
- direct_cli-0.0.0/direct_cli.egg-info/requires.txt +12 -0
- direct_cli-0.0.0/direct_cli.egg-info/top_level.txt +1 -0
- direct_cli-0.0.0/pyproject.toml +85 -0
- direct_cli-0.0.0/scripts/release_pypi.sh +116 -0
- direct_cli-0.0.0/setup.cfg +4 -0
- direct_cli-0.0.0/setup.py +65 -0
- direct_cli-0.0.0/tests/__init__.py +1 -0
- direct_cli-0.0.0/tests/test_cli.py +65 -0
- direct_cli-0.0.0/tests/test_comprehensive.py +187 -0
- direct_cli-0.0.0/tests/test_integration.py +239 -0
|
@@ -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
|