Read a report¶
read_report reads both formats and returns a
Report. This guide shows the inputs it accepts, how to
label input that has no file name, and how to handle failures.
From a path¶
from dpmxbrl import read_report
package = read_report("report.zip") # xBRL-CSV report package
instance = read_report("instance.xbrl") # XBRL-XML instance
print(package.format, len(package.facts))
print(instance.format, len(instance.facts))
A ZIP (by content, whatever its suffix), a directory and a .json path are
read as xBRL-CSV; anything else as XBRL-XML.
detect_format tells you which without reading the
report:
from dpmxbrl import detect_format
print(detect_format("report.zip"), detect_format("instance.xbrl"))
An unpacked package works too: pass its directory, or the path of its
report.json.
from zipfile import ZipFile
ZipFile("report.zip").extractall("unpacked")
print(len(read_report("unpacked/report").facts))
print(len(read_report("unpacked/report/reports/report.json").facts))
From bytes¶
Uploads and message queues hand over bytes. Pass them directly, with
name= to label messages and locations the way the file would be:
from pathlib import Path
data = Path("report.zip").read_bytes()
report = read_report(data, name="SYNTHETICDPMXL000002.CON_upload.zip")
print(report.filing[0].examples)
The name matters for the EBA file-naming rule, which checks the file name.
Without name=, ZIP bytes are labelled <bytes> and XML bytes
instance.xbrl.
Use the facts¶
Facts are frozen dataclasses. Their mappings (dimensions,
typed_dimensions, keys) are read-only and may be shared between facts;
copy one with dict() to change it.
fact = instance.facts[2]
print(fact.concept, fact.value)
print(fact.dimensions, fact.typed_dimensions)
labels = dict(fact.dimensions)
labels["note"] = "copied"
Facts and reports pickle, copy and convert to plain data:
{"value": "86911000", "concept": null, "datapoint": "dp32026", "template": "c_26.00", "dimensions": {}, "typed_dimensions": {}, "keys": {}, "unit": null, "decimals": null, "location": "reports/c_26.00.csv:4"}
To group facts by template, use Report.facts_for_template with the
filing-indicator code. It compares codes case-insensitively; see
table codes and filing indicators for
how table files map to filing indicators.
Handle failures¶
Every input that cannot be read raises ReportError,
a ValueError, naming the file and, where known, the line:
from dpmxbrl import ReportError
for source in ("missing.zip", b"<xbrli:xbrl"):
try:
read_report(source)
except ReportError as error:
print(error)
missing.zip: no such file or directory
instance.xbrl:1:0: malformed XML (unclosed token: line 1, column 0)
Catch ReportError alone: damaged ZIPs, unsafe entries, malformed JSON,
CSV and XML, and inputs over a limit all raise it.
Its problems list every problem found. Broken xBRL-CSV tables, with rows
of the wrong length, bad quoting or bad headers, are all read, up to 100
problems, before the error is raised with the first as its message:
import io
from zipfile import ZipFile
buffer = io.BytesIO()
with ZipFile("report.zip") as source, ZipFile(buffer, "w") as target:
for name in source.namelist():
data = source.read(name)
if name.endswith("c_28.00.csv"):
data += b"dp5488015,1,CP0003,extra\r\ndp5488016\r\n"
if name.endswith("c_26.00.csv"):
data += b'dp32027,"12"3\r\n'
target.writestr(name, data)
try:
read_report(buffer.getvalue(), name="report.zip")
except ReportError as error:
print(error)
for problem in error.problems:
print(" ", problem)
reports/c_26.00.csv:6: ',' expected after '"' (and 2 more problems)
reports/c_26.00.csv:6: ',' expected after '"'
reports/c_28.00.csv:6: expected 3 cells, found 4
reports/c_28.00.csv:7: expected 3 cells, found 1
Read the warnings¶
Problems that do not stop the read are kept in Report.warnings, in a
deterministic order, and repeated findings are aggregated with a count and
the first location:
reports/report.json: 1 extended metadata document could not be resolved offline (http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json); tables without resolved metadata use the EBA datapoint/factValue convention
Warnings describe how the file was read; the EBA filing rules the report
breaks are in Report.filing instead. See
check EBA filing rules.
Read several reports at once¶
Readers share no state, so threads can read several reports at once. Large packages are mostly CPU-bound Python work, so processes scale better for many big reports:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as pool:
reports = list(pool.map(read_report, ["report.zip", "instance.xbrl"]))
print([len(item.facts) for item in reports])
While a reader builds facts it pauses Python's cyclic garbage collector for the whole process and restores it afterwards; see performance.