pyaws-s3 1.0.24__tar.gz → 1.0.28__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: pyaws-s3
3
- Version: 1.0.24
3
+ Version: 1.0.28
4
4
  Summary: A Python package for AWS S3 utilities
5
5
  Author-email: Giuseppe Zileni <giuseppe.zileni@gmail.com>
6
6
  Keywords: aws,s3,utilities
@@ -42,6 +42,25 @@ class S3Client:
42
42
  self.region_name = kwargs.get("region_name", os.getenv("AWS_REGION"))
43
43
  self.bucket_name = kwargs.get("bucket_name", os.getenv("AWS_BUCKET_NAME"))
44
44
 
45
+ # crea una funzione per verifica la presenza di un file nel bucket
46
+ def file_exists(self, object_name: str) -> bool:
47
+ """
48
+ Check if a file exists in the S3 bucket.
49
+
50
+ Args:
51
+ object_name (str): The name of the S3 object to check.
52
+
53
+ Returns:
54
+ bool: True if the file exists, False otherwise.
55
+ """
56
+ s3_client = self._get_s3_client()
57
+ try:
58
+ s3_client.head_object(Bucket=self.bucket_name, Key=object_name)
59
+ return True
60
+ except Exception as e:
61
+ logger.error(f"Error checking file existence: {str(e)}")
62
+ return False
63
+
45
64
  def _bytes_from_figure(self, f: Figure, **kwargs) -> bytes:
46
65
  """
47
66
  Convert a Plotly Figure to a PNG image as bytes.
@@ -173,7 +192,7 @@ class S3Client:
173
192
 
174
193
  return temp_url
175
194
 
176
- def upload_bytes(self, *args, **kwargs: Any):
195
+ def upload_bytes(self, *args, **kwargs: Any) -> tuple[str, bool]:
177
196
  """
178
197
  Upload a Plotly Figure as a PNG image to an S3 bucket and generate a pre-signed URL.
179
198
 
@@ -181,6 +200,8 @@ class S3Client:
181
200
  bytes_data (bytes): The bytes data of the image to upload.
182
201
  object_name (str): The name of the S3 object.
183
202
  format_file (str): Format of the image. Defaults to 'pdf' ["png", "jpeg", "svg", "html", "pdf"].
203
+ overwrite (bool): If True, overwrite the existing file in S3. Defaults to False.
204
+ presigned_url (bool): If True, generate a pre-signed URL for the uploaded image. Defaults to False.
184
205
  Raises:
185
206
  Exception: If there is an error uploading the image.
186
207
 
@@ -198,6 +219,9 @@ class S3Client:
198
219
  else:
199
220
  bytes_data = kwargs.get("bytes_data", None)
200
221
  object_name = kwargs.get("object_name", None)
222
+
223
+ overwrite = kwargs.get("overwrite", False)
224
+ presigned_url = kwargs.get("presigned_url", False)
201
225
 
202
226
  if bytes_data is None:
203
227
  raise Exception("Figure is None")
@@ -208,6 +232,16 @@ class S3Client:
208
232
  format_file : FormatFile = kwargs.get("format_file", "pdf")
209
233
  mimetypes = "application/pdf"
210
234
 
235
+ # Get S3 client and resource
236
+ s3_client = self._get_s3_client()
237
+ s3_resource = self._get_s3_resource()
238
+
239
+ if not overwrite and self.file_exists(object_name):
240
+ print(f"File {object_name} already exists in the bucket {self.bucket_name}. Use overwrite=True to overwrite it.")
241
+ if presigned_url:
242
+ return self._create_url(s3_client, self.bucket_name, object_name), False
243
+ return object_name, False
244
+
211
245
  if format_file not in ["png", "jpeg", "svg", "html", "pdf"]:
212
246
  raise Exception("Invalid format_file provided. Supported formats are: png, jpeg, svg, html, pdf")
213
247
  if format_file == "png":
@@ -223,15 +257,13 @@ class S3Client:
223
257
  else:
224
258
  raise Exception("Invalid MIME type provided")
225
259
 
226
- s3_resource = self._get_s3_resource()
227
-
228
260
  s3_resource.Bucket(self.bucket_name).Object(object_name).put(Body=bytes_data, ContentType=mimetypes)
229
-
261
+ return self._create_url(s3_client, self.bucket_name, object_name), True
230
262
  except Exception as e:
231
263
  logger.error(f"Error uploading image: {str(e)}")
232
264
  raise Exception(f"Error uploading image: {str(e)}")
