esuls 0.1.14__tar.gz → 0.1.15__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: esuls
3
- Version: 0.1.14
3
+ Version: 0.1.15
4
4
  Summary: Utility library for async database operations, HTTP requests, and parallel execution
5
5
  Author-email: IperGiove <ipergiove@gmail.com>
6
6
  License: MIT
@@ -228,8 +228,7 @@ pip install -e .
228
228
 
229
229
  ```bash
230
230
  # With uv
231
- uv build
232
- twine upload dist/*
231
+ uv build && twine upload dist/*
233
232
 
234
233
  # Or with traditional tools
235
234
  pip install build twine
@@ -205,8 +205,7 @@ pip install -e .
205
205
 
206
206
  ```bash
207
207
  # With uv
208
- uv build
209
- twine upload dist/*
208
+ uv build && twine upload dist/*
210
209
 
211
210
  # Or with traditional tools
212
211
  pip install build twine
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "esuls"
7
- version = "0.1.14"
7
+ version = "0.1.15"
8
8
  description = "Utility library for async database operations, HTTP requests, and parallel execution"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
@@ -296,40 +296,51 @@ class AsyncDB(Generic[SchemaType]):
296
296
 
297
297
  saved_count = 0
298
298
 
299
- async with self._write_lock:
300
- async with self.transaction() as db:
301
- for item in items:
302
- try:
303
- if not isinstance(item, self.schema_class):
304
- if not skip_errors:
305
- raise TypeError(f"Expected {self.schema_class.__name__}, got {type(item).__name__}")
306
- continue
307
-
308
- # Extract and process data
309
- data = asdict(item)
310
- item_id = data.pop('id', None) or str(uuid.uuid4())
311
-
312
- # Ensure created_at and updated_at are set
313
- now = datetime.now()
314
- if not data.get('created_at'):
315
- data['created_at'] = now
316
- data['updated_at'] = now
317
-
318
- # Prepare SQL and values
319
- field_names = tuple(sorted(data.keys()))
320
- sql = self._generate_save_sql(field_names)
321
- values = [self._serialize_value(data[name]) for name in field_names]
322
- values.append(item_id)
323
-
324
- # Execute save
325
- await db.execute(sql, values)
326
- saved_count += 1
327
-
328
- except Exception as e:
329
- if skip_errors:
330
- logger.warning(f"Save error (skipped): {e}")
331
- continue
332
- raise
299
+ max_retries = 3
300
+ for attempt in range(max_retries):
301
+ try:
302
+ async with self._write_lock:
303
+ async with self.transaction() as db:
304
+ for item in items:
305
+ try:
306
+ if not isinstance(item, self.schema_class):
307
+ if not skip_errors:
308
+ raise TypeError(f"Expected {self.schema_class.__name__}, got {type(item).__name__}")
309
+ continue
310
+
311
+ # Extract and process data
312
+ data = asdict(item)
313
+ item_id = data.pop('id', None) or str(uuid.uuid4())
314
+
315
+ # Ensure created_at and updated_at are set
316
+ now = datetime.now()
317
+ if not data.get('created_at'):
318
+ data['created_at'] = now
319
+ data['updated_at'] = now
320
+
321
+ # Prepare SQL and values
322
+ field_names = tuple(sorted(data.keys()))
323
+ sql = self._generate_save_sql(field_names)
324
+ values = [self._serialize_value(data[name]) for name in field_names]
325
+ values.append(item_id)
326
+
327
+ # Execute save
328
+ await db.execute(sql, values)
329
+ saved_count += 1
330
+
331
+ except Exception as e:
332
+ if skip_errors:
333
+ logger.warning(f"Save error (skipped): {e}")
334
+ continue
335
+ raise
336
+ break
337
+ except Exception as e:
338
+ if "database is locked" in str(e) and attempt < max_retries - 1:
339
+ wait_time = 0.2 * (2 ** attempt)
340
+ logger.debug(f"DB locked, retry {attempt + 1}/{max_retries} in {wait_time}s")
341
+ await asyncio.sleep(wait_time)
342
+ continue
343
+ raise
333
344
 
334
345
  return saved_count
335
346
 
@@ -365,10 +376,21 @@ class AsyncDB(Generic[SchemaType]):
365
376
  values = [self._serialize_value(data[name]) for name in field_names]
366
377
  values.append(item_id)
367
378
 
368
- # Perform save with reliable transaction
369
- async with self._write_lock:
370
- async with self.transaction() as db:
371
- await db.execute(sql, values)
379
+ # Perform save with reliable transaction (retry on "database is locked")
380
+ max_retries = 3
381
+ for attempt in range(max_retries):
382
+ try:
383
+ async with self._write_lock:
384
+ async with self.transaction() as db:
385
+ await db.execute(sql, values)
386
+ break
387
+ except Exception as e:
388
+ if "database is locked" in str(e) and attempt < max_retries - 1:
389
+ wait_time = 0.2 * (2 ** attempt)
390
+ logger.debug(f"DB locked, retry {attempt + 1}/{max_retries} in {wait_time}s")
391
+ await asyncio.sleep(wait_time)
392
+ continue
393
+ raise
372
394
 
373
395
  return True
374
396
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esuls
3
- Version: 0.1.14
3
+ Version: 0.1.15
4
4
  Summary: Utility library for async database operations, HTTP requests, and parallel execution
5
5
  Author-email: IperGiove <ipergiove@gmail.com>
6
6
  License: MIT
@@ -228,8 +228,7 @@ pip install -e .
228
228
 
229
229
  ```bash
230
230
  # With uv
231
- uv build
232
- twine upload dist/*
231
+ uv build && twine upload dist/*
233
232
 
234
233
  # Or with traditional tools
235
234
  pip install build twine
File without changes
File without changes
File without changes
File without changes
File without changes