azuredol

Access Azure Blob Storage through a Mapping interface.

azuredol exposes Azure Blob Storage as dol-style Mapping / MutableMapping interfaces.

Quick start:

from azuredol import azure_store

# Uses connection_string env var or Azurite (UseDevelopmentStorage=true)
store = azure_store(
    'mycontainer',
    connection_string='UseDevelopmentStorage=true',
    create_container_if_missing=True,
)

store['k1'] = b'hello world'
store['k1']               # → b'hello world'
'k1' in store             # → True
del store['k1']

See misc/docs/architecture.md for the layered design.

class azuredol.AccountCollection(connection: AzureConnection | BlobServiceClient | str | dict | None = None, *, container_store_cls: type = None, container_store_kwargs: dict | None = None)[source]

Mapping over container names in an account (iter / contains / len).

class azuredol.AccountReader(connection: AzureConnection | BlobServiceClient | str | dict | None = None, *, container_store_cls: type = None, container_store_kwargs: dict | None = None)[source]

Adds __getitem__ returning a ContainerStore (or configured subclass).

class azuredol.AccountStore(connection: AzureConnection | BlobServiceClient | str | dict | None = None, *, container_store_cls: type = None, container_store_kwargs: dict | None = None)[source]

Read-write Mapping over containers in an account.

__setitem__ creates a container; the value is ignored except when it’s a Mapping (in which case it is treated as an initial bulk-load).

__delitem__ refuses non-empty containers — call self.delete(name, force=True) to acknowledge a cascading delete. See misc/docs/design_decisions.md §12.

delete(k: str, *, force: bool = False) None[source]

Delete a container; if force=True, cascade-delete any blobs first.

class azuredol.AzureConnection(credential: str | dict | Any | None = None, connection_string: str | None = None, account_url: str | None = None, account_key: str | None = None, client_kwargs: dict = <factory>)[source]

Holds a resolved credential and a lazy BlobServiceClient.

This is the dependency-injection seam for the whole package. Tests construct one pointing at Azurite without touching any store class.

Parameters:
  • credential – explicit credential object, key string, or SAS string.

  • connection_string – full connection string (overrides credential).

  • account_url – storage account URL (with or without credential).

  • account_key – shared-key for the account.

All four args are optional; the credential cascade in resolve_credential is consulted if none are provided.

blob_client(container: str, blob: str)[source]

Cheap derivation of a BlobClient from the shared service client.

container_client(container: str)[source]

Cheap derivation of a ContainerClient from the shared service client.

classmethod from_anything(source) AzureConnection[source]

Convenience: build an AzureConnection from a thing-or-spec.

Accepts:
  • AzureConnection (returned as-is)

  • BlobServiceClient (wrapped without further resolution)

  • str (connection string)

  • dict (passed as kwargs)

  • None (defer to env / AAD)

property service_client: BlobServiceClient

The lazily-constructed BlobServiceClient. Cached for the connection’s lifetime.

azuredol.AzureJsonStore

alias of ContainerStore

azuredol.AzurePickleStore

alias of ContainerStore

azuredol.AzureTextStore

alias of ContainerStore

exception azuredol.BlobAlreadyExistsError[source]

Raised on strict-create write attempts when the blob already exists.

KeyError subclass for symmetry with BlobNotFoundError — both signal a key-vs-store mismatch.

class azuredol.BlobHandle(container: str | ContainerClient, blob: str, *, connection: AzureConnection | BlobServiceClient | str | dict | None = None)[source]

Thin facade over one BlobClient. Not a Mapping.

Use when you need the full Azure-blob surface for a single blob: metadata, properties, conditional writes, leases, copy, append, SAS URL generation, streaming downloads.

Parameters:
  • container – An existing ContainerClient or a container name string.

  • blob – The blob name (relative to the container root; full path including ‘/’).

  • connection – Ignored if container is a ContainerClient. Otherwise resolved via AzureConnection.from_anything.

acquire_lease(lease_duration: int = -1)[source]

Acquire a lease (15-60s, or -1 for infinite). Returns a BlobLeaseClient.

