behaviorgpt 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: behaviorgpt
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Python client for BehaviorGPT, UnboxAI's Large Behavioral Model for search, recommendations and personalization
5
5
  Keywords: recommendations,personalization,search,behavioral model,unboxai
6
6
  Author: Unbox AI
@@ -123,7 +123,7 @@ The model can only return items it knows about. Out of the box the client uses `
123
123
 
124
124
  - [`docs/catalog-format.md`](https://github.com/Unbox-AI/behaviorgpt/blob/main/docs/catalog-format.md) lists the required columns and types.
125
125
  - A catalog can hold at most 100,000 items.
126
- - Each API key can hold 2 catalogs. The file name is the catalog's name: uploading a file with the same name again refreshes that catalog and keeps its `catalog_id`, and a new name takes one of the 2 slots. A failed upload does not use up a slot. Once both slots are in use, an upload under a new name is refused; to remove a catalog or raise the limit, contact alexander@unboxai.com.
126
+ - Each API key can hold 2 catalogs. The file name is the catalog's name: uploading a file with the same name again refreshes that catalog and keeps its `catalog_id`, and a new name takes one of the 2 slots. A failed upload does not use up a slot. Once both slots are in use, an upload under a new name is refused until you delete one of them (see below); to raise the limit, contact alexander@unboxai.com.
127
127
  - [`examples/sample_catalog.csv`](https://github.com/Unbox-AI/behaviorgpt/blob/main/examples/sample_catalog.csv) is an example only, showing what each column should contain. Your own file will look different depending on your catalog, and it has to be converted to the parquet structure described in the format doc before upload.
128
128
 
129
129
  ```python
@@ -146,6 +146,19 @@ client.add_items("new_items.parquet", catalog_id=job.catalog_id, wait=True)
146
146
  - Uploading the catalog's file again with `embed` replaces the whole catalog, added items included. Put them in that file too to keep them.
147
147
  - A catalog embedded before `add_items` existed needs one more full upload before items can be added to it.
148
148
 
