colacloud-cli 0.1.0__tar.gz → 0.3.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 (28) hide show
  1. colacloud_cli-0.3.0/.mcp.json +3 -0
  2. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/PKG-INFO +3 -3
  3. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/README.md +1 -1
  4. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/pyproject.toml +14 -4
  5. colacloud_cli-0.3.0/scripts/smoke_test.py +196 -0
  6. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/api.py +183 -25
  7. colacloud_cli-0.3.0/src/colacloud_cli/commands/avas.py +91 -0
  8. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/colas.py +19 -5
  9. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/config.py +3 -1
  10. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/permittees.py +3 -1
  11. colacloud_cli-0.3.0/src/colacloud_cli/commands/processing_times.py +153 -0
  12. colacloud_cli-0.3.0/src/colacloud_cli/commands/production_reports.py +89 -0
  13. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/config.py +5 -6
  14. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/formatters.py +68 -33
  15. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/main.py +12 -3
  16. colacloud_cli-0.3.0/tests/test_api.py +462 -0
  17. colacloud_cli-0.3.0/tests/test_cli.py +572 -0
  18. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/tests/test_config.py +1 -3
  19. colacloud_cli-0.1.0/tests/test_api.py +0 -236
  20. colacloud_cli-0.1.0/tests/test_cli.py +0 -286
  21. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/.gitignore +0 -0
  22. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/LICENSE +0 -0
  23. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/__init__.py +0 -0
  24. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/__init__.py +0 -0
  25. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/barcode.py +0 -0
  26. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/usage.py +0 -0
  27. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/utils.py +0 -0
  28. {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/tests/__init__.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "mcpServers": {}
3
+ }
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: colacloud-cli
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Command-line interface for the COLA Cloud API
5
5
  Project-URL: Homepage, https://colacloud.us
6
- Project-URL: Documentation, https://colacloud.us/docs/api
6
+ Project-URL: Documentation, https://docs.colacloud.us/api-reference
7
7
  Project-URL: Repository, https://github.com/cola-cloud-us/colacloud-cli
8
8
  Author-email: Jay Sobel <jay@colacloud.us>
9
9
  License: MIT
@@ -300,5 +300,5 @@ MIT License - see [LICENSE](LICENSE) for details.
300
300
  ## Links
301
301
 