append(data) None[source]

Append bytes to an append-blob (creating it if absent).

copy_from(source_url: str, *, requires_sync: bool | None = None)[source]

Server-side copy from a URL. Same-account = sync; cross-account = async (poll).

create(data, *, overwrite: bool = False, **kwargs) None[source]

Strict-create variant of write; raises BlobAlreadyExistsError on conflict.

download_stream(*, chunk_size: int | None = None)[source]

Return a streaming downloader. Iterate chunks via .chunks() or .readinto(fp).

properties()[source]

Return BlobProperties (size, last_modified, etag, content_settings, metadata, …).

read(*, offset: int | None = None, length: int | None = None) bytes[source]

Download (possibly a range) and return bytes.

url(*, expires_in: timedelta | None = None) str[source]

Return the blob URL.

If expires_in is set and the underlying credential is a shared-key, a SAS URL with read permission is generated. Otherwise the plain blob URL is returned (which is only useful for public containers or pre-shared SAS contexts).

write(data, *, blob_type: BlobType = BlobType.BLOCKBLOB, overwrite: bool = True, content_type: str | None = None, metadata: dict | None = None, **kwargs) None[source]

Upload data; default overwrite=True for MutableMapping symmetry.

exception azuredol.BlobNotFoundError[source]

Raised when a blob does not exist. KeyError subclass so Mapping consumers work.

exception azuredol.ContainerAlreadyExistsError[source]

Raised on strict-create attempts when the container already exists.

class azuredol.ContainerCollection(container: str | ContainerClient, *, prefix: str = '', connection: AzureConnection | BlobServiceClient | str | dict | None = None, create_container_if_missing: bool = False, blob_type: BlobType = BlobType.BLOCKBLOB, upload_kwargs: dict | None = None, download_kwargs: dict | None = None)[source]

Iteration / membership / cardinality (__len__ deliberately omitted) over a container.

__len__ is intentionally NOT implemented — see misc/docs/design_decisions.md §2. Pagination cost is unbounded; users wanting a count call sum(1 for _ in store).

walk(delimiter: str = '/') Iterator[Any][source]

Hierarchical walk yielding either BlobProperties or BlobPrefix nodes.

Thin pass-through to ContainerClient.walk_blobs. Useful for tree-shaped listings; not part of the Mapping interface.

exception azuredol.ContainerNotEmptyError[source]

Raised on del account_store[name] when the container has blobs.

The user must call account_store.delete(name, force=True) to acknowledge the cascading delete. See misc/docs/design_decisions.md §12.

exception azuredol.ContainerNotFoundError[source]

Raised when a container does not exist.

class azuredol.ContainerReader(container: str | ContainerClient, *, prefix: str = '', connection: AzureConnection | BlobServiceClient | str | dict | None = None, create_container_if_missing: bool = False, blob_type: BlobType = BlobType.BLOCKBLOB, upload_kwargs: dict | None = None, download_kwargs: dict | None = None)[source]

Adds __getitem__ (returns bytes) over ContainerCollection.

The trailing-slash sub-store convention lives here: store['sub/'] returns a new ContainerReader with extended prefix and zero round-trips.

class azuredol.ContainerStore(container: str | ContainerClient, *, prefix: str = '', connection: AzureConnection | BlobServiceClient | str | dict | None = None, create_container_if_missing: bool = False, blob_type: BlobType = BlobType.BLOCKBLOB, upload_kwargs: dict | None = None, download_kwargs: dict | None = None)[source]

The primary read-write Mapping over an Azure container.

Doctest below requires Azurite (or live Azure). It is shown but not auto-run.

Example (requires Azurite):

>>> from azuredol import ContainerStore
>>> s = ContainerStore(
...     'my-test-container',
...     connection='UseDevelopmentStorage=true',
...     create_container_if_missing=True,
... )
>>> s['k1'] = b'v1'
>>> s['k1']
b'v1'
>>> 'k1' in s
True
>>> del s['k1']

For unit tests without Azurite, prototype with dol’s in-memory pattern: wrap_kvs(dict(), ...).

