containerio 2.2.1__tar.gz → 2.3.1__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.3
2
2
  Name: containerio
3
- Version: 2.2.1
3
+ Version: 2.3.1
4
4
  Summary: Azure Blob Storage handler with unified local/cloud interface
5
5
  Author: Alliance SwissPass
6
6
  Requires-Dist: polars>=1.30.0
@@ -623,11 +623,10 @@ The CI pipeline will automatically:
623
623
  The repository includes a Crow CI pipeline that automatically builds, tests, and publishes the package when a git tag is pushed:
624
624
 
625
625
  1. Merge all branches you want to have part of a new release into main
626
- 1. Check tag for upcoming semantic release version with `git sv rn`
626
+ 1. `git sv rn`: check tag for upcoming semantic release version and associated changelog.
627
627
  1. If not already added, add your private key (whose public key is associated with your Codefloe Git user) to the SSH agent.
628
628
  `ssh-add ~/.ssh/<your-private-key>`
629
- 1. Generate tag with version based on git commit messages with `git sv tag`
630
- 1. Create and push a git tag (e.g., `git push --tags`)
629
+ 1. `git sv tag`: generate tag with version and push it to remote in one go, based on git commit parsing.
631
630
 
632
631
  The pipeline will:
633
632
  - Run linting and tests
@@ -609,11 +609,10 @@ The CI pipeline will automatically:
609
609
  The repository includes a Crow CI pipeline that automatically builds, tests, and publishes the package when a git tag is pushed:
610
610
 
611
611
  1. Merge all branches you want to have part of a new release into main
612
- 1. Check tag for upcoming semantic release version with `git sv rn`
612
+ 1. `git sv rn`: check tag for upcoming semantic release version and associated changelog.
613
613
  1. If not already added, add your private key (whose public key is associated with your Codefloe Git user) to the SSH agent.
614
614
  `ssh-add ~/.ssh/<your-private-key>`
615
- 1. Generate tag with version based on git commit messages with `git sv tag`
616
- 1. Create and push a git tag (e.g., `git push --tags`)
615
+ 1. `git sv tag`: generate tag with version and push it to remote in one go, based on git commit parsing.
617
616
 
618
617
  The pipeline will:
619
618
  - Run linting and tests
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "containerio"
3
- version = "2.2.1"
3
+ version = "2.3.1"
4
4
  description = "Azure Blob Storage handler with unified local/cloud interface"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -377,7 +377,24 @@ class StorageHandler:
377
377
  )
378
378
  }
379
379
 
380
- def _read_blob_as_bytes(self, blob_name: str) -> bytes:
380
+ def read_blob_as_bytes(self, blob_name: str) -> bytes:
381
+ """Read a blob's raw bytes from local or Azure storage.
382
+
383
+ Args:
384
+ blob_name (str): The path/name of the blob to read.
385
+
386
+ Returns:
387
+ bytes: The blob's raw contents.
388
+
389
+ Raises:
390
+ FileNotFoundError: If the blob does not exist.
391
+ """
392
+ if self.local:
393
+ try:
394
+ return (self.base_path / blob_name).read_bytes()
395
+ except FileNotFoundError as error:
396
+ raise FileNotFoundError(f"Blob not found: {blob_name}") from error
397
+
381
398
  try:
382
399
  return self._get_blob_client(blob_name=blob_name).download_blob().readall()
383
400
  except ResourceNotFoundError as error:
@@ -686,12 +703,8 @@ class StorageHandler:
686
703
  pl.DataFrame: The content of the CSV file as a Polars
687
704
  DataFrame.
688
705
  """
689
- if self.local:
690
- file_path = self.base_path / blob_name
691
- return pl.read_csv(source=file_path, **kwargs)
692
- else:
693
- data = self._read_blob_as_bytes(blob_name)
694
- return pl.read_csv(source=BytesIO(data), **kwargs)
706
+ data = self.read_blob_as_bytes(blob_name=blob_name)
707
+ return pl.read_csv(source=BytesIO(data), **kwargs)
695
708
 
696
709
  def scan_csv_blob(self, blob_name: str, **kwargs: Dict) -> pl.LazyFrame:
697
710
  """Scans a CSV file or multiple files from storage.
@@ -731,12 +744,8 @@ class StorageHandler:
731
744
  pl.DataFrame: The content of the Excel file as a Polars
732
745
  DataFrame.
733
746
  """
734
- if self.local:
735
- file_path = self.base_path / blob_name
736
- df = pl.read_excel(source=file_path, **kwargs)
737
- else:
738
- data = self._read_blob_as_bytes(blob_name)
739
- df = pl.read_excel(source=BytesIO(data), **kwargs)
747
+ data = self.read_blob_as_bytes(blob_name=blob_name)
748
+ df = pl.read_excel(source=BytesIO(data), **kwargs)
740
749
 
741
750
  if not isinstance(df, pl.DataFrame):
742
751
  raise TypeError(
@@ -757,16 +766,10 @@ class StorageHandler:
757
766
  Tuple[pl.DataFrame, Dict]: A tuple containing the DataFrame
758
767
  and the Parquet file's metadata.
759
768
  """
