containerio 2.1.1__tar.gz → 2.2.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.1.1
3
+ Version: 2.2.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
@@ -406,6 +406,7 @@ StorageHandler(
406
406
  | `read_parquet_blob(blob_name)` | Read parquet file, returns `(DataFrame, metadata)` |
407
407
  | `read_csv_blob(blob_name, **kwargs)` | Read CSV file into DataFrame |
408
408
  | `read_excel_blob(blob_name, **kwargs)` | Read Excel file into DataFrame |
409
+ | `read_json_blob(blob_name, **kwargs)` | Read JSON file into DataFrame |
409
410
  | `scan_parquet_blob(blob_name)` | Lazy scan parquet, returns `(LazyFrame, metadata)` |
410
411
  | `scan_csv_blob(blob_name, **kwargs)` | Lazy scan CSV file |
411
412
 
@@ -416,6 +417,7 @@ StorageHandler(
416
417
  | `write_parquet_blob(blob_name, df)` | Write DataFrame to parquet |
417
418
  | `write_csv_blob(blob_name, df, **kwargs)` | Write DataFrame to CSV |
418
419
  | `write_excel_blob(blob_name, df, **kwargs)` | Write DataFrame to Excel |
420
+ | `write_json_blob(blob_name, df, **kwargs)` | Write DataFrame to JSON |
419
421
  | `sink_parquet_blob(blob_name, lf)` | Stream LazyFrame to parquet |
420
422
 
421
423
  #### Utility Methods
@@ -620,8 +622,12 @@ The CI pipeline will automatically:
620
622
 
621
623
  The repository includes a Crow CI pipeline that automatically builds, tests, and publishes the package when a git tag is pushed:
622
624
 
623
- 2. Commit and push your changes
624
- 3. Create and push a git tag (e.g., `git tag v1.0.0 && git push --tags`)
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`
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
+ `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`)
625
631
 
626
632
  The pipeline will:
627
633
  - Run linting and tests
@@ -392,6 +392,7 @@ StorageHandler(
392
392
  | `read_parquet_blob(blob_name)` | Read parquet file, returns `(DataFrame, metadata)` |
393
393
  | `read_csv_blob(blob_name, **kwargs)` | Read CSV file into DataFrame |
394
394
  | `read_excel_blob(blob_name, **kwargs)` | Read Excel file into DataFrame |
395
+ | `read_json_blob(blob_name, **kwargs)` | Read JSON file into DataFrame |
395
396
  | `scan_parquet_blob(blob_name)` | Lazy scan parquet, returns `(LazyFrame, metadata)` |
396
397
  | `scan_csv_blob(blob_name, **kwargs)` | Lazy scan CSV file |
397
398
 
@@ -402,6 +403,7 @@ StorageHandler(
402
403
  | `write_parquet_blob(blob_name, df)` | Write DataFrame to parquet |
403
404
  | `write_csv_blob(blob_name, df, **kwargs)` | Write DataFrame to CSV |
404
405
  | `write_excel_blob(blob_name, df, **kwargs)` | Write DataFrame to Excel |
406
+ | `write_json_blob(blob_name, df, **kwargs)` | Write DataFrame to JSON |
405
407
  | `sink_parquet_blob(blob_name, lf)` | Stream LazyFrame to parquet |
406
408
 
407
409
  #### Utility Methods
@@ -606,8 +608,12 @@ The CI pipeline will automatically:
606
608
 
607
609
  The repository includes a Crow CI pipeline that automatically builds, tests, and publishes the package when a git tag is pushed:
608
610
 
609
- 2. Commit and push your changes
610
- 3. Create and push a git tag (e.g., `git tag v1.0.0 && git push --tags`)
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`
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
+ `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`)
611
617
 
612
618
  The pipeline will:
613
619
  - Run linting and tests
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "containerio"
3
- version = "2.1.1"
3
+ version = "2.2.1"
4
4
  description = "Azure Blob Storage handler with unified local/cloud interface"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -655,6 +655,25 @@ class StorageHandler:
655
655
  console = Console()
656
656
  console.print(tree)
657
657
 
658
+ def read_json_blob(self, blob_name: str, **kwargs: Dict) -> pl.DataFrame:
659
+ """Reads a JSON file from storage into a Polars DataFrame.
660
+
661
+ Args:
662
+ blob_name (str): The path/name of the file to read.
663
+ **kwargs: Additional keyword arguments passed to
664
+ `polars.read_json`.
665
+
666
+ Returns:
667
+ pl.DataFrame: The content of the JSON file as a Polars
668
+ DataFrame.
669
+ """
670
+ if self.local:
671
+ file_path = self.base_path / blob_name
672
+ return pl.read_json(source=file_path, **kwargs)
673
+ else:
674
+ data = self._read_blob_as_bytes(blob_name)
675
+ return pl.read_json(source=BytesIO(data), **kwargs)
676
+
658
677
  def read_csv_blob(self, blob_name: str, **kwargs: Dict) -> pl.DataFrame:
659
678
  """Reads a CSV file from storage into a Polars DataFrame.
660
679
 
@@ -838,6 +857,30 @@ class StorageHandler:
838
857
  except Exception as e:
839
858
  raise RuntimeError(f"Failed to write blob '{blob_name}': {e}") from e
840
859
 
860
+ def write_json_blob(self, blob_name: str, df: pl.DataFrame, **kwargs: Dict) -> None:
861
+ """Writes a Polars DataFrame to a JSON file in storage.
862
+
863
+ Args:
864
+ blob_name (str): The path/name of the file to write to.
865
+ df (pl.DataFrame): The DataFrame to write.
866
+ **kwargs: Additional keyword arguments passed to
867
+ `DataFrame.write_json`.
868
+
869
+ Returns:
870
+ None
871
+ """
872
+ if self.local:
873
+ file_path = self.base_path / blob_name
874
+ file_path.parent.mkdir(parents=True, exist_ok=True)
875
+ df.write_json(file=file_path, **kwargs)
876
+ else:
877
+ # Write the DataFrame to an in-memory stream
878
+ buffer = BytesIO()
879
+ df.write_json(file=buffer, **kwargs)
880
+ buffer.seek(0)
881
+ # Upload the bytes to Azure Blob Storage
882
+ self._write_blob_as_bytes(blob_name, buffer.getvalue())
883
+
841
884
  def write_csv_blob(self, blob_name: str, df: pl.DataFrame, **kwargs: Dict) -> None:
842
885
  """Writes a Polars DataFrame to a CSV file in storage.
843
886
 
@@ -1006,18 +1049,22 @@ class StorageHandler:
1006
1049
  prefix = blob_name.rstrip("/") + "/"
1007
1050
  children = list(self.container_client.list_blobs(name_starts_with=prefix))
1008
1051
 
1009
- # Try exact blob match
1052
+ # Delete children deepest-first. On hierarchical-namespace
1053
+ # (ADLS Gen2) accounts a directory placeholder cannot be
1054
+ # removed until empty, and a parent path always sorts before
1055
+ # its descendants, so reverse order empties each placeholder
1056
+ # before deleting it. Flat namespaces are order-independent.
1057
+ for blob in sorted(children, key=lambda child: child.name, reverse=True):
1058
+ self._get_blob_client(blob_name=blob.name).delete_blob()
1059
+ deleted.append(blob.name)
1060
+
1061
+ # Delete the exact blob (file or now-empty dir) last.
1010
1062
  try:
1011
1063
  self._get_blob_client(blob_name=blob_name).delete_blob()
1012
1064
  deleted.append(blob_name)
1013
1065
  except ResourceNotFoundError:
1014
1066
  pass
1015
1067
 
1016
- # Delete all blobs under the prefix
1017
- for blob in children:
1018
- self._get_blob_client(blob_name=blob.name).delete_blob()
1019
- deleted.append(blob.name)
1020
-
1021
1068
  if not deleted:
1022
1069
  raise FileNotFoundError(f"Blob not found: {blob_name}")
1023
1070