azuredol.azure_func_service(rootfolder, extra_args='', *, verbose=True, wait_for_log=None, timeout=30, print_output=True)[source]

Context manager to start an Azure Function host using func start in the specified folder.

Parameters:
  • rootfolder (str) – The directory containing the Azure Function App.

  • extra_args (str) – Additional command-line arguments for the func start command.

  • wait_for_log (str) – A substring or regular expression pattern that indicates when the service is ready. If provided, the context manager waits until a matching log line is detected.

  • timeout (int) – Maximum time in seconds to wait for the ready log message.

  • print_output (bool) – If True, prints the process output to stdout.

Yields:

subprocess.Popen – The process object representing the running Azure Functions host.

Usage Example:
with azure_func_service(‘/path/to/azure_func’, extra_args=”–verbose”, wait_for_log=r’Host started’) as proc:

# Place code here to test the HTTP service. …

azuredol.azure_store(container: str, *, prefix: str = '', connection: AzureConnection | str | dict | None = None, credential: Any = None, connection_string: str | None = None, account_url: str | None = None, account_key: str | None = None, create_container_if_missing: bool = False, blob_type: BlobType = BlobType.BLOCKBLOB, value_codec: Callable | None = None)[source]

Build a ready-to-use ContainerStore (or codec-wrapped variant) in one call.

This is the “simple things simple” entry point. For complex configurations construct AzureConnection and ContainerStore directly.

Parameters:
  • container – Container name.

  • prefix – Blob-name prefix to scope all keys.

  • connection – An AzureConnection (or anything AzureConnection.from_anything accepts) to reuse a service client. Mutually exclusive with the explicit credential / connection_string / account_* kwargs.

  • credential – Forwarded to AzureConnection when connection is None.

  • connection_string – Forwarded to AzureConnection when connection is None.

  • account_url – Forwarded to AzureConnection when connection is None.

  • account_key – Forwarded to AzureConnection when connection is None.

  • create_container_if_missing – Create the container on first use if absent.

  • blob_type – Default blob type for writes.

  • value_codec – A decorator that, given a class, returns a wrapped class. Typically a partially-applied dol.wrap_kvs(...). When None (default), bytes pass through.

Returns:

A ContainerStore instance, possibly codec-wrapped.

azuredol.resolve_credential(*, credential: str | dict | Any | None = None, connection_string: str | None = None, account_url: str | None = None, account_key: str | None = None) dict[source]

Resolve a credential into a normalized form that can build a BlobServiceClient.

Cascade (first hit wins):

  1. Explicit credential=

  2. Explicit connection_string=

  3. Explicit account_url= + account_key= (or just account_url= + AAD)

  4. Env var AZURE_STORAGE_CONNECTION_STRING

  5. Env vars AZURE_STORAGE_ACCOUNT_URL + AZURE_STORAGE_ACCOUNT_KEY

  6. Env var AZURE_STORAGE_ACCOUNT_URL alone + DefaultAzureCredential

Returns:

{"conn_str": "..."} # for from_connection_string {"account_url": "...", "credential": <obj>} # for __init__

Return type:

A dict with one of these shapes

Raises:

ValueError – if no source resolves.

azuredol.translate_azure_errors(*, key_arg: int | str = 0, not_found_cls: type[KeyError] = <class 'azuredol.errors.BlobNotFoundError'>, exists_cls: type[KeyError] = <class 'azuredol.errors.BlobAlreadyExistsError'>) Callable[source]

Decorator: translate Azure SDK exceptions into KeyError subclasses.

Auth errors (ClientAuthenticationError) and all other Azure errors propagate untouched — they are never swallowed as “key absent”. See misc/docs/design_decisions.md §4.

Parameters:
  • key_arg – Position (int) or name (str) of the key argument in the wrapped method’s signature. Used to populate KeyError(key). The receiver self is at index 0, so the user-facing key is typically at index 1 (the default).

  • not_found_cls – Exception class to raise on ResourceNotFoundError.

  • exists_cls – Exception class to raise on ResourceExistsError.