colacloud-cli 0.3.2__tar.gz → 0.3.3__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 (27) hide show
  1. colacloud_cli-0.3.3/.github/workflows/ci.yml +46 -0
  2. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/PKG-INFO +19 -5
  3. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/README.md +18 -4
  4. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/pyproject.toml +1 -1
  5. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/api.py +42 -2
  6. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/colas.py +94 -9
  7. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/tests/test_api.py +21 -1
  8. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/tests/test_cli.py +31 -2
  9. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/.gitignore +0 -0
  10. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/AGENTS.md +0 -0
  11. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/LICENSE +0 -0
  12. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/scripts/smoke_test.py +0 -0
  13. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/__init__.py +0 -0
  14. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/__init__.py +0 -0
  15. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/avas.py +0 -0
  16. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/barcode.py +0 -0
  17. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/config.py +0 -0
  18. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/permittees.py +0 -0
  19. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/processing_times.py +0 -0
  20. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/production_reports.py +0 -0
  21. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/usage.py +0 -0
  22. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/commands/utils.py +0 -0
  23. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/config.py +0 -0
  24. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/formatters.py +0 -0
  25. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/src/colacloud_cli/main.py +0 -0
  26. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/tests/__init__.py +0 -0
  27. {colacloud_cli-0.3.2 → colacloud_cli-0.3.3}/tests/test_config.py +0 -0
