pylatex-report 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pylatex_report-0.1.0/LICENSE +21 -0
- pylatex_report-0.1.0/PKG-INFO +391 -0
- pylatex_report-0.1.0/README.md +372 -0
- pylatex_report-0.1.0/pylatex_report/__init__.py +6 -0
- pylatex_report-0.1.0/pylatex_report/_tables.py +199 -0
- pylatex_report-0.1.0/pylatex_report/_validation.py +213 -0
- pylatex_report-0.1.0/pylatex_report/report.py +645 -0
- pylatex_report-0.1.0/pylatex_report.egg-info/PKG-INFO +391 -0
- pylatex_report-0.1.0/pylatex_report.egg-info/SOURCES.txt +22 -0
- pylatex_report-0.1.0/pylatex_report.egg-info/dependency_links.txt +1 -0
- pylatex_report-0.1.0/pylatex_report.egg-info/requires.txt +6 -0
- pylatex_report-0.1.0/pylatex_report.egg-info/top_level.txt +1 -0
- pylatex_report-0.1.0/pyproject.toml +47 -0
- pylatex_report-0.1.0/setup.cfg +4 -0
- pylatex_report-0.1.0/tests/test_aggregation.py +272 -0
- pylatex_report-0.1.0/tests/test_compilation.py +198 -0
- pylatex_report-0.1.0/tests/test_examples.py +57 -0
- pylatex_report-0.1.0/tests/test_fund_input.py +235 -0
- pylatex_report-0.1.0/tests/test_mosaics.py +75 -0
- pylatex_report-0.1.0/tests/test_readme.py +34 -0
- pylatex_report-0.1.0/tests/test_styles.py +116 -0
- pylatex_report-0.1.0/tests/test_tables.py +167 -0
- pylatex_report-0.1.0/tests/test_tikz.py +340 -0
- pylatex_report-0.1.0/tests/test_validation.py +224 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harold Guéneau
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pylatex-report
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Compose technical PDF reports from Python with PyLaTeX.
|
|
5
|
+
Author: Harold Guéneau
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/HaroldGueneau/pylatex-report
|
|
8
|
+
Project-URL: Issues, https://github.com/HaroldGueneau/pylatex-report/issues
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: matplotlib<4,>=3.8
|
|
13
|
+
Requires-Dist: numpy<3,>=1.26
|
|
14
|
+
Requires-Dist: pandas<4,>=2.2
|
|
15
|
+
Requires-Dist: polars<2,>=1
|
|
16
|
+
Requires-Dist: pydantic<3,>=2.8
|
|
17
|
+
Requires-Dist: pylatex<2,>=1.4.2
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
# PyLaTeX Report
|
|
23
|
+
|
|
24
|
+
Build technical PDF reports from Python using pandas or Polars DataFrames, Matplotlib figures, and native TikZ / PGFPlots graphs. The project wraps PyLaTeX with helpers for repeated experiments, figure mosaics, and reusable report styles.
|
|
25
|
+
|
|
26
|
+
## Getting started
|
|
27
|
+
|
|
28
|
+
You need Python 3.10 or newer and a TeX installation to generate PDFs.
|
|
29
|
+
|
|
30
|
+
### Install the Python library
|
|
31
|
+
|
|
32
|
+
Once the first PyPI release is published, install into your Python environment with pip:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m pip install pylatex-report
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or add it to an existing uv project:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv add pylatex-report
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or install the current GitHub version (requires Git):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m pip install "git+https://github.com/HaroldGueneau/pylatex-report.git"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With uv, use `uv add "git+https://github.com/HaroldGueneau/pylatex-report.git"` instead. The installed distribution is named `pylatex-report`; its Python import is `from pylatex_report import LatexReport`. Python imports use underscores instead of hyphens.
|
|
51
|
+
|
|
52
|
+
### Install TeX separately
|
|
53
|
+
|
|
54
|
+
pip and uv install the Python dependencies, but do not install TeX. You need `pdflatex` on your PATH and the LaTeX packages used by the report; TikZ graphs also need `tikz` and `pgfplots`. If `latexmk` is available, the library uses it to manage compilation passes.
|
|
55
|
+
|
|
56
|
+
On macOS, install [MacTeX](https://tug.org/mactex/). On Ubuntu, the following packages match the [test workflow](https://github.com/HaroldGueneau/pylatex-report/blob/main/.github/workflows/tests.yml):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
sudo apt-get update
|
|
60
|
+
sudo apt-get install -y latexmk lmodern texlive-latex-extra texlive-pictures
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Generate a first report
|
|
64
|
+
|
|
65
|
+
Save this as `quickstart.py` in any writable directory. It uses the installed library and needs no files from this repository.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import pandas as pd
|
|
69
|
+
from matplotlib.figure import Figure
|
|
70
|
+
|
|
71
|
+
from pylatex_report import LatexReport
|
|
72
|
+
|
|
73
|
+
scores = pd.DataFrame({"Model": ["Baseline", "Candidate"], "Score": [0.81, 0.86]})
|
|
74
|
+
chart = Figure(figsize=(5, 2.5), layout="constrained")
|
|
75
|
+
ax = chart.subplots()
|
|
76
|
+
ax.bar(scores["Model"], scores["Score"])
|
|
77
|
+
ax.set_ylabel("Score")
|
|
78
|
+
|
|
79
|
+
report = LatexReport(
|
|
80
|
+
"quickstart",
|
|
81
|
+
"Model comparison",
|
|
82
|
+
"Research team",
|
|
83
|
+
table_of_contents=False,
|
|
84
|
+
separate_cover=False,
|
|
85
|
+
)
|
|
86
|
+
report.add_section("Results")
|
|
87
|
+
report.add_text("Evaluation scores on the held-out dataset.")
|
|
88
|
+
report.add_table(scores, "Model scores", precision=2)
|
|
89
|
+
report.add_plot(chart, "Scores by model")
|
|
90
|
+
pdf = report.generate_report("reports")
|
|
91
|
+
print(pdf)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Run it with `python quickstart.py`, or `uv run python quickstart.py` from your uv project. It writes `reports/quickstart.pdf` beneath the current directory.
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
The [examples directory](https://github.com/HaroldGueneau/pylatex-report/tree/main/examples) contains two runnable reports. Their scripts and data live in the repository and are not installed with the library.
|
|
99
|
+
|
|
100
|
+
| Example | What it demonstrates | Sample output |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| [ML experiment](https://github.com/HaroldGueneau/pylatex-report/blob/main/examples/ml_experiment.py) | Compare three classifiers across five seeds. Reuse pandas summaries for a mean ± standard deviation table, TikZ curves with error bars, and grouped bars. Keep per-seed Matplotlib diagnostics in a paginated mosaic. | [PDF](https://github.com/HaroldGueneau/pylatex-report/blob/main/output/pdf/ml_experiment.pdf) |
|
|
103
|
+
| [Fund risk report](https://github.com/HaroldGueneau/pylatex-report/blob/main/examples/fund_risk.py) | Read daily returns from CSV. Use Polars for performance and risk tables, a TikZ histogram of losses, and an empirical distribution in steps. Include branded pages and Matplotlib diagnostics. | [PDF](https://github.com/HaroldGueneau/pylatex-report/blob/main/output/pdf/fund_risk.pdf) |
|
|
104
|
+
|
|
105
|
+
To run them, clone the repository and install its locked development and example dependencies with uv:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git clone https://github.com/HaroldGueneau/pylatex-report.git
|
|
109
|
+
cd pylatex-report
|
|
110
|
+
uv sync --locked
|
|
111
|
+
|
|
112
|
+
uv run --locked python -B -m examples.ml_experiment --output-dir reports
|
|
113
|
+
uv run --locked python -B -m examples.fund_risk \
|
|
114
|
+
--input examples/data/fund_returns.csv \
|
|
115
|
+
--as-of 2025-12-30 \
|
|
116
|
+
--output-dir reports
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The linked sample PDFs were generated with these commands, using `--output-dir output/pdf`. The fund example writes `reports/fund_risk.pdf` with the commands above. Its [bundled CSV](https://github.com/HaroldGueneau/pylatex-report/blob/main/examples/data/fund_returns.csv) contains simulated returns through December 31, 2025. The command above includes observations through December 30. Omit `--input` to use the bundled CSV or omit `--as-of` to report through the latest observation.
|
|
120
|
+
|
|
121
|
+
To use your own CSV, provide `date,return` columns with one row per date. The script sorts the rows by date. Dates use `YYYY-MM-DD`. Returns are daily simple returns in decimal form, so `0.0125` means 1.25%. Each return must be finite and greater than `-1`, and at least two observations must fall on or before the cutoff. When the cutoff falls between observations, the report shows the requested date and the period through the last included observation.
|
|
122
|
+
|
|
123
|
+
### Run the fund report with Docker
|
|
124
|
+
|
|
125
|
+
From the project root, build the image once and create the output directory on the host.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
docker build -f examples/Dockerfile.fund-risk -t fund-risk .
|
|
129
|
+
mkdir -p reports
|
|
130
|
+
|
|
131
|
+
docker run --rm --network none \
|
|
132
|
+
--user "$(id -u):$(id -g)" \
|
|
133
|
+
--mount "type=bind,source=$PWD/examples/data/fund_returns.csv,target=/returns.csv,readonly" \
|
|
134
|
+
--mount "type=bind,source=$PWD/reports,target=/output" \
|
|
135
|
+
fund-risk --input /returns.csv --as-of 2025-12-30 --output-dir /output
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Docker mounts the CSV read-only. The report writes `reports/fund_risk.pdf` as the sole output file. For a scheduled job, reuse the image and pass the current CSV path and cutoff date. Runs using the same output directory replace the previous PDF.
|
|
139
|
+
|
|
140
|
+
## Usage guide
|
|
141
|
+
|
|
142
|
+
### Tables and repeated runs
|
|
143
|
+
|
|
144
|
+
- `add_table(df, title, precision=2)` accepts pandas and Polars DataFrames. Use `include_index` to control whether row labels are shown and `longtable=True` for a multipage table with repeated headers.
|
|
145
|
+
- `add_aggregate_table(runs, "Scores by model", keys="Model", metrics=["Score"])` renders mean ± standard deviation. Each run is a pandas or Polars DataFrame with the same unique model keys. Use `highlight="max"` or `highlight="min"` to bold the highest or lowest mean in each row.
|
|
146
|
+
- `LatexReport.aggregate_tables(runs, keys="Model", metrics=["Score"])` returns the means and standard deviations as two pandas DataFrames. The default `ddof=1` computes sample standard deviations. Missing observations are skipped per cell. Without explicit keys, rows are aligned by pandas index or Polars row position.
|
|
147
|
+
|
|
148
|
+
Both table methods accept `formats` and `column_headers`, keyed by the original data column labels.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
report.add_table(
|
|
152
|
+
metrics,
|
|
153
|
+
"Fund performance",
|
|
154
|
+
formats={"Annual return": ".2%", "Sharpe": ".2f"},
|
|
155
|
+
column_headers={"Annual return": "Annual\nreturn"},
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Formats use Python's format specifiers. For example, `.2%` displays `0.0744` as `7.44%`. Columns without a format still use `precision`. In an aggregate table, the same format is applied to the mean and standard deviation, and highlighting uses the unrounded means. Use `\n` in a column label or its display header to start a new line. The [fund report](https://github.com/HaroldGueneau/pylatex-report/blob/main/examples/fund_risk.py) and [ML experiment](https://github.com/HaroldGueneau/pylatex-report/blob/main/examples/ml_experiment.py) show both options in use.
|
|
160
|
+
|
|
161
|
+
### Figure mosaics
|
|
162
|
+
|
|
163
|
+
`add_plot` accepts one Matplotlib `Figure` or an iterable of figures. For example, the following call lays out up to six panels per page.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
report.add_plot(figures, "Diagnostics", columns=3, rows_per_page=2, panel_labels=True)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
When the mosaic spans multiple pages, each group gets its own page and the caption shows its position, such as `(1/2)`. Panel labels continue across pages and start again at `(a)` for each new call. Figures are captured when you call `add_plot`.
|
|
170
|
+
|
|
171
|
+
### TikZ / PGFPlots graphs
|
|
172
|
+
|
|
173
|
+
`add_tikz_plot` accepts one PyLaTeX `Plot` or an iterable of plots and draws them on a single axis. It uses the existing PyLaTeX dependency and requires the `tikz` and `pgfplots` packages in your TeX installation.
|
|
174
|
+
|
|
175
|
+
The graph type is selected with standard PGFPlots options:
|
|
176
|
+
|
|
177
|
+
| Graph | `Plot(options=...)` | `axis_options` / data |
|
|
178
|
+
| --- | --- | --- |
|
|
179
|
+
| Lines / smooth curves | `mark=none` / `smooth,mark=none` | Ordered `(x, y)` pairs, or a `func` expression |
|
|
180
|
+
| Scatter | `only marks` | `(x, y)` pairs |
|
|
181
|
+
| Vertical / horizontal bars | Optional colors | `ybar` / `xbar`; horizontal bars use `(value, position)` |
|
|
182
|
+
| Grouped bars | One plot per series | `ybar` or `xbar`, shared category positions |
|
|
183
|
+
| Stacked bars | One plot per series | `ybar stacked` or `xbar stacked` |
|
|
184
|
+
| Steps | `const plot,mark=none` | Sort by x; each value extends to the next x |
|
|
185
|
+
| Stems | `ycomb` or `xcomb` | Numeric pairs |
|
|
186
|
+
| Symmetric error bars | `error bars/.cd,y dir=both,y explicit` | `error_bar=[(x_error, y_error), ...]`, one pair per point |
|
|
187
|
+
| Logarithmic axes | Any compatible plot style | `xmode=log` and/or `ymode=log`; positive values on each log axis |
|
|
188
|
+
| Histogram | `ybar interval,fill=blue!30` | Compute counts in Python; pairs of left bin edge and count, followed by `(last_edge, 0)` |
|
|
189
|
+
|
|
190
|
+
Coordinates must be numeric. Represent categories with positions such as `1, 2, 3` and set `xtick` / `xticklabels` (or their y equivalents). This helper covers 2D plots; it has no dedicated API for 3D, heatmaps, pie charts, or box plots. See the [PGFPlots plot types](https://tikz.dev/pgfplots/reference-2dplots) and [error bars](https://tikz.dev/pgfplots/reference-errorbars) documentation for option details.
|
|
191
|
+
|
|
192
|
+
#### DataFrame table, model curve, and scatter
|
|
193
|
+
|
|
194
|
+
This example uses illustrative load-test data from the same DataFrame for a table and a graph, alongside a simple latency model:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
import pandas as pd
|
|
198
|
+
from pylatex import Plot
|
|
199
|
+
|
|
200
|
+
from pylatex_report import LatexReport
|
|
201
|
+
|
|
202
|
+
measurements = pd.DataFrame(
|
|
203
|
+
{
|
|
204
|
+
"Concurrent users": [10, 25, 50, 75, 100],
|
|
205
|
+
"Latency (ms)": [31.0, 39.5, 53.0, 67.5, 86.0],
|
|
206
|
+
"Stddev (ms)": [1.5, 2.0, 3.2, 4.1, 5.0],
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
report = LatexReport(
|
|
210
|
+
"load_test",
|
|
211
|
+
"API latency",
|
|
212
|
+
"Engineering team",
|
|
213
|
+
table_of_contents=False,
|
|
214
|
+
separate_cover=False,
|
|
215
|
+
)
|
|
216
|
+
report.add_section("Load test")
|
|
217
|
+
report.add_text("Illustrative load-test measurements.")
|
|
218
|
+
report.add_table(measurements, "Latency by concurrent user count", precision=1)
|
|
219
|
+
report.add_tikz_plot(
|
|
220
|
+
[
|
|
221
|
+
Plot(name="Model", func="25 + 0.5*x"),
|
|
222
|
+
Plot(
|
|
223
|
+
name="Observations",
|
|
224
|
+
coordinates=measurements[["Concurrent users", "Latency (ms)"]].itertuples(
|
|
225
|
+
index=False, name=None
|
|
226
|
+
),
|
|
227
|
+
options="only marks",
|
|
228
|
+
),
|
|
229
|
+
],
|
|
230
|
+
"Observed latency and model",
|
|
231
|
+
axis_options=(
|
|
232
|
+
r"width=0.85\linewidth,height=5.5cm,grid=major,"
|
|
233
|
+
"xlabel={Concurrent users},ylabel={Latency (ms)},"
|
|
234
|
+
"domain=0:100,legend pos=north west"
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
print(report.generate_report("reports"))
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### Polars table and grouped or stacked bars
|
|
241
|
+
|
|
242
|
+
The following snippets reuse `report` and `Plot` above. Insert them before `generate_report` to include them in the same PDF. Here, each row describes a processing stage; both series use the same numeric positions.
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
import polars as pl
|
|
246
|
+
|
|
247
|
+
jobs = pl.DataFrame(
|
|
248
|
+
{
|
|
249
|
+
"Position": [1, 2, 3],
|
|
250
|
+
"Stage": ["Validate", "Predict", "Export"],
|
|
251
|
+
"First attempt": [940, 910, 970],
|
|
252
|
+
"Retried": [60, 90, 30],
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
report.add_table(jobs.drop("Position"), "Completed jobs by stage", precision=0)
|
|
256
|
+
report.add_tikz_plot(
|
|
257
|
+
[
|
|
258
|
+
Plot(name=column, coordinates=jobs.select("Position", column).iter_rows())
|
|
259
|
+
for column in ("First attempt", "Retried")
|
|
260
|
+
],
|
|
261
|
+
"Completed jobs: first attempt and retries",
|
|
262
|
+
axis_options=(
|
|
263
|
+
r"width=0.85\linewidth,height=5cm,ybar,ymin=0,"
|
|
264
|
+
"bar width=12pt,enlarge x limits=0.25,"
|
|
265
|
+
"xtick={1,2,3},xticklabels={Validate,Predict,Export},"
|
|
266
|
+
"ylabel={Jobs},legend style={at={(0.5,1.05)},anchor=south,legend columns=2}"
|
|
267
|
+
),
|
|
268
|
+
)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Replace `ybar` with `ybar stacked` to show each stage's total of 1,000 completed jobs, split by first attempt and retry.
|
|
272
|
+
|
|
273
|
+
#### Mean latency with error bars
|
|
274
|
+
|
|
275
|
+
Reuse the load-test DataFrame to draw the mean latency with its sample standard deviation. The first error coordinate is zero because the user count has no uncertainty here.
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
report.add_tikz_plot(
|
|
279
|
+
Plot(
|
|
280
|
+
name="Mean latency",
|
|
281
|
+
coordinates=measurements[["Concurrent users", "Latency (ms)"]].itertuples(
|
|
282
|
+
index=False, name=None
|
|
283
|
+
),
|
|
284
|
+
error_bar=((0, std) for std in measurements["Stddev (ms)"]),
|
|
285
|
+
options="mark=*,error bars/.cd,y dir=both,y explicit",
|
|
286
|
+
),
|
|
287
|
+
"Mean latency with sample standard deviation",
|
|
288
|
+
axis_options=(
|
|
289
|
+
r"width=0.85\linewidth,height=5cm,grid=major,"
|
|
290
|
+
"xlabel={Concurrent users},ylabel={Latency (ms)},legend pos=north west"
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The [ML example](https://github.com/HaroldGueneau/pylatex-report/blob/main/examples/ml_experiment.py) computes these summaries from repeated runs rather than entering them by hand. The [fund example](https://github.com/HaroldGueneau/pylatex-report/blob/main/examples/fund_risk.py) builds a Polars histogram table with `numpy.histogram` and an empirical cumulative distribution, then plots the counts as intervals and the probabilities as steps. Their rendered PDFs are linked in [Examples](#examples).
|
|
296
|
+
|
|
297
|
+
Each plot supplies either a nonempty `func` expression or nonempty `coordinates` of finite numeric `(x, y)` pairs. Functions use PGFPlots syntax, such as `x^2`, rather than Python callables. Plot data and options are captured when `add_tikz_plot` is called, including coordinates supplied as an iterator.
|
|
298
|
+
|
|
299
|
+
`axis_options` and `Plot.options` preserve PGFPlots/LaTeX syntax, including commands such as `\linewidth`; plain text captions and legend names are escaped. Escape LaTeX special characters yourself in raw options, such as `\%` in tick labels. Omitting `axis_options` uses an axis width of `0.9\linewidth` and a height of `6cm`; supplying it replaces those defaults, so include the dimensions you want. This method creates one figure with one axis; use `add_plot` for Matplotlib mosaics.
|
|
300
|
+
|
|
301
|
+
### Report style
|
|
302
|
+
|
|
303
|
+
Reuse a `ReportStyle` across reports to apply the same branding. Put your own logo at `logo.png` in your working directory, or download the [example logo](https://raw.githubusercontent.com/HaroldGueneau/pylatex-report/main/examples/assets/horizon-logo.png) and save it under that name.
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
from pylatex_report import CoverPage, LatexReport, ReportStyle
|
|
307
|
+
|
|
308
|
+
style = ReportStyle(
|
|
309
|
+
logo="logo.png",
|
|
310
|
+
accent_color="#126D77",
|
|
311
|
+
header="Horizon Research",
|
|
312
|
+
footer="Simulated data",
|
|
313
|
+
cover=CoverPage(label="Research report", subtitle="Model comparison"),
|
|
314
|
+
)
|
|
315
|
+
report = LatexReport("comparison", "Model comparison", "Research team", style=style)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Logos can be PNG, JPEG or PDF. A custom cover uses the report title, author and date, with page numbering starting on the next page. Set `page_numbers=False` on the style to hide page numbers. The constructor options `table_of_contents`, `separate_cover` and `section_page_break` control pagination. For custom LaTeX, use the underlying PyLaTeX document at `report.doc`.
|
|
319
|
+
|
|
320
|
+
### Output and diagnostics
|
|
321
|
+
|
|
322
|
+
`generate_report("reports")` returns the PDF path. By default, it publishes only the finished PDF and cleans up temporary compilation files on success or failure. Use `keep_intermediates=True` to retain a build directory with the `.tex` file, figures, logo and LaTeX log. To recompile an example from its retained build directory, run `latexmk -pdf report.tex`.
|
|
323
|
+
|
|
324
|
+
When `latexmk` is unavailable, `pdflatex` runs until the auxiliary files for references and contents stop changing, with a maximum of five passes. If they do not stabilize, generation raises an error and preserves any existing destination PDF. Use `keep_intermediates=True` to inspect the build files.
|
|
325
|
+
|
|
326
|
+
The API validates inputs with Pydantic. Pass `columns=3` as an integer. A string such as `columns="3"` raises a `ValidationError`.
|
|
327
|
+
|
|
328
|
+
## Development
|
|
329
|
+
|
|
330
|
+
Clone the [repository](https://github.com/HaroldGueneau/pylatex-report) as shown in [Examples](#examples), then run these commands from its root. `uv sync --locked` installs the library in editable mode along with the development and example dependencies.
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
uv sync --locked
|
|
334
|
+
uv run --locked pre-commit install
|
|
335
|
+
uv run --locked pre-commit run --all-files
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Run `pre-commit install` once per clone. The hooks use [Ruff](https://docs.astral.sh/ruff/) for linting and formatting checks and [ty](https://docs.astral.sh/ty/) for static type checking. To apply formatting locally, run `uv run --locked ruff format`.
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
uv run --locked python -B -m pytest
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The suite includes integration tests that compile PDFs and skips them when `pdflatex` is unavailable. Use `-m "not integration"` to run unit tests only or `--keep-test-artifacts` to keep generated test files for debugging.
|
|
345
|
+
|
|
346
|
+
GitHub Actions runs unit tests on Python 3.10 and 3.15 with freshly resolved dependencies. A third environment on Python 3.10 uses `--resolution lowest-direct` to test the minimum compatible versions of the declared direct dependencies. These jobs install only the runtime dependencies and the `test` dependency group; they do not use or modify `uv.lock`.
|
|
347
|
+
|
|
348
|
+
A single Python 3.12 job installs LaTeX and runs pre-commit, the full test suite with locked dependencies, and the installed-distribution check. The distribution check builds a wheel and a source distribution, rebuilds a second wheel from that source distribution, and installs each wheel with its runtime dependencies in a separate fresh environment. Each installation must import the public API and compile a PDF from outside the checkout, with Python's isolated mode enabled. Run the same check locally with `uv` and LaTeX available:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
bash scripts/check_distribution.sh
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Pass a new output directory to retain the tested wheel and source archive, for example `bash scripts/check_distribution.sh dist`. The script refuses an existing destination and exports files only after both installation checks succeed.
|
|
355
|
+
|
|
356
|
+
After updating a dependency with `uv lock --upgrade-package <name>`, rerun the checks.
|
|
357
|
+
|
|
358
|
+
To enforce these checks on the default branch, wait for the first successful run, then create an active branch ruleset under **Settings → Rulesets**. Require a pull request and require all four status checks: **Quality and tests**, **Python 3.10 / highest**, **Python 3.15 / highest**, and **Python 3.10 / lowest-direct**. [GitHub ruleset documentation](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository)
|
|
359
|
+
|
|
360
|
+
### Publishing a release
|
|
361
|
+
|
|
362
|
+
The [publication workflow](https://github.com/HaroldGueneau/pylatex-report/blob/main/.github/workflows/publish.yml) runs when a GitHub release is published. It reuses the full test workflow on the release's commit, verifies that the tag equals `v` followed by the version in `pyproject.toml`, and checks the distribution metadata and README rendering. Once every check passes, a separate job publishes the exact wheel and source archive tested by CI. Pushes and pull requests run tests only.
|
|
363
|
+
|
|
364
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/) tells PyPI which GitHub repository, workflow, and environment may publish this package. GitHub supplies proof of that identity during the publication job, and PyPI grants temporary upload credentials. You do not need a permanent API token or a password stored in the repository.
|
|
365
|
+
|
|
366
|
+
Configure it once before the first publication:
|
|
367
|
+
|
|
368
|
+
1. In the GitHub repository, open **Settings → Environments → New environment**. Enter `pypi` and click **Configure environment**. This is a named publication destination in GitHub, not a local Python virtual environment. The workflow already refers to this name. Leave optional settings at their defaults for the initial setup; no environment secret is required.
|
|
369
|
+
2. Sign in to PyPI, open **Your account → Publishing**, and find the form for adding a new pending publisher. Select **GitHub** and enter these values:
|
|
370
|
+
|
|
371
|
+
| Field | Value |
|
|
372
|
+
| --- | --- |
|
|
373
|
+
| PyPI project name | `pylatex-report` |
|
|
374
|
+
| Repository owner | `HaroldGueneau` |
|
|
375
|
+
| Repository name | `pylatex-report` |
|
|
376
|
+
| Workflow filename | `publish.yml` |
|
|
377
|
+
| Environment name | `pypi` |
|
|
378
|
+
|
|
379
|
+
Use the GitHub repository owner, even if your PyPI username is different. The workflow field takes only `publish.yml`, without `.github/workflows/`. Click **Add** and check that the publisher appears in the pending publishers list.
|
|
380
|
+
|
|
381
|
+
The pending status is expected: the package does not exist on PyPI yet. The first successful publication creates it and activates the publisher automatically, so no manual project creation or initial upload is needed. Registering a pending publisher does not reserve the package name.
|
|
382
|
+
|
|
383
|
+
For each release, update the version in `pyproject.toml` and run `uv lock` if the version changed. Commit and push the release changes, then wait for CI to pass. Publish a GitHub release with a matching tag, such as `v0.1.0` for version `0.1.0`. The workflow and all release changes must be present in that tagged commit. For the first release, also remove the temporary pre-publication notice and GitHub installation fallback at the top of this README.
|
|
384
|
+
|
|
385
|
+
Follow the **Publish to PyPI** run in GitHub Actions. After it succeeds, verify the published version from a fresh environment outside this repository, including PDF generation with the quickstart. Later fixes need a new version and tag; published distribution files cannot be replaced.
|
|
386
|
+
|
|
387
|
+
If only the publication job fails, fix the PyPI configuration or transient issue and choose **Re-run failed jobs** to reuse the same tested archives, which are retained for seven days.
|
|
388
|
+
|
|
389
|
+
## License
|
|
390
|
+
|
|
391
|
+
Distributed under the [MIT license](https://github.com/HaroldGueneau/pylatex-report/blob/main/LICENSE).
|