233
265
 
234
- def upload_image(self, *args, **kwargs: Any) -> str:
266
+ def upload_image(self, *args, **kwargs: Any) -> tuple[str, bool]:
235
267
  """
236
268
  Upload a Plotly Figure as a PNG image to an S3 bucket and generate a pre-signed URL.
237
269
 
@@ -239,6 +271,11 @@ class S3Client:
239
271
  fig (Figure): The Plotly Figure object to upload.
240
272
  bucket_name (str): The name of the S3 bucket.
241
273
  object_name (str): The name of the S3 object.
274
+ format_file (str): Format of the image. Defaults to 'png' ["png", "jpeg", "svg", "html"].
275
+ overwrite (bool): If True, overwrite the existing file in S3. Defaults to False.
276
+ presigned_url (bool): If True, generate a pre-signed URL for the uploaded image. Defaults to False.
277
+ Raises:
278
+ Exception: If there is an error uploading the image.
242
279
 
243
280
  Keyword Args:
244
281
  format_file (str): Format of the image. Defaults to 'png'.
@@ -257,6 +294,13 @@ class S3Client:
257
294
  else:
258
295
  fig = kwargs.get("fig", None)
259
296
  object_name = kwargs.get("object_name", None)
297
+
298
+ overwrite = kwargs.get("overwrite", False)
299
+ presigned_url = kwargs.get("presigned_url", False)
300
+
301
+ # Get S3 client and resource
302
+ s3_client = self._get_s3_client()
303
+ s3_resource = self._get_s3_resource()
260
304
 
261
305
  if fig is None:
262
306
  raise Exception("Figure is None")
@@ -267,6 +311,12 @@ class S3Client:
267
311
  format_file : FormatFile = kwargs.get("format_file", "svg")
268
312
  mimetypes = "image/svg+xml"
269
313
 
314
+ if not overwrite and self.file_exists(object_name):
315
+ print(f"File {object_name} already exists in the bucket {self.bucket_name}. Use overwrite=True to overwrite it.")
316
+ if presigned_url:
317
+ return self._create_url(s3_client, self.bucket_name, object_name), False
318
+ return object_name, False
319
+
270
320
  if format_file not in ["png", "jpeg", "svg", "html"]:
271
321
  raise Exception("Invalid format_file provided. Supported formats are: png, jpeg, svg, html")
272
322
  if format_file == "png":
@@ -280,10 +330,6 @@ class S3Client:
280
330
  else:
281
331
  raise Exception("Invalid MIME type provided")
282
332
 
283
- # Get S3 client and resource
284
- s3_client = self._get_s3_client()
285
- s3_resource = self._get_s3_resource()
286
-
287
333
  if format_file == "html":
288
334
  # Convert the figure to SVG
289
335
  file_text = self._html_from_figure(fig)
@@ -296,13 +342,13 @@ class S3Client:
296
342
  s3_resource.Bucket(self.bucket_name).Object(object_name).put(Body=file_buffer, ContentType=mimetypes)
297
343
 
298
344
  # Generate and return a pre-signed URL for the uploaded image
299
- return self._create_url(s3_client, self.bucket_name, object_name)
345
+ return self._create_url(s3_client, self.bucket_name, object_name), True
300
346
 
301
347
  except Exception as e:
302
348
  logger.error(f"Error uploading image: {str(e)}")
303
349
  raise Exception(f"Error uploading image: {str(e)}")
304
350
 
305
- def upload_from_dataframe(self, *args : Any, **kwargs: Any) -> str:
351
+ def upload_from_dataframe(self, *args : Any, **kwargs: Any) -> tuple[str, bool]:
306
352
  """
307
353
  Upload a DataFrame as an Excel file to an S3 bucket and generate a pre-signed URL.
308
354
 
@@ -311,6 +357,10 @@ class S3Client:
311
357
  **kwargs (Any): Additional keyword arguments for AWS credentials, bucket name, and object name.
312
358
  Keyword Args:
313
359
  format_file (str): Format of the file. Defaults to 'xlsx'.
360
+ overwrite (bool): If True, overwrite the existing file in S3. Defaults to False.
361
+ presigned_url (bool): If True, generate a pre-signed URL for the uploaded file. Defaults to False.
362
+ Raises:
363
+ Exception: If there is an error uploading the file.
314
364
 
315
365
  Returns:
316
366
  str: Pre-signed URL for the uploaded file.
@@ -328,6 +378,9 @@ class S3Client:
328
378
  # Get the DataFrame and object name from the keyword arguments
