test glue
This commit is contained in:
1 parent
03f6e84965
commit
d341684567
3 files changed
+411
-38
No files matched your search
+165
-11
@@ -51,10 +51,93 @@ async def get_itm(sku: str):
|
|||||||
return {"name": name, "id": itm_id}
|
return {"name": name, "id": itm_id}
|
||||||
|
|
||||||
|
|
||||||
async def update_itm(lego_set: dict):
|
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:
|
||||||
|
result = await client.catalog.retrieve_catalog_object(
|
||||||
|
object_id=item_id, include_related_objects=True
|
||||||
|
)
|
||||||
|
result_json = json.loads(result.model_dump_json())
|
||||||
|
|
||||||
|
if not result_json.get("object"):
|
||||||
|
raise ValueError(f"Item {item_id} not found")
|
||||||
|
|
||||||
|
obj = result_json["object"]
|
||||||
|
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.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
variation_id: Square catalog item variation ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Parent item details
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: If variation/item not found or API error
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get variation object
|
||||||
|
result = await client.catalog.retrieve_catalog_object(
|
||||||
|
object_id=variation_id, include_related_objects=True
|
||||||
|
)
|
||||||
|
result_json = json.loads(result.model_dump_json())
|
||||||
|
|
||||||
|
if not result_json.get("object"):
|
||||||
|
raise ValueError(f"Variation {variation_id} not found")
|
||||||
|
|
||||||
|
variation_obj = result_json["object"]
|
||||||
|
|
||||||
|
# Get parent item ID from variation
|
||||||
|
item_id = variation_obj.get("item_variation_data", {}).get("item_id")
|
||||||
|
|
||||||
|
if not item_id:
|
||||||
|
raise ValueError(f"No parent item_id found for variation {variation_id}")
|
||||||
|
|
||||||
|
# Retrieve parent item
|
||||||
|
return await get_item_by_id(item_id)
|
||||||
|
|
||||||
|
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.
|
Asynchronously updates an item in the Square catalog based on the provided LEGO set information.
|
||||||
Args:
|
Args:
|
||||||
|
item_id (str): Square catalog item ID to update
|
||||||
lego_set (dict): A dictionary containing LEGO set details from abricksetapi. Expected keys include:
|
lego_set (dict): A dictionary containing LEGO set details from abricksetapi. Expected keys include:
|
||||||
- "set_num": LEGO set number (e.g., "75192")
|
- "set_num": LEGO set number (e.g., "75192")
|
||||||
- "set_name": Official LEGO set name
|
- "set_name": Official LEGO set name
|
||||||
@@ -69,19 +152,90 @@ async def update_itm(lego_set: dict):
|
|||||||
KeyError: If expected keys are missing from the input dictionary.
|
KeyError: If expected keys are missing from the input dictionary.
|
||||||
Exception: For errors during Square API interaction.
|
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"]
|
||||||
|
|
||||||
set_number = lego_set["set_num"]
|
# Convert price to cents for Square API
|
||||||
set_name = lego_set["set_name"]
|
price_in_cents = int(set_adjustedprice * 100)
|
||||||
set_upc = lego_set["set_barcode"]
|
|
||||||
set_image = lego_set["set_image"]
|
|
||||||
pw_name = lego_set["pw_name"]
|
|
||||||
set_adjustedprice = lego_set["set_adjustredprice"]
|
|
||||||
|
|
||||||
result = await client.catalog.search_items(text_filter=set_number, limit=1)
|
# Get current item to retrieve variation ID
|
||||||
itm_id = json.loads(result.model_dump_json())["items"][0]["id"]
|
item = await get_item_by_id(item_id)
|
||||||
|
variations = item.get("variations", [])
|
||||||
|
|
||||||
# await sq.catalog.object.upsert()
|
if not variations:
|
||||||
return True
|
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):
|
async def acat2id(cat_id: str):
|
||||||
|
|||||||
+178
-24
@@ -9,33 +9,187 @@ from . import asquare
|
|||||||
from . import db
|
from . import db
|
||||||
from . import abricksetapi
|
from . import abricksetapi
|
||||||
from . import throttler
|
from . import throttler
|
||||||
|
|
||||||
Throttler = throttler.GlobalThrottler
|
Throttler = throttler.GlobalThrottler
|
||||||
|
|
||||||
|
|
||||||
LEGO_CAT_ID = get_sq_env.LEGO_CAT_ID
|
LEGO_CAT_ID = get_sq_env.LEGO_CAT_ID
|
||||||
|
|
||||||
# big glue boy
|
|
||||||
async def do_review() -> bool:
|
|
||||||
result = await asquare.acat2id(LEGO_CAT_ID) # type: ignore
|
|
||||||
for item in result:
|
|
||||||
if not re.match(r".*\d{5}.*", item["name"]):
|
|
||||||
missing = []
|
|
||||||
print(f"{item} is missing its set number!")
|
|
||||||
#print(f"[@DEBUG] Upserting {item['id']} into REVIEW table")
|
|
||||||
item_sanitized = {
|
|
||||||
"sq_object": item["id"],
|
|
||||||
"set_name": item["name"],
|
|
||||||
"set_number": None,
|
|
||||||
"time_added": time
|
|
||||||
}
|
|
||||||
missing.append(item_sanitized)
|
|
||||||
|
|
||||||
for item in missing:
|
async def enrich_single_item(item_id: str, logger=None) -> bool:
|
||||||
print(item)
|
"""
|
||||||
# sq_item = await db.fetch(item["id"])
|
Enrich a single Square catalog item with Brickset data.
|
||||||
if item["id"] != sq_item["sq_object"]:
|
|
||||||
print(f"[@DEBUG] {item['id']} is missing)")
|
Flow:
|
||||||
#await db.upsert("MISSING", item_sanitized, "sq_object")
|
1. Get item details from Square (by item_id)
|
||||||
# #print(f"[@DEBUG] Upserted {item['id']} into REVIEW table")
|
2. Extract 5-digit set number from name (regex \d{5})
|
||||||
#print(f"[@DEBUG] {item['id']} already in REVIEW table")
|
3. Fetch Brickset data (with throttling)
|
||||||
return True
|
4. Update Square item with enriched data
|
||||||
|
5. Handle errors (no set number → MISSING table)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item_id: Square catalog item ID
|
||||||
|
logger: Optional logger instance for debug output
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if logger:
|
||||||
|
logger.info(f"Starting enrichment for item {item_id}")
|
||||||
|
else:
|
||||||
|
print(f"[@DEBUG] Starting enrichment for item {item_id}")
|
||||||
|
|
||||||
|
# Get item details from Square
|
||||||
|
item = await asquare.get_item_by_id(item_id)
|
||||||
|
item_name = item.get("name", "")
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger.debug(f"Item name: {item_name}")
|
||||||
|
else:
|
||||||
|
print(f"[@DEBUG] Item name: {item_name}")
|
||||||
|
|
||||||
|
# Extract 5-digit set number
|
||||||
|
set_numbers = re.findall(r"\d{5}", item_name)
|
||||||
|
|
||||||
|
if not set_numbers:
|
||||||
|
# No set number found - log to MISSING table
|
||||||
|
if logger:
|
||||||
|
logger.warning(f"No set number found in item {item_id}: {item_name}")
|
||||||
|
else:
|
||||||
|
print(f"[@WARNING] No set number found in item {item_id}: {item_name}")
|
||||||
|
|
||||||
|
await db.upsert(
|
||||||
|
"MISSING",
|
||||||
|
{
|
||||||
|
"sq_object": item_id,
|
||||||
|
"time_added": int(time()),
|
||||||
|
"set_number": None,
|
||||||
|
"set_name": item_name,
|
||||||
|
},
|
||||||
|
"sq_object",
|
||||||
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
set_number = set_numbers[0]
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger.info(f"Extracted set number: {set_number}")
|
||||||
|
else:
|
||||||
|
print(f"[@DEBUG] Extracted set number: {set_number}")
|
||||||
|
|
||||||
|
# Fetch Brickset data (with throttling)
|
||||||
|
brickset_client = abricksetapi.AsyncBricksetAPI(debug=True, logger=logger)
|
||||||
|
lego_set = await brickset_client.throttled_get_set(set_number)
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger.info(f"Retrieved Brickset data for {lego_set['pw_name']}")
|
||||||
|
else:
|
||||||
|
print(f"[@DEBUG] Retrieved Brickset data for {lego_set['pw_name']}")
|
||||||
|
|
||||||
|
# Check if manual price review needed
|
||||||
|
if lego_set.get("requires_manual_price"):
|
||||||
|
if logger:
|
||||||
|
logger.info(f"Item {item_id} requires manual price review (≥$110)")
|
||||||
|
else:
|
||||||
|
print(f"[@DEBUG] Item {item_id} requires manual price review")
|
||||||
|
|
||||||
|
await db.upsert(
|
||||||
|
"REVIEW",
|
||||||
|
{
|
||||||
|
"sq_object": item_id,
|
||||||
|
"price": lego_set["set_adjustredprice"],
|
||||||
|
"date_modified": int(time()),
|
||||||
|
},
|
||||||
|
"sq_object",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update Square item with enriched data
|
||||||
|
success = await asquare.update_itm(item_id, lego_set)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
if logger:
|
||||||
|
logger.info(f"✓ Successfully enriched item {item_id}")
|
||||||
|
else:
|
||||||
|
print(f"[@DEBUG] ✓ Successfully enriched item {item_id}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if logger:
|
||||||
|
logger.error(f"Failed to update Square item {item_id}")
|
||||||
|
else:
|
||||||
|
print(f"[@ERROR] Failed to update Square item {item_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
if logger:
|
||||||
|
logger.error(f"Value error enriching item {item_id}: {e}")
|
||||||
|
else:
|
||||||
|
print(f"[@ERROR] Value error enriching item {item_id}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if logger:
|
||||||
|
logger.error(f"Unexpected error enriching item {item_id}: {e}")
|
||||||
|
else:
|
||||||
|
print(f"[@ERROR] Unexpected error enriching item {item_id}: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def enrich_bulk(logger=None) -> None:
|
||||||
|
"""
|
||||||
|
Enrich all items in the LEGO category (bulk operation).
|
||||||
|
|
||||||
|
This is the refactored RUNUPDATE command logic.
|
||||||
|
Iterates all items in LEGO category and enriches each one.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
logger: Optional logger instance for debug output
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if logger:
|
||||||
|
logger.info("Starting bulk enrichment for LEGO category")
|
||||||
|
else:
|
||||||
|
print(f"[@DEBUG] Starting bulk enrichment for LEGO category {LEGO_CAT_ID}")
|
||||||
|
|
||||||
|
# Get all items in LEGO category
|
||||||
|
items = await asquare.acat2id(LEGO_CAT_ID)
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger.info(f"Found {len(items)} items in LEGO category")
|
||||||
|
else:
|
||||||
|
print(f"[@DEBUG] Found {len(items)} items in LEGO category")
|
||||||
|
|
||||||
|
# Enrich each item
|
||||||
|
successful = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
item_id = item["id"]
|
||||||
|
result = await enrich_single_item(item_id, logger=logger)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
successful += 1
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger.info(
|
||||||
|
f"Bulk enrichment complete: {successful} successful, {failed} failed"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"[@DEBUG] Bulk enrichment complete: {successful} successful, {failed} failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if logger:
|
||||||
|
logger.error(f"Error during bulk enrichment: {e}")
|
||||||
|
else:
|
||||||
|
print(f"[@ERROR] Error during bulk enrichment: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
+68
-3
@@ -12,6 +12,8 @@ from square.utils.webhooks_helper import verify_signature
|
|||||||
# Configuration
|
# Configuration
|
||||||
SQUARE_WEBHOOK_SIGNATURE_KEY = sq_env.SQUARE_WEBHOOK_SIGNATURE_KEY
|
SQUARE_WEBHOOK_SIGNATURE_KEY = sq_env.SQUARE_WEBHOOK_SIGNATURE_KEY
|
||||||
SQUARE_WEBHOOK_URL = sq_env.SQUARE_WEBHOOK_URL
|
SQUARE_WEBHOOK_URL = sq_env.SQUARE_WEBHOOK_URL
|
||||||
|
SQUARE_LEGO_CAT_ID = sq_env.LEGO_CAT_ID
|
||||||
|
|
||||||
|
|
||||||
# Debug: Print configuration (redact key)
|
# Debug: Print configuration (redact key)
|
||||||
if SQUARE_WEBHOOK_SIGNATURE_KEY:
|
if SQUARE_WEBHOOK_SIGNATURE_KEY:
|
||||||
@@ -233,17 +235,77 @@ async def process_webhook(
|
|||||||
inventory_counts = (
|
inventory_counts = (
|
||||||
data.get("data", {}).get("object", {}).get("inventory_counts", [])
|
data.get("data", {}).get("object", {}).get("inventory_counts", [])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
from src import asquare, glue
|
||||||
|
|
||||||
for count in inventory_counts:
|
for count in inventory_counts:
|
||||||
catalog_id = count.get("catalog_object_id")
|
variation_id = count.get("catalog_object_id")
|
||||||
quantity = count.get("quantity")
|
quantity = count.get("quantity")
|
||||||
state = count.get("state")
|
state = count.get("state")
|
||||||
print(
|
print(
|
||||||
f"[PROCESS] Catalog: {catalog_id}, Qty: {quantity}, State: {state}"
|
f"[PROCESS] Catalog: {variation_id}, Qty: {quantity}, State: {state}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get parent item from variation ID
|
||||||
|
item = await asquare.get_item_by_variation_id(variation_id)
|
||||||
|
item_id = item.get("id")
|
||||||
|
item_name = item.get("name", "")
|
||||||
|
category_id = item.get("category_id")
|
||||||
|
|
||||||
|
print(f"[PROCESS] Item: {item_id} - {item_name}")
|
||||||
|
print(f"[PROCESS] Category: {category_id}")
|
||||||
|
|
||||||
|
# Only process LEGO category items
|
||||||
|
if category_id == SQUARE_LEGO_CAT_ID:
|
||||||
|
print(
|
||||||
|
f"[PROCESS] ✓ LEGO item detected - triggering enrichment"
|
||||||
|
)
|
||||||
|
# Trigger enrichment in background
|
||||||
|
asyncio.create_task(glue.enrich_single_item(item_id))
|
||||||
|
else:
|
||||||
|
print(f"[PROCESS] ✗ Not a LEGO item - skipping enrichment")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Failed to process variation {variation_id}: {e}")
|
||||||
|
|
||||||
elif webhook_type == "catalog.version.updated":
|
elif webhook_type == "catalog.version.updated":
|
||||||
print(f"[PROCESS] Processing catalog version update...")
|
print(f"[PROCESS] Processing catalog version update...")
|
||||||
# Add your catalog processing logic here
|
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
from src import asquare, glue
|
||||||
|
|
||||||
|
catalog_object = data.get("data", {}).get("object", {})
|
||||||
|
object_type = catalog_object.get("type")
|
||||||
|
|
||||||
|
if object_type == "ITEM":
|
||||||
|
item_id = catalog_object.get("id")
|
||||||
|
print(f"[PROCESS] Item updated: {item_id}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get item details to check category
|
||||||
|
item = await asquare.get_item_by_id(item_id)
|
||||||
|
item_name = item.get("name", "")
|
||||||
|
category_id = item.get("category_id")
|
||||||
|
|
||||||
|
print(f"[PROCESS] Item: {item_id} - {item_name}")
|
||||||
|
print(f"[PROCESS] Category: {category_id}")
|
||||||
|
|
||||||
|
# Only process LEGO category items
|
||||||
|
if category_id == SQUARE_LEGO_CAT_ID:
|
||||||
|
print(
|
||||||
|
f"[PROCESS] ✓ LEGO item detected - triggering enrichment"
|
||||||
|
)
|
||||||
|
# Trigger enrichment in background
|
||||||
|
asyncio.create_task(glue.enrich_single_item(item_id))
|
||||||
|
else:
|
||||||
|
print(f"[PROCESS] ✗ Not a LEGO item - skipping enrichment")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Failed to process item {item_id}: {e}")
|
||||||
|
else:
|
||||||
|
print(f"[PROCESS] Object type '{object_type}' - skipping")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"[PROCESS] Webhook type '{webhook_type}' - no handler configured")
|
print(f"[PROCESS] Webhook type '{webhook_type}' - no handler configured")
|
||||||
@@ -252,6 +314,9 @@ async def process_webhook(
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] Failed to process webhook {event_id}: {e}")
|
print(f"[ERROR] Failed to process webhook {event_id}: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in new issue
Block a user