Azure Blob Storage Data Source Template

Prev Next

Overview

The Azure Blob Storage data source template turns a blob container (optionally scoped to a prefix) into a Validatar data source. It lists blobs with azure-storage-blob, catalogs every blob as a table, reads and profiles the common data formats (CSV, Excel, Parquet, Avro, JSON, XML, GeoJSON), and captures statistics for non-tabular blobs (PDF, Markdown, images, plain text). Blob prefixes become schemas, blobs become tables, and columns are inferred from each blob's contents.

This is a Python (Script) template that uses the same shared file-reading library as the Windows Directory and AWS S3 templates. The macros, profiling, and custom fields are identical — only the storage layer (azure-storage-blob) and the connection parameters differ.

Platform: Azure Blob Storage / ADLS Gen2
Connection Category: Script (PythonScript)
Runs on: any runtime that has azure-storage-blob and network reachability to the storage account — typically the Data Agent
Template version: 1.1.0
Marketplace: Azure Blob Storage

What's new in 1.1.0: seven macros for nested JSON (see Nested JSON); folder listings scoped to the folder, where earlier versions listed by string prefix and a prefix of logs also returned every blob under logs-failing/; a fix for pretty-printed .json arrays, which were misdetected as line-delimited JSON and failed to read; and a fix for folder names whose first character formed a Python escape sequence. Existing data sources need the template re-selected before any of this takes effect; see Upgrading.

How It Works

  • Prefixes are schemas, blobs are tables. The template lists the container under the configured prefix. Each virtual directory becomes a schema (.-rooted, e.g. .\csv\customer_1001); each blob becomes a table.
  • Columns are inferred on read. Blobs are read into data frames and columns/types inferred (Parquet and Avro carry explicit types; CSV and Excel are inferred).
  • One ingestion pass catalogs the whole scope at the data-source level.
  • Scope with container + prefix and control depth with include_sub_folders.

Authentication

The template supports three authentication styles from a single template. Which one is used is determined by which credential parameters you fill in.

Method Parameters to fill When to use
Connection string Connection String Simplest setup; the connection string embeds the account and its key
SAS token Account URL + SAS Token Least-privilege, time-bounded access scoped to a container or prefix — the recommended production pattern
Account key Account URL + Account Key Full-account access via the storage account key, without embedding it in a connection string

The adapter picks the method by inspecting which parameters are present: a connection string is used directly; otherwise it builds a client from the Account URL plus whichever credential (SAS token or account key) you supplied.

Tip: Prefer a SAS token in production. Scope it to read + list on just the container (or a specific prefix) and give it an expiry, so Validatar's access is bounded in both scope and time. A SAS with only read/list permission is all this template needs.

Warning: Connection strings and account keys grant broad access to the storage account. Treat them as high-value secrets, and rotate them on the account's key-rotation schedule.

Connection Parameters

Parameter Reference key Type Required Description
Container container String Yes Blob container name.
Prefix prefix String No Blob-name prefix to scope the catalog.
Account URL account_url String No https://<account>.blob.core.windows.net (for SAS or account-key auth).
SAS Token sas_token Secret No SAS token (used with Account URL).
Connection String connection_string Secret No Full connection string (alternative to Account URL + credential).
Account Key account_key Secret No Storage account key (used with Account URL).
Include Sub Folders include_sub_folders Boolean Yes Descend into nested blob prefixes. Default: true.

Required Python Packages

These must be present on the runtime that executes the source (the Data Agent, or a cloud runtime that carries the SDK).

Required

  • azure-storage-blob — the Azure Blob client (required for this template)
  • pandas, numpy — the core data-frame and numeric layer
  • pyarrow — Parquet / ORC / Feather
  • fastavro — Avro
  • openpyxl — Excel (.xlsx / .xlsm)

Optional (features degrade gracefully if absent)

  • fitz (PyMuPDF) — PDF statistics
  • Pillow (PIL) — image statistics
  • charset_normalizer — text-encoding detection
  • lxml — XML reading

Note: The library degrades per blob, not per run — a missing optional package only affects the blobs that need it; the rest of the catalog continues.

Capabilities

Supported File Types

Catalog file type Extensions Read as
Delimited .csv, .tsv, .tab Tabular (delimiter sniffed)
Fixed Width / Text .txt, .flat, .dat Tabular
Excel .xlsx, .xlsm, .xls Tabular
Columnar .parquet, .orc, .feather Tabular (explicit types)
Row Binary .avro Tabular (explicit types)
Semi-Structured .json, .jsonl, .ndjson, .xml, .geojson Tabular (flattened / normalized)
Document .pdf, .md, .markdown, .rst Content statistics
Image .png, .jpg, .jpeg, .gif, .bmp, .tif, .tiff, .webp Content statistics

Note: Line-delimited JSON is detected even when a blob has a .json extension, and flattened into columns.

