majorsilence-reporting
Render RDL reports to PDF, Excel, CSV, and more from Python — via subprocess, an AOT binary, or in-process ctypes FFI. No runtime dependencies beyond the standard library.
Installation
Install from PyPI (Python 3.10+ required):
# PyPI pip install majorsilence-reporting # Or editable from source pip install -e .
No runtime dependencies — uses only the Python standard library (subprocess, ctypes, tempfile).
Integration modes
Three classes are available depending on how you deploy the reporting engine:
| Class | Mechanism | Requirement |
|---|---|---|
| Report | Spawns dotnet RdlCmd.dll as a child process. | .NET runtime installed on the server. |
| ReportAot | Spawns a self-contained AOT-compiled RdlCmd binary. | AOT binary deployed alongside the app. No .NET runtime needed. |
| report_native.Report | Loads librdlnative.so in-process via ctypes. | Native library + sibling .so files deployed. Call load_library() once at startup. |
Report — subprocess (dotnet)
from majorsilence_reporting import Report rpt = Report( report_path = '/path/to/report.rdl', rdl_cmd_path = '/path/to/RdlCmd.dll', ) rpt.set_connection_string('Data Source=/path/to/data.db') rpt.set_parameter('StartDate', '2026-01-01') # Save to a file rpt.export('pdf', '/tmp/output.pdf') # Or get bytes in memory (e.g. for a Flask response) data = rpt.export_to_memory('pdf') return Response(data, mimetype='application/pdf')
ReportAot — AOT binary
Same API as Report but points at a self-contained binary instead of a DLL. No .NET runtime required on the server.
from majorsilence_reporting import ReportAot rpt = ReportAot( report_path = '/path/to/report.rdl', rdl_cmd_path = '/path/to/RdlCmd', # self-contained binary, no .dll extension ) rpt.set_connection_string('Data Source=/path/to/data.db') data = rpt.export_to_memory('xlsx') # ReportAot also supports xlsx_table, tifb, and mht data = rpt.export_to_memory('xlsx_table')
report_native — in-process FFI
The native mode loads librdlnative.so directly into the Python process via ctypes. No child process is spawned, making it the lowest-latency option. It also supports true in-memory rendering — the report bytes are returned directly from the native buffer without writing a temp file.
from majorsilence_reporting.report_native import load_library, Report # Call once at process startup. Loads librdlnative.so and all sibling libraries. lib = load_library('/path/to/librdlnative.so') rpt = Report(lib, report_path='/path/to/report.rdl') rpt.set_connection_string('Data Source=/path/to/data.db') # Returns bytes directly from the native buffer — no temp file written data = rpt.export_to_memory('pdf') # Pass to Django / FastAPI / Flask as a binary response return Response(data, media_type='application/pdf')
load_library() sets RDLNATIVE_LIB_DIR and pre-loads all sibling .so / .dylib files with RTLD_GLOBAL so .NET P/Invoke resolves correctly. Call it once per process before creating any Report instances.
Core API
All three classes share the same interface:
| Method | Description |
|---|---|
| set_connection_string(cs) | Set the database connection string used by the report. Call before export. |
| set_parameter(name, value) | Set a named report parameter. Both arguments are strings. Call as many times as needed. |
| export(format, path) | Render the report and write the output to path on disk. |
| export_to_memory(format) | Render and return the output as bytes (binary formats) or str (text formats). For report_native.Report, always returns bytes. |
In-memory datasets
report_native.Report supports supplying data entirely in memory via add_data() — no database connection required. Pass a dataset name and a list of dicts where every value is a string.
from majorsilence_reporting.report_native import load_library, Report lib = load_library('/path/to/librdlnative.so') rpt = Report(lib, report_path='/path/to/report.rdl') rpt.add_data('SalesData', [ {'Product': 'PDF Library Pro', 'Revenue': '1200.00', 'Units': '3'}, {'Product': 'Report Designer', 'Revenue': '250.00', 'Units': '1'}, {'Product': 'Support (12 mo.)', 'Revenue': '500.00', 'Units': '1'}, ]) data = rpt.export_to_memory('pdf')
add_data() sets SkipDatabaseSchemaValidation automatically. The dataset name must match a dataset defined in the .rdl file. All field values must be strings — convert numbers and dates before passing.
Export formats
| Format string | Output | Available in |
|---|---|---|
pdf | All modes | |
csv | CSV text | All modes |
xlsx | Excel workbook | All modes |
xml | XML data | All modes |
rtf | Rich Text Format | All modes |
tif | TIFF image (colour) | All modes |
html | HTML | All modes |
xlsx_table | Excel (table style) | ReportAot + native only |
tifb | TIFF image (black & white) | ReportAot + native only |
mht | MHTML web archive | ReportAot + native only |
pdf. All methods are synchronous — wrap in asyncio.get_event_loop().run_in_executor(None, ...) if you need to call from async code without blocking.