Azure Blob Storage handler with unified local/cloud interface
Project description
py-containerio
Shared Azure Blob Storage handler for internal and collaboration projects of Alliance SwissPass. Provides a unified interface for reading and writing data to both local filesystem and Azure Blob Storage, allowing seamless switching between local development and cloud environments.
Features
- Unified API: Same code works for local files and Azure Blob Storage
- Environment-based switching: Use
STORAGE_ENVto switch between local/staging/prod - Polars integration: Native support for Polars DataFrames and LazyFrames
- Multiple auth methods: Device code flow (default), client credentials (CI/CD), or SAS tokens
- CLI tools: Unified
iocommand for listing, downloading, uploading, deleting blobs and generating SAS tokens - Memory-efficient: Streaming support with
scan_*andsink_*methods
Install containerio
From PyPI
The package is available on PyPI: https://pypi.org/project/containerio/
pip install containerio
Or with uv:
uv add containerio
uv sync
Use containerio as a package dependency
From PyPI (recommended)
Add to your project's pyproject.toml:
[project]
dependencies = [
"containerio",
]
Then run:
uv sync
From Forgejo Package Registry
If you need the Forgejo version, configure the custom index:
[project]
dependencies = [
"containerio",
]
[tool.uv.sources]
containerio = { index = "forgejo" }
[[tool.uv.index]]
name = "forgejo"
url = "https://codefloe.com/api/packages/Alliance-SwissPass/pypi/simple"
Then run:
uv sync
From Git
For development or testing, install directly from the repository:
[project]
dependencies = [
"containerio",
]
[tool.uv.sources]
containerio = { git = "https://codefloe.com/Alliance-SwissPass/py-containerio.git", tag = "v1.0.0" }
Usage in Other Projects
When installed as a dependency, containerio integrates seamlessly with your project:
- CLI tools are available - Run
uv run iodirectly from your project .envfiles live in your project - The storage module reads.envand.env.stagingfrom your current working directory, not from the containerio package- No configuration in containerio needed - Just add the dependency and configure in your project
Example workflow in your project:
# Your project directory
cd my-project
# Create your .env.staging file here
cp /path/to/.env.example .env.staging
# Edit .env.staging with your credentials
# List blobs in both containers
uv run io ls --env staging
# List blobs in local mode (./data/ directory)
uv run io ls
# Generate SAS tokens (updates .env.staging)
uv run io generate-sas --env staging --update-env
Example Usage
import os
from containerio import StorageHandler
# Set environment (defaults to "local" which uses ./data/ directory)
os.environ["STORAGE_ENV"] = "staging"
# or "prod"
# Read operations (uses read container)
handler = StorageHandler(container="ro")
# Read files
df = handler.read_csv_blob("path/to/file.csv")
df = handler.read_excel_blob("path/to/file.xlsx")
df, metadata = handler.read_parquet_blob("path/to/file.parquet")
# Lazy scan (memory-efficient for large files)
lf, metadata = handler.scan_parquet_blob("large_file.parquet")
lf = handler.scan_csv_blob("large_file.csv")
# Write operations (uses write container)
handler_rw = StorageHandler(container="rw")
handler_rw.write_csv_blob("output.csv", df)
handler_rw.write_parquet_blob("output.parquet", df)
handler_rw.write_excel_blob("output.xlsx", df)
# Stream LazyFrame directly to storage
handler_rw.sink_parquet_blob("output.parquet", lf)
# Upload any file (JSON, YAML, images, etc.)
handler_rw.upload_blob("config.json", "./local/config.json")
# List blobs
blobs = handler.list_blobs() # Returns List[BlobInfo]
handler.download_blob("file") # Download to local ./data/
handler_rw.delete_blob("file") # Delete a blob or folder
handler_rw.move_blob("old.csv", "archive/old.csv") # Move/rename
Configuration
Environment Variables
Set STORAGE_ENV to control the storage backend:
| Value | Behavior |
|---|---|
local (default) |
Uses local filesystem (./data/ directory) |
staging |
Uses Azure staging storage (reads from .env.staging) |
prod |
Uses Azure production storage (reads from .env) |
Azure Configuration
For Azure storage, create a .env file (for prod) or .env.staging (for staging).
Important: Restrict permissions on your .env file so only you can access it:
chmod 600 .env
Example .env (production)
AZURE_STORAGE_ACCOUNT=<azure_storage_account>
AZURE_CONTAINER_NAME_READ_PROD=<azure_container_name_read_prod>
AZURE_CONTAINER_NAME_WRITE_PROD=<azure_container_name_write_prod>
AZURE_TENANT_ID=<azure_tenant_id>
AZURE_CLIENT_ID=<azure_client_id>
# SAS tokens (optional, if not using device code auth)
AZURE_STORAGE_SAS_TOKEN_READ=<azure_storage_sas_token_read>
AZURE_STORAGE_SAS_TOKEN_WRITE=<azure_storage_sas_token_write>
Example .env.staging
AZURE_STORAGE_ACCOUNT=<azure_storage_account>
AZURE_CONTAINER_NAME_READ_STAGING=<azure_container_name_read_staging>
AZURE_CONTAINER_NAME_WRITE_STAGING=<azure_container_name_write_staging>
AZURE_TENANT_ID=<azure_tenant_id>
AZURE_CLIENT_ID=<azure_client_id>
# SAS tokens (optional, if not using device code auth)
AZURE_STORAGE_SAS_TOKEN_READ=<azure_storage_sas_token_read>
AZURE_STORAGE_SAS_TOKEN_WRITE=<azure_storage_sas_token_write>
Authentication
Use io auth to manage authentication explicitly.
Device-code login and status auto-discover the environment from your .env / .env.staging file, so --env is optional.
SAS token and client credentials login require explicit --env:
# Device code login (default) — auto-discovers env from .env file
uv run io auth
# Device code login with explicit env
uv run io auth --env staging
# Check auth status across all methods
uv run io auth status
# SAS token login — requires --env (wrong env = wrong token)
uv run io auth login --method sas-token --env staging
# Client credentials — requires --env (for CI/CD pipelines)
uv run io auth login --method client-credentials --env prod
# Clear cached device code tokens
uv run io auth clear
# Force re-authentication (clears cache first)
uv run io auth login --force
The chosen auth method is saved to the session. Subsequent commands (ls, download, etc.) automatically use it.
Authentication Methods
-
Device Code Flow (recommended for development)
- Interactive browser-based authentication
- Tokens are cached locally for 90 days
- No credentials stored in files
-
Client Credentials
- For CI/CD pipelines
- Use
io auth login --method client-credentialsto authenticate - Requires:
AZURE_TENANT_ID: Service principal's tenant IDAZURE_CLIENT_ID_CONTAINER: Service principal's client IDAZURE_CLIENT_SECRET_CONTAINER_PROD: Service principal's client secret for productionAZURE_CLIENT_SECRET_CONTAINER_STAGING: Service principal's client secret for staging
-
SAS Token
- Use pre-generated tokens with limited validity
- Good for sharing temporary access
CLI Tools
The package provides a unified io command with subcommands for all storage operations. All commands accept --env prod|staging to override STORAGE_ENV.
| Command | Usage | Description |
|---|---|---|
auth |
uv run io auth [login|status|clear] [--method M] [--force] |
Manage authentication |
ls |
uv run io ls [--container ro|rw|ro-rw] |
List blobs in storage |
download |
uv run io download <blob_name> [--container ro|rw] |
Download a blob to local data/ directory |
upload |
uv run io upload <blob_name> <file_path> |
Upload a local file to the write container |
rm |
uv run io rm <path> |
Delete a blob or folder from the write container |
mv |
uv run io mv <source> <dest> [--overwrite] |
Move (rename) a blob within the write container |
generate-sas |
uv run io generate-sas [--container ro|rw|ro-rw] [--days N] [--update-env] |
Generate Azure Storage SAS tokens |
Examples
# Authenticate with device code flow (auto-discovers env)
uv run io auth
# Check authentication status (auto-discovers env)
uv run io auth status
# List blobs in local mode (./data/)
uv run io ls
# List both read and write containers (default --container ro-rw)
uv run io ls --env staging
# Download a blob to the local data/ directory
uv run io download path/to/file.csv --env staging
# Upload a local file to the write container
uv run io upload path/to/blob.csv ./local/file.csv --env staging
# Delete a blob or folder from the write container
uv run io rm path/to/blob.csv --env staging
uv run io rm old-folder/ --env staging
# Move a blob within the write container
uv run io mv data/old.csv archive/old.csv --env staging
# Overwrite an existing destination
uv run io mv data/old.csv data/existing.csv --env staging --overwrite
# Generate SAS tokens and update .env.staging file
uv run io generate-sas --env staging --update-env
# Generate only read container token, valid for 3 days
uv run io generate-sas --env prod --container ro --days 3
Session
When doing multiple operations on the same container, use session to save defaults so you don't have to repeat --env and --container on every command.
Both --env and --container are required to start a session.
# Start a session — saves env and container
uv run io session --env staging --container rw
# Show active session
uv run io session
# Now these use the saved defaults
uv run io ls
uv run io rm old-folder/
uv run io mv data/old.csv archive/old.csv
# Explicit flags still override the session
uv run io ls --container ro
# End the session
uv run io session clear
The session is stored in .containerio/session.json in the current directory. It is project-local and git-ignored.
The session is also available programmatically via the Session class:
from containerio.session import Session
session = Session()
print(session.auth_method) # e.g. 'client-credentials'
print(session.env) # e.g. 'staging'
print(session.is_active) # True if any session data exists
Options:
--env:prodorstaging(default: staging for sas; usesSTORAGE_ENVfor other commands)--container:ro,rw, orro-rw(default: ro-rw)--days: Token validity in days, max 7 (default: 1)--update-env: Write tokens to .env file instead of printing
Note: SAS tokens are generated using a user delegation key which has a maximum validity of 7 days (Azure limitation). See Microsoft documentation for details.
API Reference
StorageHandler
Main class for storage operations.
StorageHandler(
container: Literal["ro", "rw"] = "ro",
auth_type: Optional[str] = None,
session: Optional[Session] = None,
)
auth_type=None: Falls back to session auth method, then device code flowauth_type="device-code": Device code flow (interactive)auth_type="sas-token": SAS token authenticationauth_type="client-credentials": Client credentials (service principal)session=None: Creates a newSession(skipped in local mode). Pass an existingSessioninstance for dependency injection.container="ro": Uses the read container (AZURE_CONTAINER_NAME_READ_{ENV})container="rw": Uses the write container (AZURE_CONTAINER_NAME_WRITE_{ENV})
Read Methods
| Method | Description |
|---|---|
read_parquet_blob(blob_name) |
Read parquet file, returns (DataFrame, metadata) |
read_csv_blob(blob_name, **kwargs) |
Read CSV file into DataFrame |
read_excel_blob(blob_name, **kwargs) |
Read Excel file into DataFrame |
scan_parquet_blob(blob_name) |
Lazy scan parquet, returns (LazyFrame, metadata) |
scan_csv_blob(blob_name, **kwargs) |
Lazy scan CSV file |
Write Methods
| Method | Description |
|---|---|
write_parquet_blob(blob_name, df) |
Write DataFrame to parquet |
write_csv_blob(blob_name, df, **kwargs) |
Write DataFrame to CSV |
write_excel_blob(blob_name, df, **kwargs) |
Write DataFrame to Excel |
sink_parquet_blob(blob_name, lf) |
Stream LazyFrame to parquet |
Utility Methods
| Method | Description |
|---|---|
upload_blob(blob_name, file_path, progress_hook) |
Upload a local file to storage |
delete_blob(blob_name) |
Delete a blob, file, or folder recursively. Returns List[str] of deleted paths |
move_blob(source, destination, overwrite) |
Move (rename) a blob within the same container |
download_blob(blob_name, progress_hook) |
Download blob to local data/ directory. Returns Optional[Path] |
list_blobs() |
List all blobs, returns List[BlobInfo] |
AzureStorageConfig
Configuration dataclass for Azure storage settings.
from containerio import AzureStorageConfig
# Load from environment
config = AzureStorageConfig.from_environment() # Uses STORAGE_ENV
config = AzureStorageConfig.from_environment(environment="staging")
# Access configuration
print(config.storage_account)
print(config.container_name_read)
Environment
Enum for storage environments.
from containerio import Environment
# Parse from string
env = Environment.from_string("prod") # Environment.PROD
# Use enum directly
if env == Environment.LOCAL:
print("Using local storage")
Development
# Clone and install
git clone ssh://git@codefloe.com/Alliance-SwissPass/py-containerio.git
cd py-containerio
uv sync --group dev
# Run tests
uv run pytest
# Lint and format
uv run ruff check
uv run ruff format
Version Management with git-sv
This project uses git-sv for semantic versioning and changelog generation.
The .gitsv/config.yaml file defines how versions are bumped based on commit messages.
- Commit messages: Follow conventional commits format
- Version bump: The CI pipeline automatically runs
git-sv bumpbased on commit history - Changelog: The CI pipeline generates the changelog using
git-sv changelogand pins it as a Git issue - Release: The CI pipeline automatically handles tagging and publishing
The CI pipeline will automatically:
- Bump the version based on commit history
- Generate and pin the changelog as a Git issue
- Build and publish the package to Forgejo
- Create a release with the changelog notes
Publishing
Via CI Pipeline (recommended)
The repository includes a Crow CI pipeline that automatically builds, tests, and publishes the package when a git tag is pushed:
- Commit and push your changes
- Create and push a git tag (e.g.,
git tag v1.0.0 && git push --tags)
The pipeline will:
- Run linting and tests
- Update the version in
pyproject.tomlautomatically - Generate changelog from git history using
git-sv - Build and publish the package to Forgejo and PyPI
- Create a release with changelog notes
- Push version updates back to the repository
Note: For CI publishing, ensure the UV_PUBLISH_TOKEN secret is configured in your Crow CI pipeline settings.
Security Best Practices
- Never commit credentials: Always add
.env*to your.gitignorefile - Restrict file permissions: Use
chmod 600 .envto limit access to your credentials - Use short-lived tokens: SAS tokens have a maximum validity of 7 days
- Rotate secrets regularly: Especially for production environments and CI/CD pipelines
- Use device code flow: For development to avoid storing credentials in files
- Environment isolation: Use separate credentials for staging and production
- Limit token scope: When generating SAS tokens, specify the minimum required permissions
References
License
Alliance SwissPass https://allianceswisspass.ch
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file containerio-1.0.0.tar.gz.
File metadata
- Download URL: containerio-1.0.0.tar.gz
- Upload date:
- Size: 24.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Alpine Linux","version":"3.23.3","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e4562513636720dfead64cb04c32cb2463f2ee003233688bc55304c7fbd2e57
|
|
| MD5 |
85ee702790c30aaa34d84c9ea638894b
|
|
| BLAKE2b-256 |
a12806e77b1560d1491df94f6e26bf5be33684c0b715cb50f3e0e7b1ac6a79aa
|
File details
Details for the file containerio-1.0.0-py3-none-any.whl.
File metadata
- Download URL: containerio-1.0.0-py3-none-any.whl
- Upload date:
- Size: 26.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Alpine Linux","version":"3.23.3","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c4ee80069e027a7ccf3bc4f69d78e7c1f6d534d5c5bbc06e03f853b28e0244a
|
|
| MD5 |
31fb1a52cc0e929d97037b44dbe98ada
|
|
| BLAKE2b-256 |
22d63e9dbc1e94d7d9039538807ee5a04a09b346059c421a3ff8a3fe0051fc5c
|