Macros

The template ships with 28 macros: 21 generic file macros and seven for nested JSON. Because every macro goes through the shared reader, the same test works against any blob and any supported format. Parameters are metadata-linked (Folder → Schema, File → Table, Column) so they render as catalog-driven dropdowns.

A. Single-blob data

Macro Returns
File - Read Data (any type) The whole blob as a dataset
File - First N Rows The first N rows (default 100)
File - Row Count Row count as a single value
File - Schema (columns + inferred types) Column list with inferred type and null %
File - Profile One Column Profile statistics for one column
File - Row Count Grouped by Column Group-by counts
File - Aggregate a Column sum / min / max / mean / count / nunique

B. Pattern & union (multi-blob)

Macro Returns
Folder - Files Matching a Pattern Blobs whose name matches a glob (e.g. account_*.avro)
Folder - Union Files Matching a Pattern Every matching blob read and unioned (schema-aligned, source-tagged)
Folder - Row Count Across Matching Files Per-blob and total row counts
Folder - Schema Consistency Across Files Each blob's columns and whether they match the first (partitioned extracts)

C. Prefix & blob information

Macro Returns
Folder - File Inventory Every blob with size, detected type, and modified date
Folder - Summary One-row rollup: blob count, total size, distinct types, date range
File - Full Metadata One blob's size, modified date, MD5, and (if tabular) row/column counts

D. Content statistics (including non-tabular)

Macro Returns
File - Content Stats (any type) Type-aware stats for any blob
PDF - Statistics Pages, words, embedded images, title/author, encryption flag
Markdown - Statistics Headings, links, images, code blocks, table rows
Text - Statistics Lines, words, encoding, blank lines, longest line
Image - Statistics Width/height, megapixels, format, mode, alpha
JSON - Statistics Record count, nesting depth, top-level keys
XML - Statistics Root tag, element count, distinct tags, depth

Tip: Folder - Union Files Matching a Pattern unifies partitioned blob layouts — point it at a prefix of *.avro or *.parquet blobs and it returns one dataset with a _source_file column, so you can validate the combined set and still trace any row to its blob.

E. Nested JSON

The macros above deal with JSON as a flat table. These seven deal with it as a tree.

The distinction matters more than it sounds. The standard reader normalizes nested objects into dotted columns, so customer.address.city works as a column. A nested array cannot become a column, so it stays in the cell as a raw list:

order_id     customer.address.city   items
ORD-00001    Austin                  [{'sku': 'WID-001', 'quantity': 3, ...}, {...}]

You cannot count that, group it, join on it, or assert anything about it. For order payloads, event streams and API captures landed as blobs, the interesting data is inside a repeated element, and that is where testing usually stops.

Macro Returns
JSON - Discover Nested Structure One row per path in the document tree: depth, JSON type(s), how many records contain it, array cardinality, and a sample value
JSON - Flatten to Table The whole document as a table, with configurable array handling (join / explode / count)
JSON - Explode an Array Path One row per element of a chosen array, optionally carrying the parent record's fields
JSON - Extract Specific Paths A chosen set of dotted paths as columns; [] descends into every array element
JSON - Array Cardinality per Record Element count per record for a chosen array path, keyed by a business identifier
JSON - Type Conflicts Across Records Paths whose JSON type varies between records
JSON - Key Coverage / Sparse Fields Paths present in fewer than N% of records

Start with Discover Nested Structure. It is the map. Run it once against a sample blob and it tells you every path, how deep it is, and which ones are arrays, so you know what to feed the other six.

Then reach for Explode an Array Path rather than Flatten to Table. Both produce tabular output, but flattening with arrays=explode expands every array at once, and sibling arrays multiply: a payload with 3 line items and 2 contacts yields 6 rows, and any sum over it is wrong by a factor of 2. Exploding one path does not have that problem. Flatten's default is therefore join, which keeps one row per record and encodes arrays as text.

Note: explode is the right choice when you genuinely want the cross-product, or when only one array is present. The parameter exists so the behaviour is a decision rather than a surprise.

See Macros for how macros are used when building tests. The six worked examples in the AWS S3 - Nested JSON Sample Tests pack run unchanged against a blob data source once the pack's data source is mapped to yours; the storage layer is the only difference.

Metadata Ingestion

Ingestion runs once at the data-source level. It lists the container under the prefix, registers each blob prefix as a schema and each blob as a table, and populates the File Info catalog custom fields — including per-prefix rollups (blob count, total size, distinct file types, newest/oldest modified). Column-level metadata is discovered on demand by the profile set.

See Metadata Ingestion Scripts — Python Templates.

Profiling