@@ -0,0 +1,46 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ push:
8
+ branches:
9
+ - main
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: ci-${{ github.event.pull_request.head.ref || github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ build:
20
+ name: Build & Validate
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 15
23
+ env:
24
+ PYTHONPATH: src
25
+ steps:
26
+ - uses: actions/checkout@v6
27
+
28
+ - name: Set up Python
29
+ uses: actions/setup-python@v6
30
+ with:
31
+ python-version: "3.10"
32
+
33
+ - name: Install uv
34
+ uses: astral-sh/setup-uv@v8.1.0
35
+
36
+ - name: Install dependencies
37
+ run: uv sync
38
+
39
+ - name: Run tests
40
+ run: uv run pytest tests
41
+
42
+ - name: Ruff lint
43
+ run: uv run ruff check .
44
+
45
+ - name: Build package
46
+ run: uv build
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: colacloud-cli
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Command-line interface for the COLA Cloud API
5
5
  Project-URL: Homepage, https://colacloud.us
6
6
  Project-URL: Documentation, https://docs.colacloud.us/api-reference
@@ -100,8 +100,12 @@ cola colas list --date-from 2024-01-01 --date-to 2024-12-31
100
100
  # Filter by ABV
101
101
  cola colas list --product-type "distilled spirits" --abv-min 40 --abv-max 50
102
102
 
103
- # Search by brand name
104
- cola colas list --brand "maker's mark"
103
+ # Filter by package and derived category
104
+ cola colas list --category Beer --derived-subcategory "Beer > Ale" \
105
+ --container-type can --volume-unit "fluid ounces" --volume-min 12 --volume-max 16
106
+
107
+ # Search generic text, including applicant/company names
108
+ cola colas list -q "molson coors"
105
109
 
106
110
  # Pagination
107
111
  cola colas list -q "bourbon" --limit 50 --page 2
@@ -117,14 +121,24 @@ cola colas list -q "whiskey" --json | jq '.data[].brand_name'
117
121
 
118
122
  | Option | Description |
119
123
  |--------|-------------|
120
- | `-q, --query` | Full-text search query |
121
- | `--product-type` | Filter by type: `malt beverage`, `wine`, `distilled spirits` |
124
+ | `-q, --query` | Generic text query across brand, product, permit, applicant/company, and related text |
125
+ | `--product-type` | Filter by TTB type: `malt beverage`, `wine`, `distilled spirits`; can be used multiple times |
126
+ | `--category` | Filter by derived category: `Beer`, `Wine`, `Liquor`; can be used multiple times |
127
+ | `--derived-subcategory` | Filter by derived category path prefix, such as `Beer > Ale` |
122
128
  | `--origin` | Filter by country/state |
129
+ | `--domestic-or-imported` | Filter by `domestic` or `imported` |
130
+ | `--status` | Filter by application status |
123
131
  | `--brand` | Filter by brand name (partial match) |
132
+ | `--permit-number` | Filter by exact permit number |
133
+ | `--barcode` | Filter by exact main barcode value |
124
134
  | `--date-from` | Minimum approval date (YYYY-MM-DD) |
125
135
  | `--date-to` | Maximum approval date (YYYY-MM-DD) |
126
136
  | `--abv-min` | Minimum ABV percentage |
127
137
  | `--abv-max` | Maximum ABV percentage |
138
+ | `--volume-unit` | Package volume unit; required with `--volume-min` or `--volume-max` |
139
+ | `--volume-min` | Minimum package volume |
140
+ | `--volume-max` | Maximum package volume |
141
+ | `--container-type` | Derived container type; can be used multiple times |
128
142
  | `--limit` | Results per page (max 100) |
129
143
  | `--page` | Page number |
130
144
  | `--json` | Output as JSON |
@@ -73,8 +73,12 @@ cola colas list --date-from 2024-01-01 --date-to 2024-12-31
73
73
  # Filter by ABV
74
74
  cola colas list --product-type "distilled spirits" --abv-min 40 --abv-max 50
75
75
 
76
- # Search by brand name
77
- cola colas list --brand "maker's mark"
76
+ # Filter by package and derived category
77
+ cola colas list --category Beer --derived-subcategory "Beer > Ale" \
78
+ --container-type can --volume-unit "fluid ounces" --volume-min 12 --volume-max 16
79
+
80
+ # Search generic text, including applicant/company names
81
+ cola colas list -q "molson coors"
78
82
 
79
83
  # Pagination
80
84
  cola colas list -q "bourbon" --limit 50 --page 2
@@ -90,14 +94,24 @@ cola colas list -q "whiskey" --json | jq '.data[].brand_name'
90
94
 
91
95
  | Option | Description |
92
96
  |--------|-------------|
93
- | `-q, --query` | Full-text search query |
94
- | `--product-type` | Filter by type: `malt beverage`, `wine`, `distilled spirits` |
97
+ | `-q, --query` | Generic text query across brand, product, permit, applicant/company, and related text |
98
+ | `--product-type` | Filter by TTB type: `malt beverage`, `wine`, `distilled spirits`; can be used multiple times |
99
+ | `--category` | Filter by derived category: `Beer`, `Wine`, `Liquor`; can be used multiple times |
100
+ | `--derived-subcategory` | Filter by derived category path prefix, such as `Beer > Ale` |
95
101
  | `--origin` | Filter by country/state |
102
+ | `--domestic-or-imported` | Filter by `domestic` or `imported` |
103
+ | `--status` | Filter by application status |
96
104
  | `--brand` | Filter by brand name (partial match) |
105
+ | `--permit-number` | Filter by exact permit number |
106
+ | `--barcode` | Filter by exact main barcode value |
97
107
  | `--date-from` | Minimum approval date (YYYY-MM-DD) |
98
108
  | `--date-to` | Maximum approval date (YYYY-MM-DD) |
99
109
  | `--abv-min` | Minimum ABV percentage |
100
110
  | `--abv-max` | Maximum ABV percentage |
111
+ | `--volume-unit` | Package volume unit; required with `--volume-min` or `--volume-max` |
112
+ | `--volume-min` | Minimum package volume |
113
+ | `--volume-max` | Maximum package volume |
114
+ | `--container-type` | Derived container type; can be used multiple times |
101
115
  | `--limit` | Results per page (max 100) |
102
116
  | `--page` | Page number |
103
117
  | `--json` | Output as JSON |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "colacloud-cli"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "Command-line interface for the COLA Cloud API"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -181,26 +181,46 @@ class ColaCloudClient:
181
181
  self,
182
182
  query: str | None = None,
183
183
  product_type: str | None = None,
184
+ category: str | None = None,
185
+ derived_subcategory: str | None = None,
184
186
  origin: str | None = None,
187
+ domestic_or_imported: str | None = None,
188
+ status: str | None = None,
185
189
  brand_name: str | None = None,
190
+ permit_number: str | None = None,
191
+ barcode_value: str | None = None,
186
192
  approval_date_from: str | None = None,
187
193
  approval_date_to: str | None = None,
188
194
  abv_min: float | None = None,
189
195
  abv_max: float | None = None,
196
+ volume_unit: str | None = None,
197
+ volume_min: float | None = None,
198
+ volume_max: float | None = None,
199
+ container_type: str | None = None,
190
200
  page: int = 1,
191
201
  per_page: int = 20,
192
202
  ) -> dict[str, Any]:
193
203
  """Search and filter COLAs.
194
204
 
195
205
  Args:
196
- query: Full-text search query.
197
- product_type: Filter by product type.
206
+ query: Text search query, including applicant/company names.
207
+ product_type: Filter by one or more TTB product types.
208
+ category: Filter by derived top-level category.
209
+ derived_subcategory: Filter by derived category path prefix.
198
210
  origin: Filter by country/state.
211
+ domestic_or_imported: Filter by domestic/imported origin.
212
+ status: Filter by application status.
199
213
  brand_name: Filter by brand name (partial match).
214
+ permit_number: Filter by exact permit number.
215
+ barcode_value: Filter by exact main barcode value.
200
216
  approval_date_from: Filter by minimum approval date (YYYY-MM-DD).
201
217
  approval_date_to: Filter by maximum approval date (YYYY-MM-DD).
202
218
  abv_min: Filter by minimum ABV.
203
219
  abv_max: Filter by maximum ABV.
220
+ volume_unit: Filter by volume unit. Required with volume_min/volume_max.
221
+ volume_min: Filter by minimum package volume.
222
+ volume_max: Filter by maximum package volume.
223
+ container_type: Filter by one or more derived container types.
204
224
  page: Page number.
205
225
  per_page: Results per page (max 100).
206
226
 
@@ -215,10 +235,22 @@ class ColaCloudClient:
215
235
  params["q"] = query
216
236
  if product_type:
217
237
  params["product_type"] = product_type
238
+ if category:
239
+ params["category"] = category
240
+ if derived_subcategory:
241
+ params["derived_subcategory"] = derived_subcategory
218
242
  if origin:
219
243
  params["origin"] = origin
244
+ if domestic_or_imported:
245
+ params["domestic_or_imported"] = domestic_or_imported
246
+ if status:
247
+ params["status"] = status
220
248
  if brand_name:
221
249
  params["brand_name"] = brand_name
250
+ if permit_number:
251
+ params["permit_number"] = permit_number
252
+ if barcode_value:
253
+ params["barcode_value"] = barcode_value
222
254
  if approval_date_from:
223
255
  params["approval_date_from"] = approval_date_from
224
256
  if approval_date_to:
@@ -227,6 +259,14 @@ class ColaCloudClient:
227
259
  params["abv_min"] = abv_min
228
260
  if abv_max is not None:
229
261
  params["abv_max"] = abv_max
262
+ if volume_unit:
263
+ params["volume_unit"] = volume_unit
264
+ if volume_min is not None:
265
+ params["volume_min"] = volume_min
266
+ if volume_max is not None:
267
+ params["volume_max"] = volume_max
268
+ if container_type:
269
+ params["container_type"] = container_type
230
270
 
231
271
  response = self._client.get("/colas", params=params)
232
272
  return self._handle_response(response)
@@ -13,6 +13,12 @@ from colacloud_cli.formatters import (
13
13
  )
14
14
 
15
15
 
16
+ def _join_multi(values: tuple[str, ...]) -> str | None:
17
+ parts = [part.strip() for value in values for part in value.split(",")]
18
+ joined = ",".join(part for part in parts if part)
19
+ return joined or None
20
+
21
+
16
22
  @click.group(name="colas")
17
23
  def colas_group():
18
24
  """Search and retrieve COLA records."""
@@ -23,13 +29,32 @@ def colas_group():
23
29
  @click.option("-q", "--query", help="Full-text search query.")
24
30
  @click.option(
25
31
  "--product-type",
32
+ multiple=True,
26
33
  type=click.Choice(
27
34
  ["malt beverage", "wine", "distilled spirits"], case_sensitive=False
28
35
  ),
29
- help="Filter by product type.",
36
+ help="Filter by TTB product type. Can be used multiple times.",
37
+ )
38
+ @click.option(
39
+ "--category",
40
+ multiple=True,
41
+ type=click.Choice(["Beer", "Wine", "Liquor"], case_sensitive=False),
42
+ help="Filter by derived top-level category. Can be used multiple times.",
43
+ )
44
+ @click.option(
45
+ "--derived-subcategory",
46
+ help='Filter by derived category path prefix, e.g. "Beer > Ale".',
30
47
  )
31
48
  @click.option("--origin", help="Filter by origin (country/state).")
49
+ @click.option(
50
+ "--domestic-or-imported",
51
+ type=click.Choice(["domestic", "imported"], case_sensitive=False),
52
+ help="Filter by domestic/imported origin.",
53
+ )
54
+ @click.option("--status", help="Filter by application status.")
32
55
  @click.option("--brand", "brand_name", help="Filter by brand name (partial match).")
56
+ @click.option("--permit-number", help="Filter by exact permit number.")
57
+ @click.option("--barcode", "barcode_value", help="Filter by exact main barcode value.")
33
58
  @click.option(
34
59
  "--date-from",
35
60
  "approval_date_from",
@@ -42,6 +67,44 @@ def colas_group():
42
67
  )
43
68
  @click.option("--abv-min", type=float, help="Filter by minimum ABV.")
44
69
  @click.option("--abv-max", type=float, help="Filter by maximum ABV.")
70
+ @click.option(
71
+ "--volume-unit",
72
+ type=click.Choice(
73
+ [
74
+ "beer barrels",
75
+ "fluid ounces",
76
+ "gallons",
77
+ "liters",
78
+ "milliliters",
79
+ "pints",
80
+ "quarts",
81
+ ],
82
+ case_sensitive=False,
83
+ ),
84
+ help="Filter by package volume unit. Required with --volume-min/--volume-max.",
85
+ )
86
+ @click.option("--volume-min", type=float, help="Filter by minimum package volume.")
87
+ @click.option("--volume-max", type=float, help="Filter by maximum package volume.")
88
+ @click.option(
89
+ "--container-type",
90
+ multiple=True,
91
+ type=click.Choice(
92
+ [
93
+ "bag",
94
+ "bottle",
95
+ "box",
96
+ "can",
97
+ "carton",
98
+ "cask",
99
+ "jug",
100
+ "keg",
101
+ "pod",
102
+ "pouch",
103
+ ],
104
+ case_sensitive=False,
105
+ ),
106
+ help="Filter by derived container type. Can be used multiple times.",
107
+ )
45
108
  @click.option(
46
109
  "--limit", "per_page", default=20, type=int, help="Results per page (max 100)."
47
110
  )
@@ -49,13 +112,23 @@ def colas_group():
49
112
  @click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
50
113
  def list_colas(
51
114
  query: str | None,
52
- product_type: str | None,
115
+ product_type: tuple[str, ...],
116
+ category: tuple[str, ...],
117
+ derived_subcategory: str | None,
53
118
  origin: str | None,
119
+ domestic_or_imported: str | None,
120
+ status: str | None,
54
121
  brand_name: str | None,
122
+ permit_number: str | None,
123
+ barcode_value: str | None,
55
124
  approval_date_from: str | None,
56
125
  approval_date_to: str | None,
57
126
  abv_min: float | None,
58
127
  abv_max: float | None,
128
+ volume_unit: str | None,
129
+ volume_min: float | None,
130
+ volume_max: float | None,
131
+ container_type: tuple[str, ...],
59
132
  per_page: int,
60
133
  page: int,
61
134
  as_json: bool,
@@ -69,28 +142,40 @@ def list_colas(
69
142
  cola colas list -q "bourbon"
70
143
 
71
144
  \b
72
- # List wines from California
73
- cola colas list --product-type wine --origin california
145
+ # List wine or beer from California
146
+ cola colas list --product-type wine --product-type "malt beverage"
147
+ --origin california
74
148
 
75
149
  \b
76
- # Find high-ABV spirits
77
- cola colas list --product-type "distilled spirits" --abv-min 50
150
+ # Find 12 oz canned beer
151
+ cola colas list --category Beer --container-type can
152
+ --volume-unit "fluid ounces" --volume-min 12 --volume-max 12
78
153
 
79
154
  \b
80
- # Search by brand
81
- cola colas list --brand "buffalo trace"
155
+ # Search generic text, including applicant/company names
156
+ cola colas list -q "molson coors"
82
157
  """
83
158
  try:
84
159
  with get_client() as client:
85
160
  result = client.list_colas(
86
161
  query=query,
87
- product_type=product_type,
162
+ product_type=_join_multi(product_type),
163
+ category=_join_multi(category),
164
+ derived_subcategory=derived_subcategory,
88
165
  origin=origin,
166
+ domestic_or_imported=domestic_or_imported,
167
+ status=status,
89
168
  brand_name=brand_name,
169
+ permit_number=permit_number,
170
+ barcode_value=barcode_value,
90
171
  approval_date_from=approval_date_from,
91
172
  approval_date_to=approval_date_to,
92
173
  abv_min=abv_min,
93
174
  abv_max=abv_max,
175
+ volume_unit=volume_unit,
176
+ volume_min=volume_min,
177
+ volume_max=volume_max,
178
+ container_type=_join_multi(container_type),
94
179
  page=page,
95
180
  per_page=min(per_page, 100),
96
181
  )
@@ -86,9 +86,19 @@ class TestListColas:
86
86
 
87
87
  client.list_colas(
88
88
  query="bourbon",
89
- product_type="distilled spirits",
89
+ product_type="distilled spirits,wine",
90
+ category="Liquor,Wine",
91
+ derived_subcategory="Liquor > Whiskey",
90
92
  origin="kentucky",
93
+ domestic_or_imported="domestic",
94
+ status="approved",
95
+ permit_number="KY-I-12345",
96
+ barcode_value="012345678905",
91
97
  abv_min=40.0,
98
+ volume_unit="milliliters",
99
+ volume_min=375,
100
+ volume_max=750,
101
+ container_type="bottle,can",
92
102
  )
93
103
 
94
104
  request = route.calls[0].request
@@ -96,6 +106,16 @@ class TestListColas:
96
106
  assert "product_type=distilled" in str(request.url)
97
107
  assert "origin=kentucky" in str(request.url)
98
108
  assert "abv_min=40" in str(request.url)
109
+ assert "category=Liquor%2CWine" in str(request.url)
110
+ assert "derived_subcategory=Liquor+%3E+Whiskey" in str(request.url)
111
+ assert "domestic_or_imported=domestic" in str(request.url)
112
+ assert "status=approved" in str(request.url)
113
+ assert "permit_number=KY-I-12345" in str(request.url)
114
+ assert "barcode_value=012345678905" in str(request.url)
115
+ assert "volume_unit=milliliters" in str(request.url)
116
+ assert "volume_min=375" in str(request.url)
117
+ assert "volume_max=750" in str(request.url)
118
+ assert "container_type=bottle%2Ccan" in str(request.url)
99
119
 
100
120
 
101
121
  class TestGetCola:
@@ -44,7 +44,7 @@ class TestColasCommands:
44
44
  @respx.mock
45
45
  def test_colas_list(self, runner, api_key):
46
46
  """colas list returns results."""
47
- respx.get("https://app.colacloud.us/api/v1/colas").mock(
47
+ route = respx.get("https://app.colacloud.us/api/v1/colas").mock(
48
48
  return_value=httpx.Response(
49
49
  200,
50
50
  json={
@@ -67,9 +67,38 @@ class TestColasCommands:
67
67
  )
68
68
  )
69
69
 
70
- result = runner.invoke(cli, ["colas", "list", "-q", "test"])
70
+ result = runner.invoke(
71
+ cli,
72
+ [
73
+ "colas",
74
+ "list",
75
+ "-q",
76
+ "test",
77
+ "--product-type",
78
+ "wine",
79
+ "--product-type",
80
+ "malt beverage",
81
+ "--category",
82
+ "Beer",
83
+ "--container-type",
84
+ "can",
85
+ "--volume-unit",
86
+ "fluid ounces",
87
+ "--volume-min",
88
+ "12",
89
+ "--volume-max",
90
+ "16",
91
+ ],
92
+ )
71
93
  assert result.exit_code == 0
72
94
  assert "Test Brand" in result.output
95
+ request_url = str(route.calls[0].request.url)
96
+ assert "product_type=wine%2Cmalt+beverage" in request_url
97
+ assert "category=Beer" in request_url
98
+ assert "container_type=can" in request_url
99
+ assert "volume_unit=fluid+ounces" in request_url
100
+ assert "volume_min=12" in request_url
101
+ assert "volume_max=16" in request_url
73
102
 
74
103
  @respx.mock
75
104
  def test_colas_list_json(self, runner, api_key):
File without changes
File without changes
File without changes