302
302
  - [COLA Cloud Website](https://colacloud.us)
303
- - [API Documentation](https://colacloud.us/docs/api)
303
+ - [API Documentation](https://docs.colacloud.us/api-reference)
304
304
  - [GitHub Repository](https://github.com/cola-cloud-us/colacloud-cli)
@@ -273,5 +273,5 @@ MIT License - see [LICENSE](LICENSE) for details.
273
273
  ## Links
274
274
 
275
275
  - [COLA Cloud Website](https://colacloud.us)
276
- - [API Documentation](https://colacloud.us/docs/api)
276
+ - [API Documentation](https://docs.colacloud.us/api-reference)
277
277
  - [GitHub Repository](https://github.com/cola-cloud-us/colacloud-cli)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "colacloud-cli"
3
- version = "0.1.0"
3
+ version = "0.3.0"
4
4
  description = "Command-line interface for the COLA Cloud API"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -33,7 +33,7 @@ cola = "colacloud_cli.main:cli"
33
33
 
34
34
  [project.urls]
35
35
  Homepage = "https://colacloud.us"
36
- Documentation = "https://colacloud.us/docs/api"
36
+ Documentation = "https://docs.colacloud.us/api-reference"
37
37
  Repository = "https://github.com/cola-cloud-us/colacloud-cli"
38
38
 
39
39
  [build-system]
@@ -47,7 +47,17 @@ packages = ["src/colacloud_cli"]
47
47
  dev = [
48
48
  "pytest>=7.0.0",
49
49
  "pytest-cov>=4.0.0",
50
- "black>=23.0.0",
51
- "isort>=5.12.0",
50
+ "respx>=0.21.0",
51
+ "ruff>=0.9.0",
52
52
  "mypy>=1.0.0",
53
53
  ]
54
+
55
+ [tool.ruff]
56
+ line-length = 88
57
+ target-version = "py310"
58
+
59
+ [tool.ruff.lint]
60
+ select = ["F", "E", "W", "I", "UP", "B", "SIM", "RUF"]
61
+
62
+ [tool.ruff.lint.isort]
63
+ known-first-party = ["colacloud_cli"]
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Live smoke test for the colacloud-cli.
4
+
5
+ Runs actual CLI commands against the production API to verify
6
+ end-to-end functionality: config handling, HTTP requests, response
7
+ formatting, and JSON output parsing.
8
+
9
+ Usage:
10
+ COLA_API_KEY=... uv run python scripts/smoke_test.py
11
+ COLA_API_KEY=... uv run python scripts/smoke_test.py --base-url http://localhost:5001/api/v1
12
+ """
13
+
14
+ import argparse
15
+ import json
16
+ import os
17
+ import subprocess
18
+ import sys
19
+ import time
20
+
21
+ results: list[tuple[bool, str]] = []
22
+
23
+
24
+ def run_cola(*args: str, env_override: dict | None = None) -> tuple[int, str, str]:
25
+ """Run a cola CLI command and return (exit_code, stdout, stderr)."""
26
+ env = os.environ.copy()
27
+ if env_override:
28
+ env.update(env_override)
29
+ proc = subprocess.run(
30
+ ["uv", "run", "cola", *args],
31
+ capture_output=True,
32
+ text=True,
33
+ timeout=30,
34
+ env=env,
35
+ )
36
+ return proc.returncode, proc.stdout, proc.stderr
37
+
38
+
39
+ def check(name: str, fn):
40
+ """Run a single check. Calls fn(), expects it to return or raise."""
41
+ start = time.monotonic()
42
+ try:
43
+ detail = fn()
44
+ elapsed = time.monotonic() - start
45
+ msg = f" OK {name} ({elapsed:.2f}s)"
46
+ if detail:
47
+ msg += f" — {detail}"
48
+ results.append((True, msg))
49
+ print(msg)
50
+ except Exception as e:
51
+ elapsed = time.monotonic() - start
52
+ msg = f"FAIL {name} — {type(e).__name__}: {e} ({elapsed:.2f}s)"
53
+ results.append((False, msg))
54
+ print(msg)
55
+
56
+
57
+ def main():
58
+ parser = argparse.ArgumentParser(description="COLA Cloud CLI smoke test")
59
+ parser.add_argument(
60
+ "--base-url",
61
+ default=None,
62
+ help="Override API base URL (default: production)",
63
+ )
64
+ args = parser.parse_args()
65
+
66
+ api_key = os.environ.get("COLA_API_KEY") or os.environ.get("COLACLOUD_API_KEY")
67
+ if not api_key:
68
+ print("Error: COLA_API_KEY or COLACLOUD_API_KEY environment variable is required")
69
+ sys.exit(1)
70
+
71
+ env = {"COLACLOUD_API_KEY": api_key}
72
+ if args.base_url:
73
+ env["COLACLOUD_BASE_URL"] = args.base_url
74
+
75
+ display = args.base_url or "https://app.colacloud.us/api/v1"
76
+ print(f"Smoke testing colacloud CLI against {display}\n")
77
+
78
+ # --- --version ---
79
+ def test_version():
80
+ rc, out, _ = run_cola("--version", env_override=env)
81
+ assert rc == 0, f"exit code {rc}"
82
+ assert "cola" in out.lower(), f"unexpected output: {out}"
83
+ return out.strip()
84
+
85
+ check("cola --version", test_version)
86
+
87
+ # --- colas list --json ---
88
+ ttb_id = None
89
+
90
+ def test_colas_list():
91
+ nonlocal ttb_id
92
+ rc, out, err = run_cola("colas", "list", "--limit", "1", "--json", env_override=env)
93
+ assert rc == 0, f"exit code {rc}: {err}"
94
+ data = json.loads(out)
95
+ assert "data" in data, "missing 'data' key"
96
+ assert len(data["data"]) > 0, "no COLAs returned"
97
+ assert "pagination" in data, "missing 'pagination' key"
98
+ cola = data["data"][0]
99
+ ttb_id = cola["ttb_id"]
100
+ return f"total={data['pagination']['total']}, first={ttb_id}"
101
+
102
+ check("cola colas list --limit 1 --json", test_colas_list)
103
+
104
+ # --- colas get --json ---
105
+ def test_colas_get():
106
+ if not ttb_id:
107
+ return "skipped — no ttb_id from list"
108
+ rc, out, err = run_cola("colas", "get", ttb_id, "--json", env_override=env)
109
+ assert rc == 0, f"exit code {rc}: {err}"
110
+ data = json.loads(out)
111
+ assert "data" in data, "missing 'data' key"
112
+ assert data["data"]["ttb_id"] == ttb_id
113
+ return f"ttb_id={ttb_id}, type={data['data'].get('product_type')}"
114
+
115
+ check(f"cola colas get {ttb_id} --json", test_colas_get)
116
+
117
+ # --- colas search --json ---
118
+ def test_colas_search():
119
+ rc, out, err = run_cola(
120
+ "colas", "search", "bourbon", "--limit", "5", "--json", env_override=env
121
+ )
122
+ assert rc == 0, f"exit code {rc}: {err}"
123
+ data = json.loads(out)
124
+ assert "data" in data
125
+ return f"found {len(data['data'])} results"
126
+
127
+ check('cola colas search "bourbon" --json', test_colas_search)
128
+
129
+ # --- permittees list --json ---
130
+ permit_number = None
131
+
132
+ def test_permittees_list():
133
+ nonlocal permit_number
134
+ rc, out, err = run_cola(
135
+ "permittees", "list", "--limit", "1", "--json", env_override=env
136
+ )
137
+ assert rc == 0, f"exit code {rc}: {err}"
138
+ data = json.loads(out)
139
+ assert "data" in data
140
+ assert len(data["data"]) > 0
141
+ permit_number = data["data"][0]["permit_number"]
142
+ return f"total={data['pagination']['total']}, first={permit_number}"
143
+
144
+ check("cola permittees list --limit 1 --json", test_permittees_list)
145
+
146
+ # --- permittees get --json ---
147
+ def test_permittees_get():
148
+ if not permit_number:
149
+ return "skipped — no permit_number from list"
150
+ rc, out, err = run_cola(
151
+ "permittees", "get", permit_number, "--json", env_override=env
152
+ )
153
+ assert rc == 0, f"exit code {rc}: {err}"
154
+ data = json.loads(out)
155
+ assert "data" in data
156
+ return f"permit={permit_number}, company={data['data'].get('company_name')}"
157
+
158
+ check(f"cola permittees get {permit_number} --json", test_permittees_get)
159
+
160
+ # --- usage --json ---
161
+ def test_usage():
162
+ rc, out, err = run_cola("usage", "--json", env_override=env)
163
+ assert rc == 0, f"exit code {rc}: {err}"
164
+ data = json.loads(out)
165
+ assert "data" in data
166
+ usage = data["data"]
167
+ return f"tier={usage.get('tier')}, used={usage.get('requests_used')}/{usage.get('monthly_limit')}"
168
+
169
+ check("cola usage --json", test_usage)
170
+
171
+ # --- colas list (table output, no --json) ---
172
+ def test_table_output():
173
+ rc, out, err = run_cola("colas", "list", "--limit", "3", env_override=env)
174
+ assert rc == 0, f"exit code {rc}: {err}"
175
+ assert len(out.strip()) > 0, "empty output"
176
+ return f"{len(out.splitlines())} lines of output"
177
+
178
+ check("cola colas list --limit 3 (table)", test_table_output)
179
+
180
+ # --- Summary ---
181
+ total = len(results)
182
+ passed = sum(1 for p, _ in results if p)
183
+ failed = total - passed
184
+
185
+ print(f"\n{'=' * 40}")
186
+ print(f" {passed}/{total} passed", end="")
187
+ if failed:
188
+ print(f", {failed} failed")
189
+ else:
190
+ print()
191
+
192
+ sys.exit(0 if failed == 0 else 1)
193
+
194
+
195
+ if __name__ == "__main__":
196
+ main()
@@ -1,6 +1,7 @@
1
1
  """COLA Cloud API client wrapper."""
2
2
 
3
- from typing import Any, Optional
3
+ import contextlib
4
+ from typing import Any
4
5
 
5
6
  import httpx
6
7
 
@@ -14,9 +15,9 @@ class APIError(Exception):
14
15
  def __init__(
15
16
  self,
16
17
  message: str,
17
- status_code: Optional[int] = None,
18
- error_code: Optional[str] = None,
19
- details: Optional[dict] = None,
18
+ status_code: int | None = None,
19
+ error_code: str | None = None,
20
+ details: dict | None = None,
20
21
  ):
21
22
  super().__init__(message)
22
23
  self.message = message
@@ -45,7 +46,7 @@ class RateLimitError(APIError):
45
46
  def __init__(
46
47
  self,
47
48
  message: str,
48
- retry_after: Optional[int] = None,
49
+ retry_after: int | None = None,
49
50
  **kwargs,
50
51
  ):
51
52
  super().__init__(message, **kwargs)
@@ -57,8 +58,8 @@ class ColaCloudClient:
57
58
 
58
59
  def __init__(
59
60
  self,
60
- api_key: Optional[str] = None,
61
- base_url: Optional[str] = None,
61
+ api_key: str | None = None,
62
+ base_url: str | None = None,
62
63
  timeout: float = 30.0,
63
64
  ):
64
65
  """Initialize the API client.
@@ -117,7 +118,9 @@ class ColaCloudClient:
117
118
  # Extract error information
118
119
  error_info = data.get("error", {})
119
120
  error_code = error_info.get("code", "unknown_error")
120
- error_message = error_info.get("message", response.reason_phrase or "Unknown error")
121
+ error_message = error_info.get(
122
+ "message", response.reason_phrase or "Unknown error"
123
+ )
121
124
  error_details = error_info.get("details", {})
122
125
 
123
126
  # Handle specific error types
@@ -132,10 +135,8 @@ class ColaCloudClient:
132
135
  if response.status_code == 429:
133
136
  retry_after = None
134
137
  if "Retry-After" in response.headers:
135
- try:
138
+ with contextlib.suppress(ValueError):
136
139
  retry_after = int(response.headers["Retry-After"])
137
- except ValueError:
138
- pass
139
140
 
140
141
  raise RateLimitError(
141
142
  error_message,
@@ -160,8 +161,8 @@ class ColaCloudClient:
160
161
  """
161
162
  if not self.api_key:
162
163
  raise AuthenticationError(
163
- "API key not configured. Run 'cola config set-key' to set your API key, "
164
- "or set the COLACLOUD_API_KEY environment variable."
164
+ "API key not configured. Run 'cola config set-key' to set "
165
+ "your API key, or set the COLACLOUD_API_KEY environment variable."
165
166
  )
166
167
 
167
168
  def close(self) -> None:
@@ -178,14 +179,14 @@ class ColaCloudClient:
178
179
 
179
180
  def list_colas(
180
181
  self,
181
- query: Optional[str] = None,
182
- product_type: Optional[str] = None,
183
- origin: Optional[str] = None,
184
- brand_name: Optional[str] = None,
185
- approval_date_from: Optional[str] = None,
186
- approval_date_to: Optional[str] = None,
187
- abv_min: Optional[float] = None,
188
- abv_max: Optional[float] = None,
182
+ query: str | None = None,
183
+ product_type: str | None = None,
184
+ origin: str | None = None,
185
+ brand_name: str | None = None,
186
+ approval_date_from: str | None = None,
187
+ approval_date_to: str | None = None,
188
+ abv_min: float | None = None,
189
+ abv_max: float | None = None,
189
190
  page: int = 1,
190
191
  per_page: int = 20,
191
192
  ) -> dict[str, Any]:
@@ -193,7 +194,7 @@ class ColaCloudClient:
193
194
 
194
195
  Args:
195
196
  query: Full-text search query.
196
- product_type: Filter by product type (malt beverage, wine, distilled spirits).
197
+ product_type: Filter by product type.
197
198
  origin: Filter by country/state.
198
199
  brand_name: Filter by brand name (partial match).
199
200
  approval_date_from: Filter by minimum approval date (YYYY-MM-DD).
@@ -248,9 +249,9 @@ class ColaCloudClient:
248
249
 
249
250
  def list_permittees(
250
251
  self,
251
- query: Optional[str] = None,
252
- state: Optional[str] = None,
253
- is_active: Optional[bool] = None,
252
+ query: str | None = None,
253
+ state: str | None = None,
254
+ is_active: bool | None = None,
254
255
  page: int = 1,
255
256
  per_page: int = 20,
256
257
  ) -> dict[str, Any]:
@@ -310,6 +311,163 @@ class ColaCloudClient:
310
311
  response = self._client.get(f"/barcode/{barcode_value}")
311
312
  return self._handle_response(response)
312
313
 
314
+ # Processing times endpoints
315
+
316
+ def list_processing_times(
317
+ self,
318
+ commodity: str | None = None,
319
+ ) -> dict[str, Any]:
320
+ """Get COLA processing times.
321
+
322
+ Args:
323
+ commodity: Filter by commodity type.
324
+
325
+ Returns:
326
+ API response with processing times data.
327
+ """
328
+ self._require_api_key()
329
+
330
+ params: dict[str, Any] = {}
331
+ if commodity:
332
+ params["commodity"] = commodity
333
+
334
+ response = self._client.get("/processing-times", params=params)
335
+ return self._handle_response(response)
336
+
337
+ def list_formula_processing_times(
338
+ self,
339
+ formula_type: str | None = None,
340
+ commodity: str | None = None,
341
+ ) -> dict[str, Any]:
342
+ """Get formula processing times.
343
+
344
+ Args:
345
+ formula_type: Filter by formula type.
346
+ commodity: Filter by commodity type.
347
+
348
+ Returns:
349
+ API response with formula processing times data.
350
+ """
351
+ self._require_api_key()
352
+
353
+ params: dict[str, Any] = {}
354
+ if formula_type:
355
+ params["formula_type"] = formula_type
356
+ if commodity:
357
+ params["commodity"] = commodity
358
+
359
+ response = self._client.get("/processing-times/formula", params=params)
360
+ return self._handle_response(response)
361
+
362
+ def list_registration_processing_times(
363
+ self,
364
+ category: str | None = None,
365
+ application_type: str | None = None,
366
+ ) -> dict[str, Any]:
367
+ """Get registration processing times.
368
+
369
+ Args:
370
+ category: Filter by category.
371
+ application_type: Filter by application type.
372
+
373
+ Returns:
374
+ API response with registration processing times data.
375
+ """
376
+ self._require_api_key()
377
+
378
+ params: dict[str, Any] = {}
379
+ if category:
380
+ params["category"] = category
381
+ if application_type:
382
+ params["application_type"] = application_type
383
+
384
+ response = self._client.get("/processing-times/registration", params=params)
385
+ return self._handle_response(response)
386
+
387
+ # Production reports endpoint
388
+
389
+ def list_production_reports(
390
+ self,
391
+ commodity: str | None = None,
392
+ year: int | None = None,
393
+ month: int | None = None,
394
+ report_type: str | None = None,
395
+ statistical_group: str | None = None,
396
+ page: int = 1,
397
+ per_page: int = 100,
398
+ ) -> dict[str, Any]:
399
+ """Get production reports.
400
+
401
+ Args:
402
+ commodity: Filter by commodity type.
403
+ year: Filter by year.
404
+ month: Filter by month.
405
+ report_type: Filter by report type.
406
+ statistical_group: Filter by statistical group.
407
+ page: Page number.
408
+ per_page: Results per page (max 100).
409
+
410
+ Returns:
411
+ API response with production reports data.
412
+ """
413
+ self._require_api_key()
414
+
415
+ params: dict[str, Any] = {"page": page, "per_page": per_page}
416
+ if commodity:
417
+ params["commodity"] = commodity
418
+ if year is not None:
419
+ params["year"] = year
420
+ if month is not None:
421
+ params["month"] = month
422
+ if report_type:
423
+ params["report_type"] = report_type
424
+ if statistical_group:
425
+ params["statistical_group"] = statistical_group
426
+
427
+ response = self._client.get("/production-reports", params=params)
428
+ return self._handle_response(response)
429
+
430
+ # AVA endpoints
431
+
432
+ def list_avas(
433
+ self,
434
+ state: str | None = None,
435
+ query: str | None = None,
436
+ ) -> dict[str, Any]:
437
+ """List American Viticultural Areas (AVAs).
438
+
439
+ Args:
440
+ state: Filter by state.
441
+ query: Search by name.
442
+
443
+ Returns:
444
+ API response with AVA data.
445
+ """
446
+ self._require_api_key()
447
+
448
+ params: dict[str, Any] = {}
449
+ if state:
450
+ params["state"] = state
451
+ if query:
452
+ params["q"] = query
453
+
454
+ response = self._client.get("/avas", params=params)
455
+ return self._handle_response(response)
456
+
457
+ def get_ava(self, ava_id: str) -> dict[str, Any]:
458
+ """Get a single AVA by ID.
459
+
460
+ Args:
461
+ ava_id: The AVA identifier.
462
+
463
+ Returns:
464
+ API response with AVA details.
465
+ """
466
+ self._require_api_key()
467
+
468
+ response = self._client.get(f"/avas/{ava_id}")
469
+ return self._handle_response(response)
470
+
313
471
  # Usage endpoint
314
472
 
315
473
  def get_usage(self) -> dict[str, Any]:
@@ -0,0 +1,91 @@
1
+ """AVA commands for COLA Cloud CLI."""
2
+
3
+ import json
4
+
5
+ import click
6
+
7
+ from colacloud_cli.api import APIError, get_client
8
+ from colacloud_cli.commands.utils import console, handle_api_error
9
+
10
+
11
+ @click.group(name="avas")
12
+ def avas_group():
13
+ """Browse American Viticultural Areas (AVAs)."""
14
+ pass
15
+
16
+
17
+ @avas_group.command(name="list")
18
+ @click.option("--state", help="Filter by state (e.g., CA, OR, WA).")
19
+ @click.option("-q", "--query", help="Search by AVA name.")
20
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
21
+ def list_avas(state: str | None, query: str | None, as_json: bool):
22
+ """List American Viticultural Areas.
23
+
24
+ Browse and search federally recognized wine grape-growing regions.
25
+
26
+ Examples:
27
+
28
+ \b
29
+ # List all AVAs
30
+ cola avas list
31
+
32
+ \b
33
+ # Filter by state
34
+ cola avas list --state CA
35
+
36
+ \b
37
+ # Search by name
38
+ cola avas list -q "napa"
39
+ """
40
+ try:
41
+ with get_client() as client:
42
+ result = client.list_avas(state=state, query=query)
43
+
44
+ if as_json:
45
+ click.echo(json.dumps(result, indent=2))
46
+ else:
47
+ data = result.get("data", [])
48
+ meta = result.get("meta", {})
49
+
50
+ if not data:
51
+ console.print("[yellow]No AVAs found matching your criteria.[/]")
52
+ return
53
+
54
+ console.print(json.dumps(data, indent=2))
55
+ total = meta.get("total", len(data))
56
+ console.print(f"\n[dim]{total} result(s)[/]")
57
+
58
+ except APIError as e:
59
+ handle_api_error(e)
60
+
61
+
62
+ @avas_group.command(name="get")
63
+ @click.argument("ava_id")
64
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
65
+ def get_ava(ava_id: str, as_json: bool):
66
+ """Get detailed information about a specific AVA.
67
+
68
+ AVA_ID is the unique identifier for the American Viticultural Area.
69
+
70
+ Examples:
71
+
72
+ \b
73
+ # Get AVA details
74
+ cola avas get napa-valley
75
+
76
+ \b
77
+ # Output as JSON
78
+ cola avas get napa-valley --json
79
+ """
80
+ try:
81
+ with get_client() as client:
82
+ result = client.get_ava(ava_id)
83
+
84
+ if as_json:
85
+ click.echo(json.dumps(result, indent=2))
86
+ else:
87
+ data = result.get("data", {})
88
+ console.print(json.dumps(data, indent=2))
89
+
90
+ except APIError as e:
91
+ handle_api_error(e)
@@ -23,16 +23,28 @@ def colas_group():
23
23
  @click.option("-q", "--query", help="Full-text search query.")
24
24
  @click.option(
25
25
  "--product-type",
26
- type=click.Choice(["malt beverage", "wine", "distilled spirits"], case_sensitive=False),
26
+ type=click.Choice(
27
+ ["malt beverage", "wine", "distilled spirits"], case_sensitive=False
28
+ ),
27
29
  help="Filter by product type.",
28
30
  )
29
31
  @click.option("--origin", help="Filter by origin (country/state).")
30
32
  @click.option("--brand", "brand_name", help="Filter by brand name (partial match).")
31
- @click.option("--date-from", "approval_date_from", help="Filter by minimum approval date (YYYY-MM-DD).")
32
- @click.option("--date-to", "approval_date_to", help="Filter by maximum approval date (YYYY-MM-DD).")
33
+ @click.option(
34
+ "--date-from",
35
+ "approval_date_from",
36
+ help="Filter by minimum approval date (YYYY-MM-DD).",
37
+ )
38
+ @click.option(
39
+ "--date-to",
40
+ "approval_date_to",
41
+ help="Filter by maximum approval date (YYYY-MM-DD).",
42
+ )
33
43
  @click.option("--abv-min", type=float, help="Filter by minimum ABV.")
34
44
  @click.option("--abv-max", type=float, help="Filter by maximum ABV.")
35
- @click.option("--limit", "per_page", default=20, type=int, help="Results per page (max 100).")
45
+ @click.option(
46
+ "--limit", "per_page", default=20, type=int, help="Results per page (max 100)."
47
+ )
36
48
  @click.option("--page", default=1, type=int, help="Page number.")
37
49
  @click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
38
50
  def list_colas(
@@ -135,7 +147,9 @@ def get_cola(ttb_id: str, as_json: bool):
135
147
 
136
148
  @colas_group.command(name="search")
137
149
  @click.argument("query")
138
- @click.option("--limit", "per_page", default=20, type=int, help="Results per page (max 100).")
150
+ @click.option(
151
+ "--limit", "per_page", default=20, type=int, help="Results per page (max 100)."
152
+ )
139
153
  @click.option("--page", default=1, type=int, help="Page number.")
140
154
  @click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
141
155
  def search_colas(query: str, per_page: int, page: int, as_json: bool):
@@ -49,7 +49,9 @@ def set_key(key: str | None):
49
49
  config.set_api_key(key)
50
50
 
51
51
  console.print("[green]Success![/] API key saved to ~/.colacloud/config.json")
52
- console.print("[dim]The config file has been set to mode 600 (owner read/write only).[/]")
52
+ console.print(
53
+ "[dim]The config file has been set to mode 600 (owner read/write only).[/]"
54
+ )
53
55
 
54
56
 
55
57
  @config_group.command(name="show")