149
+ ### Listing and deleting catalogs
150
+
151
+ ```python
152
+ for c in client.list_catalogs():
153
+ print(c.catalog_id, c.catalog_name, c.status, c.owned)
154
+
155
+ client.delete_catalog("cat_...")
156
+ ```
157
+
158
+ - `list_catalogs()` returns your own catalogs, in any status, and the shared ones that are ready. `owned` marks your own.
159
+ - `delete_catalog()` removes one of your catalogs with its items and frees its slot. It cannot be undone.
160
+ - Deleting another key's catalog fails with status 404, the same as an unknown id. While an `embed` or `add_items` job runs for the catalog, deleting fails with status 409; try again once the job is ready or failed.
161
+
149
162
  If mapping your data to the format is hard, open a coding agent in this directory and ask it to embed your catalog. The repo ships a skill at [`.claude/skills/embed-catalog/SKILL.md`](https://github.com/Unbox-AI/behaviorgpt/blob/main/.claude/skills/embed-catalog/SKILL.md) that walks the agent through mapping your columns, building and validating the parquet, checking image URLs, and uploading. Claude Code picks it up automatically; for other agents, point them at that file.
150
163
 
151
164
  ### Events
@@ -106,7 +106,7 @@ The model can only return items it knows about. Out of the box the client uses `
106
106
 
107
107
  - [`docs/catalog-format.md`](https://github.com/Unbox-AI/behaviorgpt/blob/main/docs/catalog-format.md) lists the required columns and types.
108
108
  - A catalog can hold at most 100,000 items.
109
- - Each API key can hold 2 catalogs. The file name is the catalog's name: uploading a file with the same name again refreshes that catalog and keeps its `catalog_id`, and a new name takes one of the 2 slots. A failed upload does not use up a slot. Once both slots are in use, an upload under a new name is refused; to remove a catalog or raise the limit, contact alexander@unboxai.com.
109
+ - Each API key can hold 2 catalogs. The file name is the catalog's name: uploading a file with the same name again refreshes that catalog and keeps its `catalog_id`, and a new name takes one of the 2 slots. A failed upload does not use up a slot. Once both slots are in use, an upload under a new name is refused until you delete one of them (see below); to raise the limit, contact alexander@unboxai.com.
110
110
  - [`examples/sample_catalog.csv`](https://github.com/Unbox-AI/behaviorgpt/blob/main/examples/sample_catalog.csv) is an example only, showing what each column should contain. Your own file will look different depending on your catalog, and it has to be converted to the parquet structure described in the format doc before upload.
111
111
 
112
112
  ```python
@@ -129,6 +129,19 @@ client.add_items("new_items.parquet", catalog_id=job.catalog_id, wait=True)
129
129
  - Uploading the catalog's file again with `embed` replaces the whole catalog, added items included. Put them in that file too to keep them.
130
130
  - A catalog embedded before `add_items` existed needs one more full upload before items can be added to it.
131
131
 
132
+ ### Listing and deleting catalogs
133
+
134
+ ```python
135
+ for c in client.list_catalogs():
136
+ print(c.catalog_id, c.catalog_name, c.status, c.owned)
137
+
138
+ client.delete_catalog("cat_...")
139
+ ```
140
+
141
+ - `list_catalogs()` returns your own catalogs, in any status, and the shared ones that are ready. `owned` marks your own.
142
+ - `delete_catalog()` removes one of your catalogs with its items and frees its slot. It cannot be undone.
143
+ - Deleting another key's catalog fails with status 404, the same as an unknown id. While an `embed` or `add_items` job runs for the catalog, deleting fails with status 409; try again once the job is ready or failed.
144
+
132
145
  If mapping your data to the format is hard, open a coding agent in this directory and ask it to embed your catalog. The repo ships a skill at [`.claude/skills/embed-catalog/SKILL.md`](https://github.com/Unbox-AI/behaviorgpt/blob/main/.claude/skills/embed-catalog/SKILL.md) that walks the agent through mapping your columns, building and validating the parquet, checking image URLs, and uploading. Claude Code picks it up automatically; for other agents, point them at that file.
133
146
 
134
147
  ### Events
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "behaviorgpt"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "Python client for BehaviorGPT, UnboxAI's Large Behavioral Model for search, recommendations and personalization"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "behaviorgpt"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "Python client for BehaviorGPT, UnboxAI's Large Behavioral Model for search, recommendations and personalization"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -18,6 +18,7 @@ from behaviorgpt.types import (
18
18
  AddItemsJobDetails,
19
19
  AddToCart,
20
20
  CartItem,
21
+ CatalogSummary,
21
22
  Domains,
22
23
  EmbedJobDetails,
23
24
  Item,
@@ -290,6 +291,22 @@ class UnboxAIClient(_BaseClient):
290
291
  def job_status(self, job_id: str) -> JobStatus:
291
292
  return self.catalogs.get_job_status(job_id)
292
293
 
294
+ def list_catalogs(self) -> list[CatalogSummary]:
295
+ """The catalogs this key can query: its own, in any status, and the
296
+ shared ones that are ready. `owned` marks the key's own."""
297
+ return self.catalogs.list_catalogs()
298
+
299
+ def delete_catalog(self, catalog_id: str) -> None:
300
+ """Delete one of this key's catalogs, its items and its index, and
301
+ free its slot. This cannot be undone.
302
+
303
+ A catalog that is not this key's raises `UnboxAIError` with status
304
+ 404, the same as an unknown one. While an embed or `add_items` job runs
305
+ for the catalog, it raises with status 409; delete once the job is
306
+ ready or failed.
307
+ """
308
+ self.catalogs.delete_catalog(catalog_id)
309
+
293
310
  def similar_items(
294
311
  self,
295
312
  item_id: str,
@@ -454,6 +471,14 @@ class AsyncUnboxAIClient(_BaseClient):
454
471
  async def job_status(self, job_id: str) -> JobStatus:
455
472
  return await self.catalogs.get_job_status(job_id)
456
473
 
474
+ async def list_catalogs(self) -> list[CatalogSummary]:
475
+ """See `UnboxAIClient.list_catalogs`."""
476
+ return await self.catalogs.list_catalogs()
477
+
478
+ async def delete_catalog(self, catalog_id: str) -> None:
479
+ """See `UnboxAIClient.delete_catalog`."""
480
+ await self.catalogs.delete_catalog(catalog_id)
481
+
457
482
  async def similar_items(
458
483
  self,
459
484
  item_id: str,
@@ -4,6 +4,7 @@ import warnings
4
4
  from datetime import UTC, datetime
5
5
  from pathlib import Path
6
6
  from typing import Any, Callable, Optional
7
+ from urllib.parse import quote
7
8
 
8
9
  import httpx
9
10
  import pyarrow as pa
@@ -13,6 +14,7 @@ from behaviorgpt._exceptions import UnboxAIError
13
14
  from behaviorgpt.resources._shared import handle_response
14
15
  from behaviorgpt.types import (
15
16
  AddItemsJobDetails,
17
+ CatalogSummary,
16
18
  EmbedJobDetails,
17
19
  JobStatus,
18
20
  SimilarItemsRequest,
@@ -133,6 +135,16 @@ def items_path(catalog_id: str) -> str:
133
135
  return f"/catalogs/{catalog_id}/items"
134
136
 
135
137
 
138
+ def catalog_path(catalog_id: str) -> str:
139
+ if not catalog_id:
140
+ raise ValueError("catalog_id is required to delete a catalog")
141
+ return f"/catalogs/{quote(catalog_id, safe='')}"
142
+
143
+
144
+ def catalog_summaries(response: httpx.Response) -> list[CatalogSummary]:
145
+ return [CatalogSummary(**c) for c in handle_response(response)["catalogs"]]
146
+
147
+
136
148
  def similar_items_body(
137
149
  item_id: str, catalog_id: str, limit: int, offset: int, filters: Optional[dict]
138
150
  ) -> dict:
@@ -405,6 +417,12 @@ class Catalogs:
405
417
  def get_umap(self, *, catalog_id: str):
406
418
  return umap_page(self._client.get(f"/{catalog_id}/umap"))
407
419
 
420
+ def list_catalogs(self) -> list[CatalogSummary]:
421
+ return catalog_summaries(self._client.get("/catalogs"))
422
+
423
+ def delete_catalog(self, catalog_id: str) -> None:
424
+ handle_response(self._client.delete(catalog_path(catalog_id)))
425
+
408
426
 
409
427
  class AsyncCatalogs:
410
428
  """The async counterpart of `Catalogs`; see it for what each method does."""
@@ -520,3 +538,9 @@ class AsyncCatalogs:
520
538
 
521
539
  async def get_umap(self, *, catalog_id: str) -> str:
522
540
  return umap_page(await self._client.get(f"/{catalog_id}/umap"))
541
+
542
+ async def list_catalogs(self) -> list[CatalogSummary]:
543
+ return catalog_summaries(await self._client.get("/catalogs"))
544
+
545
+ async def delete_catalog(self, catalog_id: str) -> None:
546
+ handle_response(await self._client.delete(catalog_path(catalog_id)))
@@ -16,6 +16,7 @@ from behaviorgpt.types.events import (
16
16
  )
17
17
  from behaviorgpt.types.http import (
18
18
  AddItemsJobDetails,
19
+ CatalogSummary,
19
20
  EmbedJobDetails,
20
21
  Item,
21
22
  ItemsPage,
@@ -42,6 +43,7 @@ __all__ = [
42
43
  "SimilarItemsRequest",
43
44
  "EmbedJobDetails",
44
45
  "AddItemsJobDetails",
46
+ "CatalogSummary",
45
47
  "JobStatus",
46
48
  "ItemsRejected",
47
49
  "RejectedItem",
@@ -148,6 +148,17 @@ class AddItemsJobDetails(EmbedJobDetails):
148
148
  products: int
149
149
 
150
150
 
151
+ class CatalogSummary(BaseModel):
152
+ catalog_id: str
153
+ # display label derived from the uploaded file name, server-side
154
+ catalog_name: str
155
+ # 'pending', 'ready' or 'failed'; shared catalogs are listed only when ready
156
+ status: str
157
+ # uploaded with this API key, so it counts against the key's catalog slots
158
+ # and this key can delete it
159
+ owned: bool
160
+
161
+
151
162
  class SimilarItemsRequest(BaseModel):
152
163
  model_config = ConfigDict(serialize_by_alias=True)
153
164
 
File without changes