Files
studspider/src/asquare.py
T
2026-01-04 11:41:26 -08:00

338 lines
11 KiB
Python

import os
import dotenv
import json
import asyncio
import uuid
import re
from time import time
from . import db
from . import get_sq_env
def get_set_number(text: str) -> int:
# Remove all letters, keeping only digits and spaces
numbers = re.findall(r"\d{5}", text)
if not numbers:
raise ValueError("No five-digit number found in string")
return int(numbers[0])
SQUARE_TOKEN = get_sq_env.SQUARE_TOKEN
LEGO_CAT_ID = get_sq_env.LEGO_CAT_ID
ENVIRONMENT = get_sq_env.environment
from square import AsyncSquare
from square.environment import SquareEnvironment
client = AsyncSquare(token=SQUARE_TOKEN, environment=ENVIRONMENT) # type: ignore
async def get_itm(sku: str):
"""
Asynchronously retrieves item details from Square Catalog using a SKU.
Args:
sku (str): The SKU or text filter to search for the item.
Returns:
dict: A dictionary containing the item's name and ID.
Raises:
IndexError: If no items are found matching the SKU.
Exception: If there is an error communicating with the Square API.
"""
result = await client.catalog.search_items(text_filter=sku, limit=1)
result_json = json.loads(result.model_dump_json())
name = result_json["items"][0]["item_data"]["name"]
itm_id = result_json["items"][0]["id"]
return {"name": name, "id": itm_id}
async def get_item_by_id(item_id: str) -> dict:
"""
Retrieve full item details by Square catalog item ID.
Args:
item_id: Square catalog item ID
Returns:
dict: Item details including name, category_id, variations, etc.
Raises:
Exception: If item not found or API error
"""
try:
# Search for item by searching all items and filtering
# This is not ideal but the async client lacks direct retrieval
result = await client.catalog.search(
object_types=["ITEM"],
limit=1000, # Broad search
)
result_json = json.loads(result.model_dump_json())
if not result_json.get("objects"):
raise ValueError(f"No items found")
# Find the specific item by ID
obj = None
for item in result_json["objects"]:
if item["id"] == item_id:
obj = item
break
if not obj:
raise ValueError(f"Item {item_id} not found")
item_data = obj.get("item_data", {})
return {
"id": obj["id"],
"name": item_data.get("name", ""),
"description": item_data.get("description", ""),
"category_id": item_data.get("category_id"),
"variations": item_data.get("variations", []),
"updated_at": obj.get("updated_at"),
"version": obj.get("version"),
}
except Exception as e:
print(f"[@ERROR] Failed to get item {item_id}: {e}")
raise
async def get_item_by_variation_id(variation_id: str) -> dict:
"""
Retrieve parent item details from a variation ID.
Webhooks send variation IDs (inventory.count.updated), but we need
the parent item to extract the set number from the name.
Since async Square API lacks direct object retrieval, we search all items
in the LEGO category and match variations.
Args:
variation_id: Square catalog item variation ID
Returns:
dict: Parent item details
Raises:
Exception: If variation/item not found or API error
"""
try:
# Search LEGO category items to find which one has this variation
print(f"[@DEBUG] Searching for variation {variation_id} in LEGO category")
result = await client.catalog.search(
object_types=["ITEM"],
query={
"exact_query": {
"attribute_name": "category_id",
"attribute_value": LEGO_CAT_ID,
}
},
limit=1000,
)
result_json = json.loads(result.model_dump_json())
if not result_json.get("objects"):
raise ValueError(f"No items found in LEGO category")
# Search through items to find which has this variation
for obj in result_json["objects"]:
item_data = obj.get("item_data", {})
variations = item_data.get("variations", [])
for variation in variations:
if variation.get("id") == variation_id:
# Found the parent item!
print(f"[@DEBUG] Found parent item: {obj['id']}")
return {
"id": obj["id"],
"name": item_data.get("name", ""),
"description": item_data.get("description", ""),
"category_id": item_data.get("category_id"),
"variations": variations,
"updated_at": obj.get("updated_at"),
"version": obj.get("version"),
}
raise ValueError(
f"Variation {variation_id} not found in any LEGO category item"
)
except Exception as e:
print(f"[@ERROR] Failed to get item from variation {variation_id}: {e}")
raise
async def update_itm(item_id: str, lego_set: dict):
"""
Asynchronously updates an item in the Square catalog based on the provided LEGO set information.
Args:
item_id (str): Square catalog item ID to update
lego_set (dict): A dictionary containing LEGO set details from abricksetapi. Expected keys include:
- "set_num": LEGO set number (e.g., "75192")
- "set_name": Official LEGO set name
- "set_theme": LEGO theme (e.g., "Star Wars")
- "set_barcode": UPC barcode
- "set_image": Thumbnail URL
- "pw_name": Formatted name for Square inventory (e.g., "75192 - STAR WARS UCS Millennium Falcon")
- "set_adjustredprice": Retail price with 10% markup applied
Returns:
bool: True if the update operation was successful.
Raises:
KeyError: If expected keys are missing from the input dictionary.
Exception: For errors during Square API interaction.
"""
try:
set_number = lego_set["set_num"]
set_name = lego_set["set_name"]
set_theme = lego_set["set_theme"]
set_upc = lego_set["set_barcode"]
set_image = lego_set["set_image"]
pw_name = lego_set["pw_name"]
set_adjustedprice = lego_set["set_adjustredprice"]
# Convert price to cents for Square API
price_in_cents = int(set_adjustedprice * 100)
# Get current item to retrieve variation ID
item = await get_item_by_id(item_id)
variations = item.get("variations", [])
if not variations:
print(
f"[@WARNING] Item {item_id} has no variations, creating new variation"
)
variation_id = None
else:
variation_id = variations[0]["id"]
# Build upsert body
upsert_body = {
"idempotency_key": str(uuid.uuid4()),
"object": {
"type": "ITEM",
"id": item_id,
"item_data": {
"name": pw_name,
"description": f"{set_name} - {set_theme}",
"variations": [
{
"type": "ITEM_VARIATION",
"id": (
variation_id if variation_id else "#TEMP_VARIATION_ID"
),
"item_variation_data": {
"item_id": item_id,
"name": "Regular",
"pricing_type": "FIXED_PRICING",
"price_money": {
"amount": price_in_cents,
"currency": "USD",
},
"sku": set_upc if set_upc else None,
},
}
],
},
},
}
# Remove None values
if not set_upc:
del upsert_body["object"]["item_data"]["variations"][0][
"item_variation_data"
]["sku"]
print(f"[@DEBUG] Upserting item {item_id} with name: {pw_name}")
# Perform upsert
result = await client.catalog.upsert_catalog_object(body=upsert_body)
result_json = json.loads(result.model_dump_json())
if result_json.get("catalog_object"):
print(f"[@DEBUG] ✓ Successfully updated item {item_id}")
# TODO: Handle image upload separately (Square Images API)
# This requires multipart/form-data upload
return True
else:
print(
f"[@ERROR] Failed to update item {item_id}: No catalog_object in response"
)
return False
except Exception as e:
print(f"[@ERROR] Failed to update item {item_id}: {e}")
import traceback
traceback.print_exc()
return False
async def acat2id(cat_id: str):
"""
Return a list of dictionaries containing item IDs and names within the specified category ID.
Args:
cat_id (str): The category ID to search for items.
Returns:
list[dict]: A list of dictionaries, each containing:
- 'id': The item's ID
- 'name': The item's name
"""
result = await client.catalog.search(
object_types=["ITEM"],
query={
"exact_query": {
"attribute_name": "category_id",
"attribute_value": cat_id,
}
},
)
result_json = json.loads(result.model_dump_json())
items = [
{
"id": obj["id"],
"name": obj.get("item_data", {}).get("name", "Unknown"),
"number": (
None
if not obj.get("item_data", {}).get("name", "")
else (
get_set_number(obj.get("item_data", {}).get("name", ""))
if re.findall(r"\d{5}", obj.get("item_data", {}).get("name", ""))
else None
)
),
"updated_at": obj.get("updated_at"),
"version": obj.get("version"),
}
for obj in result_json.get("objects", [])
]
return items
async def test() -> None:
result = await acat2id(LEGO_CAT_ID) # type: ignore
print("OBJECTS IN LEGO CATEGORY:")
for item in result:
print(f"ID: {item['id']}, Name: {item['name']}, Updated: {item['updated_at']}")
"""
result = await sq.catalog.search_items(text_filter="71433", limit=1)
result_json = json.loads(result.model_dump_json())
name = result_json["items"][0]["item_data"]["name"]
print(f"Item name: {name}")"""
if __name__ == "__main__":
print(f"@[DEBUG] ENV:\t{ENVIRONMENT}")
print(f"@[DEBUG] CATID:\t{LEGO_CAT_ID}")
asyncio.run(test())