329
379
  df = kwargs.get("df", None)
330
380
  object_name = kwargs.get("object_name", None)
381
+
382
+ overwrite = kwargs.get("overwrite", False)
383
+ presigned_url = kwargs.get("presigned_url", False)
331
384
 
332
385
  if df is None:
333
386
  raise Exception("Figure is None")
@@ -349,8 +402,15 @@ class S3Client:
349
402
  else:
350
403
  raise Exception("Invalid MIME type provided")
351
404
 
405
+ # Get S3 client and resource
352
406
  s3_client = self._get_s3_client()
353
407
  s3_resource = self._get_s3_resource()
408
+
409
+ if not overwrite and self.file_exists(object_name):
410
+ print(f"File {object_name} already exists in the bucket {self.bucket_name}. Use overwrite=True to overwrite it.")
411
+ if presigned_url:
412
+ return self._create_url(s3_client, self.bucket_name, object_name), False
413
+ return object_name, False
354
414
 
355
415
  # Create a file buffer
356
416
  ext: str = ""
@@ -381,46 +441,74 @@ class S3Client:
381
441
 
382
442
  logger.info(f"Uploaded file to S3: {object_name}")
383
443
 
384
- return self._create_url(s3_client, self.bucket_name, object_name)
444
+ return self._create_url(s3_client, self.bucket_name, object_name), True
385
445
  except Exception as e:
386
446
  logger.error(f"Error uploading file: {str(e)}")
387
447
  raise Exception(f"Error uploading file: {str(e)}")
388
448
 
389
- async def delete_all(self, filter : str | None = None) -> None:
449
+ async def delete_all(self, **kwargs) -> None:
390
450
  """
391
451
  Delete all files from an S3 bucket.
392
452
 
393
453
  Args:
394
454
  filter (str | None): Optional filter to delete specific files. If None, all files will be deleted.
455
+ prefix (str | None): Optional prefix to filter files by their key. If None, all files will be deleted.
456
+
395
457
  Raises:
396
458
  Exception: If there is an error deleting the files.
397
459
  """
398
460
  try:
399
461
  s3_client = self._get_s3_client()
462
+
463
+ prefix = kwargs.get("prefix", None)
464
+ filter = kwargs.get("filter", None)
465
+
466
+ if prefix is not None and not prefix.endswith('/'):
467
+ prefix += '/'
400
468
 
401
469
  # List all objects in the bucket
402
- objects = s3_client.list_objects_v2(Bucket=self.bucket_name)
470
+ objects = None
471
+ if prefix is None:
472
+ # List all objects in the bucket
473
+ objects = s3_client.list_objects_v2(Bucket=self.bucket_name)
474
+ else:
475
+ # List objects with the specified prefix
476
+ logger.info(f"Listing objects with prefix: {prefix}")
477
+ objects = s3_client.list_objects_v2(Bucket=self.bucket_name, Prefix=prefix)
403
478
 
404
479
  # Check if the bucket contains any objects
405
480
  if 'Contents' in objects:
406
481
  for obj in objects['Contents']:
407
- if filter in obj['Key']:
408
- # Delete each object
482
+ if filter is not None:
483
+ if filter in obj['Key']:
484
+ # Delete each object
485
+ s3_client.delete_object(Bucket=self.bucket_name, Key=obj['Key'])
486
+ print(f"Deleted {obj['Key']}")
487
+ else:
488
+ logger.info(f"Skipping {obj['Key']} as it does not match the filter: {filter}")
409
489
  s3_client.delete_object(Bucket=self.bucket_name, Key=obj['Key'])
410
490
  print(f"Deleted {obj['Key']}")
491
+ else:
492
+ logger.info("No objects found in the bucket.")
493
+
494
+ print("All files deleted successfully.")
411
495
  except Exception as e:
412
496
  logger.error(f"Error deleting files: {str(e)}")
413
497
  raise Exception(f"Error deleting files: {str(e)}")
414
498
 
415
- def upload_to_pdf(self, *args: Any, **kwargs: Any) -> str:
499
+ def upload_to_pdf(self, *args: Any, **kwargs: Any) -> tuple[str, bool]:
416
500
  """
417
501
  Export the given text as a PDF and upload it to the S3 bucket.
418
502
 
419
503
  Args:
420
504
  text (str): The text to write in the PDF.
421
505
  object_name (str): The name of the S3 object.
506
+ presigned_url (bool): If True, generate a pre-signed URL for the uploaded PDF. Defaults to False.
507
+ overwrite (bool): If True, overwrite the existing file in S3. Defaults to False
508
+
422
509
  Raises:
423
510
  Exception: If there is an error exporting the PDF.
511
+
424
512
  Returns:
425
513
  str: Pre-signed URL for the uploaded PDF.
426
514
  """
