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.
- colacloud_cli-0.3.0/.mcp.json +3 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/PKG-INFO +3 -3
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/README.md +1 -1
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/pyproject.toml +14 -4
- colacloud_cli-0.3.0/scripts/smoke_test.py +196 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/api.py +183 -25
- colacloud_cli-0.3.0/src/colacloud_cli/commands/avas.py +91 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/colas.py +19 -5
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/config.py +3 -1
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/permittees.py +3 -1
- colacloud_cli-0.3.0/src/colacloud_cli/commands/processing_times.py +153 -0
- colacloud_cli-0.3.0/src/colacloud_cli/commands/production_reports.py +89 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/config.py +5 -6
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/formatters.py +68 -33
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/main.py +12 -3
- colacloud_cli-0.3.0/tests/test_api.py +462 -0
- colacloud_cli-0.3.0/tests/test_cli.py +572 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/tests/test_config.py +1 -3
- colacloud_cli-0.1.0/tests/test_api.py +0 -236
- colacloud_cli-0.1.0/tests/test_cli.py +0 -286
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/.gitignore +0 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/LICENSE +0 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/__init__.py +0 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/__init__.py +0 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/barcode.py +0 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/usage.py +0 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/src/colacloud_cli/commands/utils.py +0 -0
- {colacloud_cli-0.1.0 → colacloud_cli-0.3.0}/tests/__init__.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: colacloud-cli
|
|
3
|
-
Version: 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/
|
|
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/
|
|
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/
|
|
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.
|
|
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/
|
|
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
|
-
"
|
|
51
|
-
"
|
|
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
|
-
|
|
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:
|
|
18
|
-
error_code:
|
|
19
|
-
details:
|
|
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:
|
|
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:
|
|
61
|
-
base_url:
|
|
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(
|
|
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
|
-
|
|
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
|
|
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:
|
|
182
|
-
product_type:
|
|
183
|
-
origin:
|
|
184
|
-
brand_name:
|
|
185
|
-
approval_date_from:
|
|
186
|
-
approval_date_to:
|
|
187
|
-
abv_min:
|
|
188
|
-
abv_max:
|
|
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
|
|
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:
|
|
252
|
-
state:
|
|
253
|
-
is_active:
|
|
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(
|
|
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(
|
|
32
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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")
|