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.

Functions

resolve_credential(*[, credential, ...])

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

translate_azure_errors(*[, key_arg, ...])

Decorator: translate Azure SDK exceptions into KeyError subclasses.

azure_store(container, *[, prefix, ...])

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

typed_store(decode, encode, *[, base])

A base (default ContainerStore) class with values decode(bytes) / encode(obj) -> bytes.

typed_codec(decode, encode)

typed_store as a class decorator: the shape azure_store(value_codec=...) takes.

delete_container(store, name, *[, force])

Delete container name (a key of store); with force=True cascade-delete its blobs.

walk_container(store[, delimiter])

Hierarchical walk (BlobProperties / BlobPrefix nodes) of store's own scope.

substore(store, key)

The sub-store of store under key (a key ending in '/'), as the caller sees it.

azure_func_service(rootfolder[, extra_args, ...])

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

Classes

AzureConnection([credential, ...])

Holds a resolved credential and a lazy BlobServiceClient.

AccountCollection([connection, ...])

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

AccountReader([connection, ...])

Adds __getitem__ returning a ContainerStore (or configured subclass).

AccountStore([connection, ...])

Read-write Mapping over containers in an account.

BlobHandle(container, blob, *[, connection])

Thin facade over one BlobClient.

ContainerCollection(container, *[, prefix, ...])

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

ContainerReader(container, *[, prefix, ...])

Adds __getitem__ (returns bytes) over ContainerCollection.

ContainerStore(container, *[, prefix, ...])

The primary read-write Mapping over an Azure container.

AzureJsonStore

AzurePickleStore

AzureTextStore

Exceptions

BlobAlreadyExistsError

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

BlobNotFoundError

Raised when a blob does not exist.

ContainerAlreadyExistsError

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

ContainerNotEmptyError

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

ContainerNotFoundError

Raised when a container does not exist.

class azuredol.AccountCollection(connection=None, *, container_store_cls=None, container_store_kwargs=None)[source]#

Bases: _AccountStoreBase, KvReader

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

class azuredol.AccountReader(connection=None, *, container_store_cls=None, container_store_kwargs=None)[source]#

Bases: AccountCollection

Adds __getitem__ returning a ContainerStore (or configured subclass).

class azuredol.AccountStore(connection=None, *, container_store_cls=None, container_store_kwargs=None)[source]#

Bases: AccountReader, KvPersister

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 azuredol.ops.delete_container(store, name, force=True) to acknowledge a cascading delete. See misc/docs/design_decisions.md §12.

class azuredol.AzureConnection(credential=None, connection_string=None, account_url=None, account_key=None, client_kwargs=dict())[source]#

Bases: object

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 (Union[str, dict, Any, None]) – explicit credential object, key string, or SAS string.

  • connection_string (Optional[str]) – full connection string (overrides credential).

  • account_url (Optional[str]) – storage account URL (with or without credential).

  • account_key (Optional[str]) – 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, blob)[source]#

Cheap derivation of a BlobClient from the shared service client.

container_client(container)[source]#

Cheap derivation of a ContainerClient from the shared service client.

classmethod from_anything(source)[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)

Return type:

AzureConnection

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]#

Bases: KeyError

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, blob, *, connection=None)[source]#

Bases: object

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 (Union[str, ContainerClient]) – An existing ContainerClient or a container name string.

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

  • connection (Union[AzureConnection, BlobServiceClient, str, dict, None]) – Ignored if container is a ContainerClient. Otherwise resolved via AzureConnection.from_anything.

acquire_lease(lease_duration=-1)[source]#

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

append(data)[source]#

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

Return type:

None

copy_from(source_url, *, requires_sync=None)[source]#

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

create(data, *, overwrite=False, **kwargs)[source]#

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

Return type:

None

download_stream(*, chunk_size=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=None, length=None)[source]#

Download (possibly a range) and return bytes.

Return type:

bytes

url(*, expires_in=None)[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).

Return type:

str

write(data, *, blob_type=BlobType.BLOCKBLOB, overwrite=True, content_type=None, metadata=None, **kwargs)[source]#

Upload data; default overwrite=True for MutableMapping symmetry.

Return type:

None

exception azuredol.BlobNotFoundError[source]#

Bases: KeyError

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

exception azuredol.ContainerAlreadyExistsError[source]#

Bases: KeyError

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

class azuredol.ContainerCollection(container, *, prefix='', connection=None, create_container_if_missing=False, blob_type=BlobType.BLOCKBLOB, upload_kwargs=None, download_kwargs=None)[source]#

Bases: _ContainerStoreBase, KvReader

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_ids(delimiter='/')[source]#

Hierarchical walk of the leaf’s prefix, yielding BlobProperties / BlobPrefix nodes.

Thin pass-through to ContainerClient.walk_blobs. The scope is the leaf’s own prefix and each node’s .name is an absolute blob id (never a store key), so under a dol key codec it disagrees with __iter__. For a scope that follows the caller’s (possibly wrapped) store use azuredol.ops.walk(store).

Return type:

Iterator[Any]

exception azuredol.ContainerNotEmptyError[source]#

Bases: RuntimeError

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

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

exception azuredol.ContainerNotFoundError[source]#

Bases: KeyError

Raised when a container does not exist.

class azuredol.ContainerReader(container, *, prefix='', connection=None, create_container_if_missing=False, blob_type=BlobType.BLOCKBLOB, upload_kwargs=None, download_kwargs=None)[source]#