@@ -431,6 +519,9 @@ class S3Client:
431
519
  else:
432
520
  text = kwargs.get("text", None)
433
521
  object_name = kwargs.get("object_name", None)
522
+
523
+ overwrite = kwargs.get("overwrite", False)
524
+ presigned_url = kwargs.get("presigned_url", False)
434
525
 
435
526
  if text is None:
436
527
  raise Exception("Text is None")
@@ -439,8 +530,15 @@ class S3Client:
439
530
  raise Exception("Object name is None")
440
531
 
441
532
  mimetypes = "application/pdf"
533
+ # Get S3 client and resource
442
534
  s3_client = self._get_s3_client()
443
535
  s3_resource = self._get_s3_resource()
536
+
537
+ if not overwrite and self.file_exists(object_name):
538
+ print(f"File {object_name} already exists in the bucket {self.bucket_name}. Use overwrite=True to overwrite it.")
539
+ if presigned_url:
540
+ return self._create_url(s3_client, self.bucket_name, object_name), False
541
+ return object_name, False
444
542
 
445
543
  # Crea il PDF in memoria
446
544
  pdf_buffer = BytesIO()
@@ -449,29 +547,47 @@ class S3Client:
449
547
  c.setFont("Helvetica", 10)
450
548
  x_margin = 20 * mm
451
549
  y = height - 20 * mm
550
+ max_width = width - 2 * x_margin
551
+
552
+ def split_line(line, font_name, font_size):
553
+ # Divide la riga in più righe se supera la larghezza massima
554
+ words = line.split()
555
+ lines = []
556
+ current = ""
557
+ for word in words:
558
+ test = current + (" " if current else "") + word
559
+ if c.stringWidth(test, font_name, font_size) <= max_width:
560
+ current = test
561
+ else:
562
+ if current:
563
+ lines.append(current)
564
+ current = word
565
+ if current:
566
+ lines.append(current)
567
+ return lines
452
568
 
453
569
  for line in text.strip().split('\n'):
454
570
  line = line.strip()
455
- if y < 20 * mm:
456
- c.showPage()
457
- c.setFont("Helvetica", 10)
458
- y = height - 20 * mm
459
-
460
571
  # Markdown-style header detection
461
572
  if line.startswith("### "):
462
- c.setFont("Helvetica-Bold", 11)
573
+ font_name, font_size = "Helvetica-Bold", 11
463
574
  line = line[4:]
464
575
  elif line.startswith("## "):
465
- c.setFont("Helvetica-Bold", 12)
576
+ font_name, font_size = "Helvetica-Bold", 12
466
577
  line = line[3:]
467
578
  elif line.startswith("# "):
468
- c.setFont("Helvetica-Bold", 14)
579
+ font_name, font_size = "Helvetica-Bold", 14
469
580
  line = line[2:]
470
581
  else:
471
- c.setFont("Helvetica", 10)
582
+ font_name, font_size = "Helvetica", 10
472
583
 
473
- c.drawString(x_margin, y, line)
474
- y -= 12
584
+ for subline in split_line(line, font_name, font_size):
585
+ if y < 20 * mm + font_size:
586
+ c.showPage()
587
+ y = height - 20 * mm
588
+ c.setFont(font_name, font_size)
589
+ c.drawString(x_margin, y, subline)
590
+ y -= font_size + 2 # Spazio tra le righe
475
591
 
476
592
  c.save()
477
593
  pdf_buffer.seek(0)
@@ -481,7 +597,7 @@ class S3Client:
481
597
  Body=pdf_buffer,
482
598
  ContentType=mimetypes
483
599
  )
484
- return self._create_url(s3_client, self.bucket_name, object_name)
600
+ return self._create_url(s3_client, self.bucket_name, object_name), True
485
601
 
486
602
  except Exception as e:
487
603
  logger.error(f"Error exporting PDF: {str(e)}")
@@ -527,13 +643,14 @@ class S3Client:
527
643
  except Exception as e:
528
644
  logger.error(f"Error downloading file: {str(e)}")
529
645
  raise Exception(f"Error downloading file: {str(e)}")