760
- if self.local:
761
- file_path = self.base_path / blob_name
762
- df = pl.read_parquet(source=file_path)
763
- metadata = pl.read_parquet_metadata(source=file_path)
764
- return df, metadata
765
- else:
766
- data = self._read_blob_as_bytes(blob_name)
767
- df = pl.read_parquet(source=BytesIO(data))
768
- metadata = pl.read_parquet_metadata(source=BytesIO(data))
769
- return df, metadata
769
+ data = self.read_blob_as_bytes(blob_name=blob_name)
770
+ df = pl.read_parquet(source=BytesIO(data))
771
+ metadata = pl.read_parquet_metadata(source=BytesIO(data))
772
+ return df, metadata
770
773
 
771
774
  def scan_parquet_blob(
772
775
  self, blob_name: str, **kwargs: Dict
@@ -845,15 +848,43 @@ class StorageHandler:
845
848
 
846
849
  return download_path
847
850
 
848
- def _write_blob_as_bytes(self, blob_name: str, data: bytes) -> Dict[str, Any]:
851
+ def write_blob_as_bytes(
852
+ self, blob_name: str, data: bytes
853
+ ) -> Optional[Dict[str, Any]]:
854
+ """Write raw bytes to a blob in local or Azure storage.
855
+
856
+ Existing blobs are overwritten. For local storage, parent
857
+ directories are created as needed.
858
+
859
+ Args:
860
+ blob_name (str): The path/name of the blob to write.
861
+ data (bytes): The raw bytes to write.
862
+
863
+ Returns:
864
+ Optional[Dict[str, Any]]: The Azure upload response, or
865
+ `None` for local storage.
866
+
867
+ Raises:
868
+ ValueError: If `blob_name` is empty or not a string.
869
+ TypeError: If `data` is not bytes.
870
+ """
849
871
  if not isinstance(blob_name, str) or not blob_name:
850
872
  raise ValueError("Blob name must be str and not empty")
851
873
  if not isinstance(data, bytes):
852
874
  raise TypeError("Data must be bytes")
875
+
876
+ if self.local:
877
+ file_path = self.base_path / blob_name
878
+ file_path.parent.mkdir(parents=True, exist_ok=True)
879
+ file_path.write_bytes(data)
880
+ return None
881
+
853
882
  # Overwrite if the blob exists
854
883
  try:
855
884
  blob_client = self._get_blob_client(blob_name=blob_name)
856
- return blob_client.upload_blob(data, blob_type="BlockBlob", overwrite=True)
885
+ return blob_client.upload_blob(
886
+ data=data, blob_type="BlockBlob", overwrite=True
887
+ )
857
888
  except Exception as e:
858
889
  raise RuntimeError(f"Failed to write blob '{blob_name}': {e}") from e
859
890
 
@@ -893,17 +924,9 @@ class StorageHandler:
893
924
  Returns:
894
925
  None
895
926
  """
896
- if self.local:
897
- file_path = self.base_path / blob_name
898
- file_path.parent.mkdir(parents=True, exist_ok=True)
899
- df.write_csv(file=file_path, **kwargs)
900
- else:
901
- # Write the DataFrame to an in-memory stream
902
- buffer = BytesIO()
903
- df.write_csv(file=buffer, **kwargs)
904
- buffer.seek(0)
905
- # Upload the bytes to Azure Blob Storage
906
- self._write_blob_as_bytes(blob_name, buffer.getvalue())
927
+ buffer = BytesIO()
928
+ df.write_csv(file=buffer, **kwargs)
929
+ self.write_blob_as_bytes(blob_name=blob_name, data=buffer.getvalue())
907
930
 
908
931
  def write_excel_blob(
909
932
  self, blob_name: str, df: pl.DataFrame, **kwargs: Dict
@@ -919,17 +942,9 @@ class StorageHandler:
919
942
  Returns:
920
943
  None
921
944
  """
922
- if self.local:
923
- file_path = self.base_path / blob_name
924
- file_path.parent.mkdir(parents=True, exist_ok=True)
925
- df.write_excel(workbook=file_path, **kwargs)
926
- else:
927
- # Write the DataFrame to an in-memory stream
928
- buffer = BytesIO()
929
- df.write_excel(workbook=buffer, **kwargs)
930
- buffer.seek(0)
931
- # Upload the bytes to Azure Blob Storage
932
- self._write_blob_as_bytes(blob_name, buffer.getvalue())
945
+ buffer = BytesIO()
946
+ df.write_excel(workbook=buffer, **kwargs)
947
+ self.write_blob_as_bytes(blob_name=blob_name, data=buffer.getvalue())
933
948
 
934
949
  def write_parquet_blob(
935
950
  self, blob_name: str, df: pl.DataFrame, **kwargs: Dict
@@ -945,17 +960,9 @@ class StorageHandler:
945
960
  Returns:
946
961
  None
947
962
  """
948
- if self.local:
949
- file_path = self.base_path / blob_name
950
- file_path.parent.mkdir(parents=True, exist_ok=True)
951
- df.write_parquet(file=file_path, **kwargs)
952
- else:
953
- # Write the DataFrame to an in-memory stream
954
- buffer = BytesIO()
955
- df.write_parquet(file=buffer, **kwargs)
956
- buffer.seek(0)
957
- # Upload the bytes to Azure Blob Storage
958
- self._write_blob_as_bytes(blob_name, buffer.getvalue())
963
+ buffer = BytesIO()
964
+ df.write_parquet(file=buffer, **kwargs)
965
+ self.write_blob_as_bytes(blob_name=blob_name, data=buffer.getvalue())
959
966
 
960
967
  def sink_parquet_blob(
961
968
  self, blob_name: str, lf: pl.LazyFrame, **kwargs: Dict