Bases: ContainerCollection

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. This works on a bare store and on typed-store classes, but not through every wrapper stack (a dol wrapper placed on an instance is not carried into the sub-store); azuredol.substore() is the portable form and should be preferred (i2mint/azuredol#9).

class azuredol.ContainerStore(container, *, prefix='', connection=None, create_container_if_missing=False, blob_type=BlobType.BLOCKBLOB, upload_kwargs=None, download_kwargs=None)[source]#

Bases: ContainerReader, KvPersister

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, *, prefix='', connection=None, credential=None, connection_string=None, account_url=None, account_key=None, create_container_if_missing=False, blob_type=BlobType.BLOCKBLOB, value_codec=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 (str) – Container name.

  • prefix (str) – Blob-name prefix to scope all keys.

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

  • credential (Any) – Forwarded to AzureConnection when connection is None.

  • connection_string (Optional[str]) – Forwarded to AzureConnection when connection is None.

  • account_url (Optional[str]) – Forwarded to AzureConnection when connection is None.

  • account_key (Optional[str]) – Forwarded to AzureConnection when connection is None.

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

  • blob_type (BlobType) – Default blob type for writes.

  • value_codec (Optional[Callable]) – A decorator that, given a class, returns a wrapped class. Typically typed_codec(decode, encode). A bare dol.wrap_kvs(...) breaks the trailing-slash sub-store (i2mint/azuredol#4). When None (default), bytes pass through.

Returns:

A ContainerStore instance, possibly codec-wrapped.

azuredol.delete_container(store, name, *, force=False)[source]#

Delete container name (a key of store); with force=True cascade-delete its blobs.

The deletion is del store[name] on the caller’s own store, so a key codec maps name, a read-only wrapper refuses, and a scoped view cannot reach out of scope.

Raises:
Return type:

None

azuredol.resolve_credential(*, credential=None, connection_string=None, account_url=None, account_key=None)[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:

dict

Raises:

ValueError – if no source resolves.

azuredol.substore(store, key)[source]#

The sub-store of store under key (a key ending in '/'), as the caller sees it.

This is the portable form. store['a/'] is kept and works on bare stores and typed-store classes, but not through every wrapper stack; prefer substore everywhere else.

substore(store, 'a/')[k] is store['a/' + k] for every wrapper stack on store (up to the key limits below). On a bare ContainerStore it is the cheap, server-scoped store['a/'] (listing only a/’s blobs). On anything wrapped (a dol key/value codec or scope, including the classes typed_store/AzureJsonStore build) it is a KeyCodecs.prefixed(key) view of that store, so the caller’s key and value mappings still apply; that view lists the wrapped store’s whole scope and keeps the keys under key. (store['a/'] on a store wrapped as an instance is minted by the innermost class and does not know about the wrappers: i2mint/azuredol#6.)

The cheap path needs a leaf that does not override _id_of_key (a subclass that maps keys

gets the view). Attribute lookups on the view fall through to the wrapped store, so .prefix and .walk_ids() of a view describe the parent’s scope: use walk() on it. Needs dol>=0.3.75 (its prefix view skips out-of-scope keys when iterating).

Parameters:
  • store – a ContainerReader/ContainerStore, wrapped or not

  • key (str) – a key ending in '/', without a leading '/' or an inner '//'

azuredol.translate_azure_errors(*, key_arg=0, not_found_cls=<class 'azuredol.errors.BlobNotFoundError'>, exists_cls=<class 'azuredol.errors.BlobAlreadyExistsError'>)[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 (int | str) – 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 (type[KeyError]) – Exception class to raise on ResourceNotFoundError.

  • exists_cls (type[KeyError]) – Exception class to raise on ResourceExistsError.

Return type:

Callable

azuredol.typed_codec(decode, encode)[source]#

typed_store as a class decorator: the shape azure_store(value_codec=...) takes.

>>> import json
>>> codec = typed_codec(json.loads, lambda o: json.dumps(o).encode())
>>> callable(codec)
True
Return type:

Callable

azuredol.typed_store(decode, encode, *, base=<class 'azuredol.base.ContainerStore'>)[source]#

A base (default ContainerStore) class with values decode(bytes) / encode(obj) -> bytes.

Unlike a bare wrap_kvs(ContainerStore, obj_of_data=..., data_of_obj=...), the trailing-slash sub-store convention keeps working: store['2026/'] comes back as a sub-store of the same typed class, instead of being fed to decode (i2mint/azuredol#4).

azuredol.walk_container(store, delimiter='/')#

Hierarchical walk (BlobProperties / BlobPrefix nodes) of store’s own scope.

The scope is the blob prefix the caller’s empty key resolves to, so a key codec’s prefix is included (a codec that is not a pure prefix, e.g. a suffix, raises ValueError; unlike ContainerCollection.walk_ids, which sees only the leaf’s prefix).

Return type:

Iterator[Any]

Modules

base

Close-to-metal Mapping stores over Azure Blob Storage.

connection

Connection-layer for azuredol.

errors

Custom exceptions and error-translation decorator for azuredol.

functions

Azure functions tools

ops

Key-taking and scope-wide operations, as free functions over the caller's (possibly wrapped) store.

recipes

Convenience wrappers and factories for azuredol.

testing

Testing helpers for azuredol — Azurite fixture and convenience builders.