530
-
646
+
531
647
  def list_files(self, *args: Any, **kwargs : Any) -> list[str]:
532
648
  """
533
649
  List all files in the S3 bucket.
534
650
 
535
651
  Args:
536
652
  filter (str | None): Optional filter to list specific files. If None, all files will be listed.
653
+ prefix (str | None): Optional prefix to filter files by their key. If None, all files will be listed.
537
654
  Raises:
538
655
  Exception: If there is an error listing the files.
539
656
  Returns:
@@ -547,9 +664,14 @@ class S3Client:
547
664
  if prefix is None:
548
665
  raise Exception("Prefix is None")
549
666
 
667
+ if prefix is not None and not prefix.endswith('/'):
668
+ prefix += '/'
669
+
550
670
  filter = kwargs.get("filter", None)
551
671
 
552
672
  s3_client = self._get_s3_client()
673
+
674
+ logger.info(f"Listing objects with prefix: {prefix}")
553
675
  objects = s3_client.list_objects_v2(Bucket=self.bucket_name, Prefix=prefix)
554
676
 
555
677
  # Check if the bucket contains any objects
@@ -557,7 +679,6 @@ class S3Client:
557
679
  if 'Contents' in objects:
558
680
  for obj in objects['Contents']:
559
681
  if obj['Key']:
560
- # Log the object key
561
682
  if filter is not None:
562
683
  if filter in obj['Key']:
563
684
  logger.info(f"Object: {obj['Key']}")
@@ -569,12 +690,14 @@ class S3Client:
569
690
  logger.error(f"Error listing files: {str(e)}")
570
691
  raise Exception(f"Error listing files: {str(e)}")
571
692
 
572
- def delete_file(self, *args : Any) -> None:
693
+ def delete_file(self, *args : Any, **kwargs) -> None:
573
694
  """
574
695
  Delete a file from the S3 bucket.
575
696
 
576
697
  Args:
577
698
  object_name (str): The name of the S3 object to delete.
699
+ prefix (str | None): Optional prefix to filter files by their key. If None, all files will be deleted.
700
+ filter (str | None): Optional filter to delete specific files. If None, the specified
578
701
  Raises:
579
702
  Exception: If there is an error deleting the file.
580
703
  """
@@ -583,8 +706,35 @@ class S3Client:
583
706
  if object_name is None:
584
707
  raise Exception("Object name is None")
585
708
 
709
+ prefix = kwargs.get("prefix", None)
710
+ if prefix is not None and not prefix.endswith('/'):
711
+ prefix += '/'
712
+
713
+ filter = kwargs.get("filter", None)
714
+
586
715
  s3_client = self._get_s3_client()
587
- s3_client.delete_object(Bucket=self.bucket_name, Key=object_name)
716
+
717
+ objects = None
718
+ if prefix is None:
719
+ # List all objects in the bucket
720
+ objects = s3_client.list_objects_v2(Bucket=self.bucket_name)
721
+ else:
722
+ # List objects with the specified prefix
723
+ logger.info(f"Listing objects with prefix: {prefix}")
724
+ objects = s3_client.list_objects_v2(Bucket=self.bucket_name, Prefix=prefix)
725
+
726
+ # Check if the bucket contains any objects
727
+ if 'Contents' in objects:
728
+ for obj in objects['Contents']:
729
+ if filter is not None:
730
+ if filter in obj['Key']:
731
+ # Delete each object
732
+ s3_client.delete_object(Bucket=self.bucket_name, Key=obj['Key'])
733
+ print(f"Deleted {obj['Key']}")
734
+ else:
735
+ # If no objects match the filter, delete the specified object
736
+ s3_client.delete_object(Bucket=self.bucket_name, Key=object_name)
737
+ print(f"Deleted {object_name}")
588
738
  except Exception as e:
589
739
  logger.error(f"Error deleting file: {str(e)}")
590
740
  raise Exception(f"Error deleting file: {str(e)}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyaws-s3
3
- Version: 1.0.24
3
+ Version: 1.0.28
4
4
  Summary: A Python package for AWS S3 utilities
5
5
  Author-email: Giuseppe Zileni <giuseppe.zileni@gmail.com>
6
6
  Keywords: aws,s3,utilities
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyaws-s3"
7
- version = "1.0.24"
7
+ version = "1.0.28"
8
8
  description = "A Python package for AWS S3 utilities"
9
9
  authors = [
10
10
  { name="Giuseppe Zileni", email="giuseppe.zileni@gmail.com" }
File without changes
File without changes
File without changes