11import time
22import uuid
3+ from functools import wraps
34
5+ from square .core .api_error import ApiError
46from square .core .request_options import RequestOptions
57from square .types .catalog_item import CatalogItem
68from square .types .catalog_item_variation import CatalogItemVariation
2022MAX_TIMEOUT = 120
2123
2224
25+ def retry_on_rate_limit (max_retries = 5 , base_delay = 2 ):
26+ def decorator (func ):
27+ @wraps (func )
28+ def wrapper (* args , ** kwargs ):
29+ for attempt in range (max_retries ):
30+ try :
31+ return func (* args , ** kwargs )
32+ except ApiError as e :
33+ if e .status_code == 429 and attempt < max_retries - 1 :
34+ delay = base_delay * (2 ** attempt ) # exponential backoff
35+ print (f"Rate limited. Retrying in { delay } seconds..." )
36+ time .sleep (delay )
37+ continue
38+ raise
39+ return None
40+ return wrapper
41+ return decorator
42+
43+
44+ @retry_on_rate_limit ()
2345def test_upload_catalog_image ():
2446 # Wait to kick off the first test to avoid being rate limited.
2547 time .sleep (3 )
2648
2749 client = helpers .test_client ()
2850
29- # Setup: Create a catalog object to associate the image with
30- catalog_object = helpers .create_test_catalog_item (
31- helpers .CreateCatalogItemOptions ()
32- )
33- create_catalog_resp = client .catalog .batch_upsert (
34- idempotency_key = str (uuid .uuid4 ()),
35- batches = [CatalogObjectBatch (objects = [catalog_object ])],
36- )
37-
38- objects = create_catalog_resp .objects
39- assert objects is not None
40- assert 1 == len (objects )
41- created_catalog_object = objects [0 ]
42- assert isinstance (created_catalog_object , CatalogObjectItem )
43- assert created_catalog_object .id is not None
44-
45- # Create a new catalog image
46- image_name = "Test Image " + str (uuid .uuid4 ())
47- create_catalog_image_resp = client .catalog .images .create (
48- image_file = helpers .get_test_file (),
49- request = {
50- "idempotency_key" : str (uuid .uuid4 ()),
51- "image" : {
52- "type" : "IMAGE" ,
53- "id" : helpers .new_test_square_id (),
54- "image_data" : {"name" : image_name },
51+ try :
52+ # Setup: Create a catalog object to associate the image with
53+ catalog_object = helpers .create_test_catalog_item (
54+ helpers .CreateCatalogItemOptions ()
55+ )
56+ create_catalog_resp = client .catalog .batch_upsert (
57+ idempotency_key = str (uuid .uuid4 ()),
58+ batches = [CatalogObjectBatch (objects = [catalog_object ])],
59+ request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
60+ )
61+
62+ time .sleep (2 ) # Add delay after creation
63+
64+ objects = create_catalog_resp .objects
65+ assert objects is not None
66+ assert 1 == len (objects )
67+ created_catalog_object = objects [0 ]
68+ assert isinstance (created_catalog_object , CatalogObjectItem )
69+ assert created_catalog_object .id is not None
70+
71+ # Create a new catalog image
72+ image_name = "Test Image " + str (uuid .uuid4 ())
73+ create_catalog_image_resp = client .catalog .images .create (
74+ image_file = helpers .get_test_file (),
75+ request = {
76+ "idempotency_key" : str (uuid .uuid4 ()),
77+ "image" : {
78+ "type" : "IMAGE" ,
79+ "id" : helpers .new_test_square_id (),
80+ "image_data" : {"name" : image_name },
81+ },
82+ "object_id" : created_catalog_object .id ,
5583 },
56- "object_id" : created_catalog_object .id ,
57- },
58- request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
59- )
60- image = create_catalog_image_resp .image
61- assert image is not None
62- assert isinstance (image , CatalogObjectImage )
63-
64- # Cleanup
65- client .catalog .batch_delete (
66- object_ids = [created_catalog_object .id , image .id ],
67- request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
68- )
69-
70-
84+ request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
85+ )
86+
87+ time .sleep (2 ) # Add delay after image creation
88+
89+ image = create_catalog_image_resp .image
90+ assert image is not None
91+ assert isinstance (image , CatalogObjectImage )
92+
93+ # Add retry logic for cleanup
94+ for attempt in range (MAX_RETRIES ):
95+ try :
96+ time .sleep (2 ) # Add delay before cleanup attempt
97+ # Cleanup
98+ client .catalog .batch_delete (
99+ object_ids = [created_catalog_object .id , image .id ],
100+ request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
101+ )
102+ break
103+ except ApiError as e :
104+ if e .status_code == 429 and attempt < MAX_RETRIES - 1 :
105+ delay = 2 * (2 ** attempt )
106+ print (f"Cleanup rate limited. Retrying in { delay } seconds..." )
107+ time .sleep (delay )
108+ continue
109+ raise
110+ except Exception as e :
111+ print (f"Error in test_upload_catalog_image: { str (e )} " )
112+ raise
113+
114+
115+ @retry_on_rate_limit ()
71116def test_upsert_catalog_object ():
117+ time .sleep (2 ) # Add initial delay
72118 client = helpers .test_client ()
73119
74120 coffee_variation_opts = helpers .CreateCatalogItemVariationOptions ()
@@ -106,7 +152,9 @@ def test_upsert_catalog_object():
106152 assert "Colombian Fair Trade" == item_variation_data .name
107153
108154
155+ @retry_on_rate_limit ()
109156def test_catalog_info ():
157+ time .sleep (2 ) # Add initial delay
110158 client = helpers .test_client ()
111159 response = client .catalog .search (
112160 limit = 1 , request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT )
@@ -116,14 +164,17 @@ def test_catalog_info():
116164 assert len (response .objects ) > 0
117165
118166
167+ @retry_on_rate_limit ()
119168def test_search_catalog_items ():
169+ time .sleep (2 ) # Add initial delay
120170 client = helpers .test_client ()
121171 response = client .catalog .search_items (
122172 limit = 1 , request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT )
123173 )
124174 assert response is not None
125175
126176
177+ @retry_on_rate_limit ()
127178def test_batch_upsert_catalog_objects ():
128179 # Wait to kick off this test to avoid being rate limited.
129180 time .sleep (3 )
@@ -187,8 +238,11 @@ def test_batch_upsert_catalog_objects():
187238 ]
188239 }
189240 ],
241+ request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
190242 )
191243
244+ time .sleep (2 ) # Add delay after batch upsert
245+
192246 objects = response .objects
193247 assert objects is not None
194248 assert 2 == len (objects )
@@ -220,6 +274,8 @@ def test_batch_upsert_catalog_objects():
220274 assert len (catalog_modifier_list_ids ) > 0
221275 catalog_modifier_list_id = catalog_modifier_list_ids [0 ]
222276
277+ time .sleep (2 ) # Add delay before batch get
278+
223279 response = client .catalog .batch_get (
224280 object_ids = [catalog_modifier_id , catalog_modifier_list_id , catalog_tax_id ],
225281 request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
@@ -235,15 +291,21 @@ def test_batch_upsert_catalog_objects():
235291 catalog_tax_id ,
236292 }
237293
294+ time .sleep (2 ) # Add delay before catalog item creation
295+
238296 catalog_item = helpers .create_test_catalog_item (helpers .CreateCatalogItemOptions ())
239297 catalog_response = client .catalog .object .upsert (
240- idempotency_key = str (uuid .uuid4 ()), object = catalog_item
298+ idempotency_key = str (uuid .uuid4 ()),
299+ object = catalog_item ,
300+ request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
241301 )
242302 catalog_object = catalog_response .catalog_object
243303 assert catalog_object is not None
244304 assert isinstance (catalog_object , CatalogObjectItem )
245305 catalog_object_id = catalog_object .id
246306
307+ time .sleep (2 ) # Add delay before update taxes
308+
247309 response = client .catalog .update_item_taxes (
248310 item_ids = [catalog_object_id ],
249311 taxes_to_enable = [catalog_tax_id ],
@@ -253,6 +315,8 @@ def test_batch_upsert_catalog_objects():
253315 assert response .updated_at is not None
254316 assert response .errors is None
255317
318+ time .sleep (2 ) # Add delay before update modifier lists
319+
256320 response = client .catalog .update_item_modifier_lists (
257321 item_ids = [catalog_object_id ],
258322 modifier_lists_to_enable = [catalog_modifier_list_id ],
@@ -263,18 +327,27 @@ def test_batch_upsert_catalog_objects():
263327 assert response .errors is None
264328
265329
330+ @retry_on_rate_limit ()
266331def test_delete_catalog_object ():
332+ time .sleep (2 ) # Add initial delay
267333 client = helpers .test_client ()
268334
269335 catalog_item = helpers .create_test_catalog_item (helpers .CreateCatalogItemOptions ())
270336 catalog_response = client .catalog .object .upsert (
271- idempotency_key = str (uuid .uuid4 ()), object = catalog_item
337+ idempotency_key = str (uuid .uuid4 ()),
338+ object = catalog_item ,
339+ request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
272340 )
273341 catalog_object = catalog_response .catalog_object
274342 assert catalog_object is not None
275343 assert isinstance (catalog_object , CatalogObjectItem )
276344 catalog_object_id = catalog_object .id
277345
278- response = client . catalog . object . delete ( object_id = catalog_object_id )
346+ time . sleep ( 2 ) # Add delay before delete
279347
280- assert response is not None
348+ response = client .catalog .object .delete (
349+ object_id = catalog_object_id ,
350+ request_options = RequestOptions (timeout_in_seconds = MAX_TIMEOUT ),
351+ )
352+
353+ assert response is not None
0 commit comments