tesorotools-python 0.0.34__tar.gz → 0.0.35__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.
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/PKG-INFO +1 -1
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/pyproject.toml +1 -1
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/line_plot.py +31 -38
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/stacked.py +25 -27
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/type_curve.py +5 -3
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/plots.yaml +0 -2
- tesorotools_python-0.0.35/src/tesorotools/driver.py +138 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/table.py +42 -7
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/.gitignore +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/barh.md +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/barh_plot.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/table.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/convert.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/data_sources/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/main.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/offsets/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/providers/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/utils/config.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/utils/globals.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/utils/matplotlib.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/utils/template.py +0 -0
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/line_plot.py
RENAMED
|
@@ -310,21 +310,20 @@ def style_baseline(
|
|
|
310
310
|
reference: float = 0,
|
|
311
311
|
**baseline_config: Any,
|
|
312
312
|
) -> None:
|
|
313
|
-
|
|
313
|
+
"""Draw a horizontal baseline at *reference*.
|
|
314
|
+
|
|
315
|
+
Always uses ``axhline`` (with high zorder) so callers that
|
|
316
|
+
later restyle the spines do not silently erase the baseline.
|
|
317
|
+
"""
|
|
314
318
|
bottom_lim, top_lim = ax.get_ylim()
|
|
315
319
|
ax.set_ylim(
|
|
316
320
|
bottom=min(reference, bottom_lim),
|
|
317
321
|
top=max(reference, top_lim),
|
|
318
322
|
)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
ax.spines["top"].set_edgecolor(color)
|
|
324
|
-
else:
|
|
325
|
-
ax.axhline( # type: ignore[reportUnknownMemberType]
|
|
326
|
-
y=reference, **baseline_config
|
|
327
|
-
)
|
|
323
|
+
baseline_config.setdefault("zorder", 2.5)
|
|
324
|
+
ax.axhline( # type: ignore[reportUnknownMemberType]
|
|
325
|
+
y=reference, **baseline_config
|
|
326
|
+
)
|
|
328
327
|
|
|
329
328
|
|
|
330
329
|
def plot_line_chart(
|
|
@@ -333,12 +332,12 @@ def plot_line_chart(
|
|
|
333
332
|
*,
|
|
334
333
|
base_100: bool,
|
|
335
334
|
annotate: bool,
|
|
336
|
-
|
|
335
|
+
fmt: dict[str, Any],
|
|
337
336
|
**kwargs: Any,
|
|
338
337
|
) -> None:
|
|
339
338
|
if base_100:
|
|
340
339
|
data = data / data.iloc[0, :] * 100
|
|
341
|
-
if
|
|
340
|
+
if fmt["units"] == "p.b.":
|
|
342
341
|
data = data * 100
|
|
343
342
|
fig: Figure = plt.figure( # type: ignore[reportUnknownMemberType]
|
|
344
343
|
**FIG_CONFIG
|
|
@@ -349,11 +348,13 @@ def plot_line_chart(
|
|
|
349
348
|
pass
|
|
350
349
|
|
|
351
350
|
reference = 100 if base_100 else 0
|
|
352
|
-
style_spines(ax, **
|
|
351
|
+
style_spines(ax, **fmt, **AX_CONFIG["spines"])
|
|
353
352
|
style_baseline(ax, reference, **AX_CONFIG["baseline"])
|
|
354
|
-
ax.
|
|
355
|
-
|
|
356
|
-
|
|
353
|
+
handles, label_strs = ax.get_legend_handles_labels()
|
|
354
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
355
|
+
handles,
|
|
356
|
+
label_strs,
|
|
357
|
+
loc="outside lower center",
|
|
357
358
|
ncol=(
|
|
358
359
|
kwargs["legend"]["ncol"]
|
|
359
360
|
if kwargs.get("legend", None) is not None
|
|
@@ -407,10 +408,8 @@ class Legend:
|
|
|
407
408
|
def __init__(
|
|
408
409
|
self,
|
|
409
410
|
ncol: int | None = None,
|
|
410
|
-
sep: float | None = None,
|
|
411
411
|
) -> None:
|
|
412
412
|
self.ncol = ncol
|
|
413
|
-
self.sep = sep
|
|
414
413
|
|
|
415
414
|
@classmethod
|
|
416
415
|
def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
|
|
@@ -436,7 +435,7 @@ class LinePlot:
|
|
|
436
435
|
annotate: bool = False,
|
|
437
436
|
annotate_color: str | None = None,
|
|
438
437
|
baseline: bool = False,
|
|
439
|
-
|
|
438
|
+
fmt: Format | None = None,
|
|
440
439
|
legend: Legend | None = None,
|
|
441
440
|
data: pd.DataFrame | None = None,
|
|
442
441
|
figsize: tuple[float, float] | None = None,
|
|
@@ -469,7 +468,7 @@ class LinePlot:
|
|
|
469
468
|
self.base_100_date = base_100_date
|
|
470
469
|
self.annotate = annotate
|
|
471
470
|
self.annotate_color = annotate_color
|
|
472
|
-
self.
|
|
471
|
+
self.fmt = fmt
|
|
473
472
|
self.start_date = start_date
|
|
474
473
|
self.end_date = end_date
|
|
475
474
|
self.series = series
|
|
@@ -545,13 +544,13 @@ class LinePlot:
|
|
|
545
544
|
if self.vlines:
|
|
546
545
|
draw_vlines(ax, self.vlines)
|
|
547
546
|
|
|
548
|
-
assert self.
|
|
547
|
+
assert self.fmt is not None
|
|
549
548
|
if self.annotate:
|
|
550
549
|
annotate_last_values(
|
|
551
550
|
ax,
|
|
552
551
|
plot_data,
|
|
553
|
-
decimals=self.
|
|
554
|
-
units=self.
|
|
552
|
+
decimals=self.fmt.decimals,
|
|
553
|
+
units=self.fmt.units,
|
|
555
554
|
labels=self.series,
|
|
556
555
|
series_styles=self.series_styles,
|
|
557
556
|
annotate_color=self.annotate_color,
|
|
@@ -559,8 +558,8 @@ class LinePlot:
|
|
|
559
558
|
|
|
560
559
|
style_spines( # maybe make this function accept a Format object
|
|
561
560
|
ax,
|
|
562
|
-
decimals=self.
|
|
563
|
-
units=self.
|
|
561
|
+
decimals=self.fmt.decimals,
|
|
562
|
+
units=self.fmt.units,
|
|
564
563
|
**AX_CONFIG["spines"],
|
|
565
564
|
)
|
|
566
565
|
if self.baseline:
|
|
@@ -569,25 +568,19 @@ class LinePlot:
|
|
|
569
568
|
|
|
570
569
|
if self.legend is not None:
|
|
571
570
|
labels = [self.series[c] for c in plot_data.columns]
|
|
571
|
+
handles, label_strs = ax.get_legend_handles_labels()
|
|
572
|
+
fig_width_px: float = fig.get_size_inches()[0] * fig.dpi
|
|
572
573
|
ncol = (
|
|
573
574
|
self.legend.ncol
|
|
574
575
|
if self.legend.ncol is not None
|
|
575
|
-
else auto_ncol(ax, labels)
|
|
576
|
-
)
|
|
577
|
-
sep = (
|
|
578
|
-
self.legend.sep
|
|
579
|
-
if self.legend.sep is not None
|
|
580
|
-
else LINE_PLOT_CONFIG["legend_sep"]
|
|
576
|
+
else auto_ncol(ax, labels, available_width_px=fig_width_px)
|
|
581
577
|
)
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
578
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
579
|
+
handles,
|
|
580
|
+
label_strs,
|
|
581
|
+
loc="outside lower center",
|
|
585
582
|
ncol=ncol,
|
|
586
583
|
)
|
|
587
|
-
else:
|
|
588
|
-
ax.legend().set_visible( # type: ignore[reportUnknownMemberType]
|
|
589
|
-
False
|
|
590
|
-
)
|
|
591
584
|
|
|
592
585
|
if self.plot_size is not None:
|
|
593
586
|
adjust_figure_for_plot_size(fig, ax, self.plot_size)
|
|
@@ -22,8 +22,6 @@ from tesorotools.artists.line_plot import (
|
|
|
22
22
|
)
|
|
23
23
|
from tesorotools.utils.config import TemplateLoader
|
|
24
24
|
|
|
25
|
-
_DEFAULT_SEP = -0.125
|
|
26
|
-
|
|
27
25
|
|
|
28
26
|
class StackedAreaPlot:
|
|
29
27
|
"""Stacked area chart with the tesorotools visual style.
|
|
@@ -43,7 +41,7 @@ class StackedAreaPlot:
|
|
|
43
41
|
start_date: str | None = None,
|
|
44
42
|
end_date: str | None = None,
|
|
45
43
|
baseline: bool = False,
|
|
46
|
-
|
|
44
|
+
fmt: Format | None = None,
|
|
47
45
|
legend: Legend | None = None,
|
|
48
46
|
figsize: tuple[float, float] | None = None,
|
|
49
47
|
plot_size: tuple[float, float] | None = None,
|
|
@@ -57,7 +55,7 @@ class StackedAreaPlot:
|
|
|
57
55
|
self.start_date = start_date
|
|
58
56
|
self.end_date = end_date
|
|
59
57
|
self.baseline = baseline
|
|
60
|
-
self.
|
|
58
|
+
self.fmt = fmt or Format()
|
|
61
59
|
self.legend = legend
|
|
62
60
|
self.figsize = figsize
|
|
63
61
|
self.plot_size = plot_size
|
|
@@ -109,23 +107,25 @@ class StackedAreaPlot:
|
|
|
109
107
|
|
|
110
108
|
style_spines(
|
|
111
109
|
ax,
|
|
112
|
-
decimals=self.
|
|
113
|
-
units=self.
|
|
110
|
+
decimals=self.fmt.decimals,
|
|
111
|
+
units=self.fmt.units,
|
|
114
112
|
**AX_CONFIG["spines"],
|
|
115
113
|
)
|
|
116
114
|
if self.baseline:
|
|
117
115
|
style_baseline(ax, 0, **AX_CONFIG["baseline"])
|
|
118
116
|
|
|
117
|
+
fig_width_px: float = fig.get_size_inches()[0] * fig.dpi
|
|
119
118
|
legend_ncol = self.legend.ncol if self.legend else None
|
|
120
|
-
ncol =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
else _DEFAULT_SEP
|
|
119
|
+
ncol = (
|
|
120
|
+
legend_ncol
|
|
121
|
+
if legend_ncol is not None
|
|
122
|
+
else auto_ncol(ax, labels, available_width_px=fig_width_px)
|
|
125
123
|
)
|
|
126
|
-
ax.
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
handles, label_strs = ax.get_legend_handles_labels()
|
|
125
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
126
|
+
handles,
|
|
127
|
+
label_strs,
|
|
128
|
+
loc="outside lower center",
|
|
129
129
|
ncol=ncol,
|
|
130
130
|
)
|
|
131
131
|
|
|
@@ -163,7 +163,7 @@ class StackedBarPlot:
|
|
|
163
163
|
start_date: str | None = None,
|
|
164
164
|
end_date: str | None = None,
|
|
165
165
|
baseline: bool = True,
|
|
166
|
-
|
|
166
|
+
fmt: Format | None = None,
|
|
167
167
|
legend: Legend | None = None,
|
|
168
168
|
figsize: tuple[float, float] | None = None,
|
|
169
169
|
overlay_series: dict[str, str] | None = None,
|
|
@@ -180,7 +180,7 @@ class StackedBarPlot:
|
|
|
180
180
|
self.start_date = start_date
|
|
181
181
|
self.end_date = end_date
|
|
182
182
|
self.baseline = baseline
|
|
183
|
-
self.
|
|
183
|
+
self.fmt = fmt or Format()
|
|
184
184
|
self.legend = legend
|
|
185
185
|
self.plot_size = plot_size
|
|
186
186
|
self.figsize = figsize
|
|
@@ -313,8 +313,8 @@ class StackedBarPlot:
|
|
|
313
313
|
|
|
314
314
|
style_spines(
|
|
315
315
|
ax,
|
|
316
|
-
decimals=self.
|
|
317
|
-
units=self.
|
|
316
|
+
decimals=self.fmt.decimals,
|
|
317
|
+
units=self.fmt.units,
|
|
318
318
|
**AX_CONFIG["spines"],
|
|
319
319
|
)
|
|
320
320
|
if self.baseline:
|
|
@@ -323,20 +323,18 @@ class StackedBarPlot:
|
|
|
323
323
|
all_labels = list(self.series.values()) + list(
|
|
324
324
|
self.overlay_series.values()
|
|
325
325
|
)
|
|
326
|
+
fig_width_px: float = fig.get_size_inches()[0] * fig.dpi
|
|
326
327
|
legend_ncol = self.legend.ncol if self.legend else None
|
|
327
328
|
ncol = (
|
|
328
329
|
legend_ncol
|
|
329
330
|
if legend_ncol is not None
|
|
330
|
-
else auto_ncol(ax, all_labels)
|
|
331
|
-
)
|
|
332
|
-
sep = (
|
|
333
|
-
self.legend.sep
|
|
334
|
-
if self.legend and self.legend.sep is not None
|
|
335
|
-
else _DEFAULT_SEP
|
|
331
|
+
else auto_ncol(ax, all_labels, available_width_px=fig_width_px)
|
|
336
332
|
)
|
|
337
|
-
ax.
|
|
338
|
-
|
|
339
|
-
|
|
333
|
+
handles, label_strs = ax.get_legend_handles_labels()
|
|
334
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
335
|
+
handles,
|
|
336
|
+
label_strs,
|
|
337
|
+
loc="outside lower center",
|
|
340
338
|
ncol=ncol,
|
|
341
339
|
)
|
|
342
340
|
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/type_curve.py
RENAMED
|
@@ -167,9 +167,11 @@ def plot_type_curve(
|
|
|
167
167
|
style_spines(ax, **merged_config["yaxis"], **AX_CONFIG["spines"])
|
|
168
168
|
_rotate_xticks(ax)
|
|
169
169
|
style_baseline(ax, 0, **AX_CONFIG["baseline"])
|
|
170
|
-
ax.
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
171
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
172
|
+
handles,
|
|
173
|
+
labels,
|
|
174
|
+
loc="outside lower center",
|
|
173
175
|
ncol=3,
|
|
174
176
|
)
|
|
175
177
|
fig.savefig( # type: ignore[reportUnknownMemberType]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Schema-agnostic YAML → docx driver with a plug-in renderer registry.
|
|
2
|
+
|
|
3
|
+
Each project that consumes tesorotools wraps its data sources in a
|
|
4
|
+
small adapter, and then declares its charts/tables/text blocks in a
|
|
5
|
+
YAML file. This module dispatches those declarations to the right
|
|
6
|
+
renderer at runtime, so projects share the orchestration code instead
|
|
7
|
+
of each one growing its own ``driver.py``.
|
|
8
|
+
|
|
9
|
+
Typical usage::
|
|
10
|
+
|
|
11
|
+
from tesorotools import driver
|
|
12
|
+
from tesorotools.artists.line_plot import LinePlot
|
|
13
|
+
|
|
14
|
+
def render_line(document, cfg, ctx):
|
|
15
|
+
chart = LinePlot(out_path=Path(cfg["out_path"]), ...)
|
|
16
|
+
chart.plot()
|
|
17
|
+
# then embed the PNG into ``document``...
|
|
18
|
+
|
|
19
|
+
driver.register_renderer("line", render_line)
|
|
20
|
+
items = driver.load(yaml_path)["charts"]
|
|
21
|
+
driver.render(document, items, ctx={"year": 2026})
|
|
22
|
+
|
|
23
|
+
Renderers are looked up by the ``type`` key on each item; selecting
|
|
24
|
+
which items to render in a given pass is done with the ``section``
|
|
25
|
+
and ``on_table`` filters, mirroring the diary/reports use case where
|
|
26
|
+
some charts go before the tables and others embed inside them.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any, Callable, Mapping
|
|
33
|
+
|
|
34
|
+
from docx.document import Document
|
|
35
|
+
|
|
36
|
+
from tesorotools.utils.config import read_config
|
|
37
|
+
|
|
38
|
+
Renderer = Callable[[Document, dict[str, Any], Mapping[str, Any]], None]
|
|
39
|
+
|
|
40
|
+
_REGISTRY: dict[str, Renderer] = {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def register_renderer(type_name: str, renderer: Renderer) -> None:
|
|
44
|
+
"""Register *renderer* for items declaring ``type: <type_name>``.
|
|
45
|
+
|
|
46
|
+
Calling twice with the same name overwrites the previous entry,
|
|
47
|
+
so projects can swap implementations in tests.
|
|
48
|
+
"""
|
|
49
|
+
_REGISTRY[type_name] = renderer
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def unregister_renderer(type_name: str) -> None:
|
|
53
|
+
"""Remove a previously registered renderer; no-op if absent."""
|
|
54
|
+
_REGISTRY.pop(type_name, None)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def registered_types() -> list[str]:
|
|
58
|
+
"""Names currently in the registry, sorted alphabetically."""
|
|
59
|
+
return sorted(_REGISTRY)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load(path: Path) -> dict[str, Any]:
|
|
63
|
+
"""Read a driver YAML file. Pure I/O wrapper around ``read_config``."""
|
|
64
|
+
return read_config(path)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _substitute_tokens(value: Any, ctx: Mapping[str, Any]) -> Any:
|
|
68
|
+
"""Recursively substitute ``{token}`` placeholders in strings.
|
|
69
|
+
|
|
70
|
+
Unknown tokens are left literal so partially-templated YAML is
|
|
71
|
+
still readable when ctx is incomplete (useful in tests). Lists
|
|
72
|
+
and dicts are walked; non-string leaves pass through untouched.
|
|
73
|
+
"""
|
|
74
|
+
if isinstance(value, str):
|
|
75
|
+
try:
|
|
76
|
+
return value.format_map(_SafeMapping(ctx))
|
|
77
|
+
except (KeyError, IndexError, ValueError):
|
|
78
|
+
return value
|
|
79
|
+
if isinstance(value, list):
|
|
80
|
+
return [_substitute_tokens(v, ctx) for v in value] # type: ignore[reportUnknownVariableType]
|
|
81
|
+
if isinstance(value, dict):
|
|
82
|
+
return {k: _substitute_tokens(v, ctx) for k, v in value.items()} # type: ignore[reportUnknownVariableType]
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class _SafeMapping(dict[str, Any]):
|
|
87
|
+
"""``str.format_map`` mapping that leaves unknown keys literal."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, base: Mapping[str, Any]) -> None:
|
|
90
|
+
super().__init__(base)
|
|
91
|
+
|
|
92
|
+
def __missing__(self, key: str) -> str:
|
|
93
|
+
return "{" + key + "}"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def render(
|
|
97
|
+
document: Document,
|
|
98
|
+
items: Mapping[str, dict[str, Any]],
|
|
99
|
+
ctx: Mapping[str, Any] | None = None,
|
|
100
|
+
*,
|
|
101
|
+
section: str | None = None,
|
|
102
|
+
on_table: str | None = None,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Render every item in *items* whose filters match.
|
|
105
|
+
|
|
106
|
+
*items* maps item ids to configuration dicts; each dict must
|
|
107
|
+
declare a ``type`` key that resolves to a registered renderer.
|
|
108
|
+
String values inside the config (titles, labels, paths) are
|
|
109
|
+
formatted against *ctx*: ``"Bolsas {year}"`` with
|
|
110
|
+
``ctx={"year": 2026}`` becomes ``"Bolsas 2026"``. Missing tokens
|
|
111
|
+
are left as literals.
|
|
112
|
+
|
|
113
|
+
Filters:
|
|
114
|
+
|
|
115
|
+
- ``section`` — only render items whose ``section`` field equals
|
|
116
|
+
this value (e.g. ``"pre_tables"`` vs ``"intro"``).
|
|
117
|
+
- ``on_table`` — only render items whose ``on_table`` field
|
|
118
|
+
equals this value, used to embed charts beside specific tables.
|
|
119
|
+
|
|
120
|
+
When both filters are ``None`` every item is rendered.
|
|
121
|
+
Items declaring an unknown ``type`` raise ``KeyError`` so the
|
|
122
|
+
misconfiguration surfaces immediately.
|
|
123
|
+
"""
|
|
124
|
+
ctx_resolved: Mapping[str, Any] = ctx if ctx is not None else {}
|
|
125
|
+
for item_id, raw_cfg in items.items():
|
|
126
|
+
if section is not None and raw_cfg.get("section") != section:
|
|
127
|
+
continue
|
|
128
|
+
if on_table is not None and raw_cfg.get("on_table") != on_table:
|
|
129
|
+
continue
|
|
130
|
+
cfg: dict[str, Any] = _substitute_tokens(dict(raw_cfg), ctx_resolved)
|
|
131
|
+
cfg["id"] = item_id
|
|
132
|
+
type_name: str = cfg.get("type", "")
|
|
133
|
+
if type_name not in _REGISTRY:
|
|
134
|
+
raise KeyError(
|
|
135
|
+
f"No renderer registered for type {type_name!r} "
|
|
136
|
+
f"(item {item_id!r}). Available: {registered_types()}"
|
|
137
|
+
)
|
|
138
|
+
_REGISTRY[type_name](document, cfg, ctx_resolved)
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/table.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
# pyright: reportPrivateUsage=false
|
|
4
|
+
from dataclasses import dataclass
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Any, Self
|
|
6
7
|
|
|
@@ -22,6 +23,27 @@ from tesorotools.utils.template import TemplateLoader
|
|
|
22
23
|
|
|
23
24
|
RENDER_CONFIG: dict[str, Any] = read_config(PLOT_CONFIG_FILE)["table"]
|
|
24
25
|
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class RenderConfig:
|
|
29
|
+
"""Per-call rendering options for :func:`render_table`.
|
|
30
|
+
|
|
31
|
+
Attributes match the ``table`` section of the bundled
|
|
32
|
+
``plots.yaml``; defaults are read from there. Pass an instance
|
|
33
|
+
explicitly to override the global config without monkey-patching.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
style: str | None = None
|
|
37
|
+
autofit: bool = False
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_global(cls) -> RenderConfig:
|
|
41
|
+
return cls(
|
|
42
|
+
style=RENDER_CONFIG.get("style"),
|
|
43
|
+
autofit=bool(RENDER_CONFIG["autofit"]),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
25
47
|
TEXTO_TABLAS: int = 9
|
|
26
48
|
|
|
27
49
|
CENTER = WD_ALIGN_PARAGRAPH.CENTER
|
|
@@ -250,9 +272,9 @@ def _fill_content(
|
|
|
250
272
|
_style_content(cell)
|
|
251
273
|
|
|
252
274
|
|
|
253
|
-
def _style_table(table_docx: TableDocx) -> None:
|
|
254
|
-
table_docx.style =
|
|
255
|
-
table_docx.autofit =
|
|
275
|
+
def _style_table(table_docx: TableDocx, config: RenderConfig) -> None:
|
|
276
|
+
table_docx.style = config.style
|
|
277
|
+
table_docx.autofit = config.autofit
|
|
256
278
|
|
|
257
279
|
|
|
258
280
|
def render_table(
|
|
@@ -261,16 +283,29 @@ def render_table(
|
|
|
261
283
|
shade_table: pd.DataFrame | None,
|
|
262
284
|
document: Document,
|
|
263
285
|
block_sep: bool,
|
|
286
|
+
*,
|
|
287
|
+
config: RenderConfig | None = None,
|
|
264
288
|
**kwargs: Any,
|
|
265
|
-
) ->
|
|
266
|
-
|
|
289
|
+
) -> TableDocx:
|
|
290
|
+
"""Append a styled table to *document* and return the ``Table``.
|
|
291
|
+
|
|
292
|
+
Returning the ``docx.table.Table`` mirrors python-docx's own
|
|
293
|
+
``add_*`` helpers (``add_paragraph``, ``add_heading``) and lets
|
|
294
|
+
callers post-process the table (alignment overrides, custom
|
|
295
|
+
borders, references for navigation) without resorting to
|
|
296
|
+
``document.tables[-1]``. Pass *config* to override the global
|
|
297
|
+
rendering style/autofit on a per-call basis.
|
|
298
|
+
"""
|
|
299
|
+
cfg: RenderConfig = (
|
|
300
|
+
config if config is not None else RenderConfig.from_global()
|
|
301
|
+
)
|
|
267
302
|
horizontal: bool = isinstance(table.columns, pd.MultiIndex)
|
|
268
303
|
table_docx: TableDocx = document.add_table(
|
|
269
304
|
rows=len(table.index) + table.columns.nlevels,
|
|
270
305
|
cols=len(table.columns) + 1,
|
|
271
306
|
)
|
|
272
307
|
|
|
273
|
-
_style_table(table_docx)
|
|
308
|
+
_style_table(table_docx, cfg)
|
|
274
309
|
_fill_column_names(table, table_docx, horizontal)
|
|
275
310
|
_fill_index_names(
|
|
276
311
|
index=table.index,
|
|
@@ -282,7 +317,7 @@ def render_table(
|
|
|
282
317
|
_separate_blocks(table.index, table_docx)
|
|
283
318
|
_fill_content(table, color_table, shade_table, table_docx, horizontal)
|
|
284
319
|
table_docx.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
285
|
-
return
|
|
320
|
+
return table_docx
|
|
286
321
|
|
|
287
322
|
|
|
288
323
|
class Table:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/artists/barh_plot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/fonts/README.md
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/assets/tesoro.mplstyle
RENAMED
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/data_sources/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/data_sources/debug.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/database/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/node.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/dependencies/resolution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/pipeline/diagnose.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/providers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/__init__.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/content.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/images.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/section.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/subtitle.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/text.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.34 → tesorotools_python-0.0.35}/src/tesorotools/render/content/title.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|