# objectstore.azure

> A module of objectstore's API.

`objectstore.azure` — Azure Blob Storage through SAS-token URLs.

Azure's own signing schemes are `SharedKey` (an HMAC-SHA256 over a canonical
form that is close to but not the same as SigV4) and Entra ID bearer tokens.
Neither is implemented. What is implemented is the **SAS token**: a query
string that already carries a signature someone else produced, which is what
Iceberg's `adls.sas-token.<account>` property holds and what a catalog vends.
With a SAS token, a blob is an HTTPS URL, and the whole backend is URL
construction plus two required headers.

Iceberg addresses ADLS Gen2 as `abfss://container@account.dfs.core.windows.net/path`.
The Blob endpoint (`account.blob.core.windows.net`) serves the same data with
the simpler API, so that is what this targets; `wasbs://` names the Blob
endpoint directly and is accepted as well.

## Structs

### `AzureLocation`

```mojo
struct AzureLocation
```

A parsed `abfs(s)://` or `wasb(s)://` location.

Implements: `Copyable`, `Movable`

Fields:

- `account: String`
- `container: String`
- `path: String`

### `AzureConfig`

```mojo
struct AzureConfig
```

Implements: `Copyable`, `Movable`

Fields:

- `account: String`
- `sas_token: String`: The query string of a SAS URL, with or without its leading `?`.
- `endpoint: String`: Overrides the derived `https://<account>.blob.core.windows.net`, for Azurite or a sovereign cloud.

#### `__init__`

```mojo
def __init__(out self)
```

```mojo
def __init__(out self, var account: String, var sas_token: String, var endpoint: String)
```

#### `from_properties`

```mojo
def from_properties(props: Dict[String, String], account: String = "") -> Self
```

Reads Iceberg's `adls.sas-token.<account>` (and a plain `adls.sas-token` / `azure.sas-token` fallback).

#### `base_url`

```mojo
def base_url(self) -> String
```

#### `url_for`

```mojo
def url_for(self, container: String, blob: String) -> String
```

#### `list_url`

```mojo
def list_url(self, container: String, prefix: String, marker: String) -> String
```

#### `headers`

```mojo
def headers(self) -> List[Header]
```

#### `write_headers`

```mojo
def write_headers(self) -> List[Header]
```

### `BlobInfo`

```mojo
struct BlobInfo
```

Implements: `Copyable`, `Movable`

Fields:

- `name: String`
- `size: Int`

### `AzureClient`

```mojo
struct AzureClient
```

Implements: `Copyable`, `Movable`

Fields:

- `config: AzureConfig`
- `http: HttpClient`

#### `__init__`

```mojo
def __init__(out self, var config: AzureConfig)
```

```mojo
def __init__(out self, var config: AzureConfig, var http: HttpClient)
```

#### `input`

```mojo
def input(self, container: String, blob: String) -> HttpInputFile
```

#### `output`

```mojo
def output(self, container: String, blob: String) -> HttpOutputFile
```

#### `delete_blob`

```mojo
def delete_blob(self, container: String, blob: String)
```

#### `list_blobs`

```mojo
def list_blobs(self, container: String, prefix: String = "") -> List[BlobInfo]
```

Every blob under `prefix`, following `NextMarker` pages.

The response is `<EnumerationResults><Blobs><Blob><Name>…`, which the
same shallow XML reader handles as S3's `ListBucketResult`.

## Functions

### `parse_azure_uri`

```mojo
def parse_azure_uri(location: String) -> AzureLocation
```

`abfss://container@account.dfs.core.windows.net/a/b` -> account `account`, container `container`, path `a/b`.

`wasbs://container@account.blob.core.windows.net/a/b` parses the same way.
A bare `az://container/path` is accepted with the account left empty, for
callers that configure the endpoint explicitly.

## Aliases

### `BLOB_API_VERSION`

```mojo
comptime BLOB_API_VERSION = String("2021-12-02")
```

Sent as `x-ms-version`. Azure requires it on every request, and older services reject versions they do not know — this one is old enough to be universally supported and new enough for everything used here.