The template ships one profile: File Profile (columns + table metrics, any file type) — the same type-safe profiler as the Windows Directory template. Run it as a profile set against a prefix to profile every blob in it. Following the principle that numeric/statistical values are profiles and descriptors are custom fields, it produces:

  • Column-level profiles: null/blank/distinct counts and percentages, length statistics, most-common value, numeric statistics (min/max/mean/median/standard deviation, zero/negative counts), and date range.
  • Table-level file metrics: file size (bytes), file age (days), row count, column count, and type-specific metrics (PDF page count, content word/line/record count, image width/height).

See Profiling Configuration — Python Templates.

Catalog Custom Fields (required dependency)

The ingestion script emits File Info catalog custom fields to describe blobs and prefixes — the text/tag/date descriptors and prefix rollups that don't fit the numeric profile model. Import the File Info - Catalog Custom Fields set before running ingestion; Validatar silently drops custom-field columns with no matching definition.

Schema level (prefix rollups): Contains Files, Schema Object Represents, Full Path, File Count, Total Size (bytes), Distinct File Types, Readable File Count, Newest Modified, Oldest Modified

Table level (per blob): File Type, File Name, Sheet Name, File Size, File Total Bytes, File Extension, Date Modified, File Logical Table

Warning: Catalog custom fields import through the UI only (Settings > Catalog Custom Fields > Import) and require the GlobalCustomMetadataAdmin role.

Installation

  1. Import the custom fields first — the File Info - Catalog Custom Fields set (Settings > Catalog Custom Fields > Import).
  2. Import the template — Settings > Data Source Templates > Import, select the Azure Blob Storage template file.
  3. Create a data source and set Container and Prefix, then supply credentials for one auth method (connection string; or Account URL + SAS token; or Account URL + account key).
  4. Assign a runtime with azure-storage-blob and network access to the storage account.
  5. Run metadata ingestion, then run the File Profile profile set.

Upgrading

Importing a newer version of the template with Overwrite existing updates the template itself and nothing else. Each data source built from a Script template keeps its own copy of the template's pre-execution script, taken at the moment the template was selected. A data source created on 1.0.0 keeps running the 1.0.0 code, without the JSON macros and with the old folder listing, until you refresh that copy.

To pick up a new version on an existing data source:

  1. Import the new template file (Settings > Data Source Templates > Import, with overwrite).
  2. Open each data source built from the template and edit its connection.
  3. Re-select Azure Blob Storage as the template, confirm the parameters are still filled, and save.
  4. Run a folder macro (for example Folder - File Inventory on a prefix that has a similarly named neighbour) to confirm the new behaviour.

New data sources created after the import pick up the current version with no extra step.

What You Still Need to Configure

  • Auth method — connection string, SAS token, or account key. Prefer a scoped, time-bounded SAS token in production.
  • Container access — read + list on the container (or a prefix, for a scoped SAS).
  • Runtime placement — a Data Agent or cloud runtime that carries azure-storage-blob.
  • Scope — set prefix to avoid cataloging an entire large container, and include_sub_folders to control depth.

Troubleshooting

Symptom Cause Fix
Authentication / authorization error Wrong or expired credential; SAS lacks read/list Re-issue the SAS with read + list; verify the connection string or account key
Empty catalog Wrong container/prefix Verify the container name and prefix
No auth method resolves None of the credential combinations are complete Provide a connection string, or Account URL + SAS token, or Account URL + account key
A single blob errors Missing optional package for that format Install the package (fastavro, openpyxl, pyarrow, fitz, Pillow)
Custom-field values missing File Info custom fields not imported before ingestion Import the File Info set, then re-run ingestion
A folder macro or test returns blobs from a neighbouring prefix (logs also lists logs-failing/…) Versions before 1.1.0 listed by string prefix rather than by folder Fixed in template 1.1.0; import it and re-select the template on the data source (see Upgrading)
A folder's blobs report not found even though they exist Folder name begins with a character that forms a Python escape (reference, temp, feeds, nightly, …) Fixed in template 1.1.0; upgrade the template
A pretty-printed .json array fails to read with Expecting value: line 1 column 2 Versions before 1.1.0 misdetected indented arrays as line-delimited JSON Fixed in template 1.1.0; upgrade the template
The template was upgraded but the old behaviour persists The data source still holds its own copy of the previous pre-execution script Re-select the template on the data source (see Upgrading)
Columnar read failed … Repetition level histogram size mismatch The Parquet blob was written by a newer pyarrow than the runtime has Upgrade pyarrow on the Data Agent; the reader is shared with the AWS S3 template, whose article explains the version skew

Sample Test Pack

Azure Blob Storage - Unstructured File Sample Tests (marketplace) ships 190 worked examples against this template: text, log and markdown files, PDFs and images, each with its adjustable values in a block at the top of the script. See Azure Blob Storage Unstructured File Sample Tests. The files those tests read are a separate download, Unstructured File Sample Fixture (S3 and Azure Blob).

Related Articles