tesorotools-python 0.0.35__tar.gz → 0.0.36__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.35 → tesorotools_python-0.0.36}/PKG-INFO +1 -1
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/pyproject.toml +1 -1
- tesorotools_python-0.0.36/src/tesorotools/__init__.py +115 -0
- tesorotools_python-0.0.36/src/tesorotools/_registry.py +117 -0
- tesorotools_python-0.0.36/src/tesorotools/artists/__init__.py +25 -0
- tesorotools_python-0.0.36/src/tesorotools/artists/type_curve.py +229 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/convert.py +17 -3
- tesorotools_python-0.0.36/src/tesorotools/providers/__init__.py +32 -0
- tesorotools_python-0.0.36/src/tesorotools/render/__init__.py +22 -0
- tesorotools_python-0.0.36/src/tesorotools/utils/config.py +38 -0
- tesorotools_python-0.0.35/src/tesorotools/artists/__init__.py +0 -5
- tesorotools_python-0.0.35/src/tesorotools/artists/type_curve.py +0 -201
- tesorotools_python-0.0.35/src/tesorotools/providers/__init__.py +0 -0
- tesorotools_python-0.0.35/src/tesorotools/render/__init__.py +0 -0
- tesorotools_python-0.0.35/src/tesorotools/render/content/__init__.py +0 -0
- tesorotools_python-0.0.35/src/tesorotools/utils/config.py +0 -98
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/.gitignore +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/artists/barh.md +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/artists/barh_plot.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/artists/line_plot.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/artists/stacked.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/artists/table.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/README.md +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/README.md +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/plots.yaml +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/tesoro.mplstyle +0 -0
- {tesorotools_python-0.0.35/src/tesorotools → tesorotools_python-0.0.36/src/tesorotools/data_sources}/__init__.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/data_sources/debug.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/database/__init__.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/database/local.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/database/push.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/database/shared.py +0 -0
- {tesorotools_python-0.0.35/src/tesorotools/data_sources → tesorotools_python-0.0.36/src/tesorotools/dependencies}/__init__.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/dependencies/node.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/dependencies/resolution.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/driver.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/main.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/manifest.py +0 -0
- {tesorotools_python-0.0.35/src/tesorotools/dependencies → tesorotools_python-0.0.36/src/tesorotools/offsets}/__init__.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/offsets/offsets.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/offsets/outliers.py +0 -0
- {tesorotools_python-0.0.35/src/tesorotools/offsets → tesorotools_python-0.0.36/src/tesorotools/pipeline}/__init__.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/pipeline/diagnose.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/pipeline/engine.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/pipeline/rules.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/providers/base.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/providers/bde.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/providers/ecb.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/py.typed +0 -0
- {tesorotools_python-0.0.35/src/tesorotools/pipeline → tesorotools_python-0.0.36/src/tesorotools/render/content}/__init__.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/content.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/images.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/section.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/subtitle.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/table.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/text.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/title.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/report.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/testing/__init__.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/testing/compare.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/utils/__init__.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/utils/format.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/utils/globals.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/utils/matplotlib.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/utils/series.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/utils/shortcuts.py +0 -0
- {tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/utils/template.py +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""tesorotools public API.
|
|
2
|
+
|
|
3
|
+
Importing ``tesorotools`` eagerly loads artist and render
|
|
4
|
+
classes (their modules run matplotlib/locale setup as side
|
|
5
|
+
effects) and registers their YAML tags via
|
|
6
|
+
``_register_builtins``.
|
|
7
|
+
|
|
8
|
+
Provider subclasses gated by optional extras
|
|
9
|
+
(``BdeProvider`` requires ``[bde]``, ``EcbProvider``
|
|
10
|
+
requires ``[ecb]``) are exposed lazily through
|
|
11
|
+
``__getattr__``; importing this module does not require the
|
|
12
|
+
extras to be installed.
|
|
13
|
+
|
|
14
|
+
Third parties extend the package via ``register_artist``,
|
|
15
|
+
``register_tag``, and ``register_provider``.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING, Any
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from tesorotools.providers.bde import BdeProvider
|
|
22
|
+
from tesorotools.providers.ecb import EcbProvider
|
|
23
|
+
|
|
24
|
+
from tesorotools._registry import (
|
|
25
|
+
Artist,
|
|
26
|
+
get_artist,
|
|
27
|
+
get_provider,
|
|
28
|
+
register_artist,
|
|
29
|
+
register_provider,
|
|
30
|
+
register_tag,
|
|
31
|
+
)
|
|
32
|
+
from tesorotools.artists import (
|
|
33
|
+
Format,
|
|
34
|
+
HorizontalBarChart,
|
|
35
|
+
Legend,
|
|
36
|
+
LinePlot,
|
|
37
|
+
StackedAreaPlot,
|
|
38
|
+
StackedBarPlot,
|
|
39
|
+
TypeCurve,
|
|
40
|
+
)
|
|
41
|
+
from tesorotools.providers.base import DataProvider
|
|
42
|
+
from tesorotools.render import (
|
|
43
|
+
Content,
|
|
44
|
+
Image,
|
|
45
|
+
Images,
|
|
46
|
+
Report,
|
|
47
|
+
Section,
|
|
48
|
+
Subtitle,
|
|
49
|
+
Table,
|
|
50
|
+
Text,
|
|
51
|
+
Title,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _register_builtins() -> None:
|
|
56
|
+
register_artist("line_plot", LinePlot)
|
|
57
|
+
register_artist("stacked_area", StackedAreaPlot)
|
|
58
|
+
register_artist("stacked_bar", StackedBarPlot)
|
|
59
|
+
register_artist("barh", HorizontalBarChart)
|
|
60
|
+
register_artist("type_curve", TypeCurve)
|
|
61
|
+
|
|
62
|
+
register_tag("format", Format)
|
|
63
|
+
register_tag("legend", Legend)
|
|
64
|
+
register_tag("report", Report)
|
|
65
|
+
register_tag("section", Section)
|
|
66
|
+
register_tag("image", Image)
|
|
67
|
+
register_tag("images", Images)
|
|
68
|
+
register_tag("table", Table)
|
|
69
|
+
register_tag("text", Text)
|
|
70
|
+
register_tag("title", Title)
|
|
71
|
+
register_tag("subtitle", Subtitle)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_register_builtins()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = [
|
|
78
|
+
"Artist",
|
|
79
|
+
"BdeProvider",
|
|
80
|
+
"Content",
|
|
81
|
+
"DataProvider",
|
|
82
|
+
"EcbProvider",
|
|
83
|
+
"Format",
|
|
84
|
+
"HorizontalBarChart",
|
|
85
|
+
"Image",
|
|
86
|
+
"Images",
|
|
87
|
+
"Legend",
|
|
88
|
+
"LinePlot",
|
|
89
|
+
"Report",
|
|
90
|
+
"Section",
|
|
91
|
+
"StackedAreaPlot",
|
|
92
|
+
"StackedBarPlot",
|
|
93
|
+
"Subtitle",
|
|
94
|
+
"Table",
|
|
95
|
+
"Text",
|
|
96
|
+
"Title",
|
|
97
|
+
"TypeCurve",
|
|
98
|
+
"get_artist",
|
|
99
|
+
"get_provider",
|
|
100
|
+
"register_artist",
|
|
101
|
+
"register_provider",
|
|
102
|
+
"register_tag",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def __getattr__(name: str) -> Any:
|
|
107
|
+
if name == "BdeProvider":
|
|
108
|
+
from tesorotools.providers.bde import BdeProvider
|
|
109
|
+
|
|
110
|
+
return BdeProvider
|
|
111
|
+
if name == "EcbProvider":
|
|
112
|
+
from tesorotools.providers.ecb import EcbProvider
|
|
113
|
+
|
|
114
|
+
return EcbProvider
|
|
115
|
+
raise AttributeError(f"module 'tesorotools' has no attribute {name!r}")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Registries for artists, providers, and YAML tags.
|
|
2
|
+
|
|
3
|
+
Single source of truth for "name -> class" lookups used both
|
|
4
|
+
by the YAML loader (``TemplateLoader``) and by code that
|
|
5
|
+
dispatches by string name.
|
|
6
|
+
|
|
7
|
+
Three public entry points:
|
|
8
|
+
|
|
9
|
+
``register_artist(name, cls)``
|
|
10
|
+
Adds ``cls`` to the artist registry **and** registers the
|
|
11
|
+
YAML tag ``!{name}`` via ``cls.from_yaml``. Use for
|
|
12
|
+
chart/figure classes (LinePlot, StackedAreaPlot, etc.).
|
|
13
|
+
|
|
14
|
+
``register_tag(name, constructor)``
|
|
15
|
+
Registers a YAML tag ``!{name}`` only. Use for things
|
|
16
|
+
that exist as YAML constructors but are not artists or
|
|
17
|
+
providers (Format, Legend, Title, Subtitle, Section,
|
|
18
|
+
Image, Text, Table, Report).
|
|
19
|
+
|
|
20
|
+
``register_provider(name, cls)``
|
|
21
|
+
Adds ``cls`` to the provider registry. Programmatic
|
|
22
|
+
only -- providers do not appear in YAML today.
|
|
23
|
+
|
|
24
|
+
Look up registered classes via ``get_artist`` / ``get_provider``;
|
|
25
|
+
both raise ``KeyError`` listing the available names.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import Any, Callable, Protocol, cast
|
|
31
|
+
|
|
32
|
+
from yaml.nodes import MappingNode
|
|
33
|
+
|
|
34
|
+
from tesorotools.providers.base import DataProvider
|
|
35
|
+
from tesorotools.utils.template import TemplateLoader
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Artist(Protocol):
|
|
39
|
+
"""Anything constructible from a YAML mapping node.
|
|
40
|
+
|
|
41
|
+
Concrete artists satisfy this Protocol structurally;
|
|
42
|
+
inheriting from it is not required.
|
|
43
|
+
|
|
44
|
+
The ``loader`` parameter is typed as ``Any`` because some
|
|
45
|
+
in-tree ``from_yaml`` classmethods are declared with
|
|
46
|
+
``yaml.Loader`` (the base class) and others with
|
|
47
|
+
``TemplateLoader``; both work at runtime since the loader
|
|
48
|
+
is always a ``TemplateLoader``.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_yaml(cls, loader: Any, node: MappingNode) -> Any: ...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
YamlConstructor = Callable[[Any, MappingNode], Any]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
_ARTIST_REGISTRY: dict[str, type[Artist]] = {}
|
|
59
|
+
_PROVIDER_REGISTRY: dict[str, type[DataProvider]] = {}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def register_artist(name: str, cls: type[Artist]) -> None:
|
|
63
|
+
"""Register ``cls`` as the artist for ``name``.
|
|
64
|
+
|
|
65
|
+
Adds ``cls`` to the artist registry and binds the YAML
|
|
66
|
+
tag ``!{name}`` to ``cls.from_yaml``. Re-registering the
|
|
67
|
+
same name overrides both bindings.
|
|
68
|
+
"""
|
|
69
|
+
_ARTIST_REGISTRY[name] = cls
|
|
70
|
+
TemplateLoader.add_constructor(
|
|
71
|
+
f"!{name}", cast(YamlConstructor, cls.from_yaml)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_artist(name: str) -> type[Artist]:
|
|
76
|
+
try:
|
|
77
|
+
return _ARTIST_REGISTRY[name]
|
|
78
|
+
except KeyError:
|
|
79
|
+
available = sorted(_ARTIST_REGISTRY)
|
|
80
|
+
raise KeyError(
|
|
81
|
+
f"No artist registered as {name!r}. Available: {available}"
|
|
82
|
+
) from None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def register_provider(name: str, cls: type[DataProvider]) -> None:
|
|
86
|
+
"""Register ``cls`` as the provider for ``name``."""
|
|
87
|
+
_PROVIDER_REGISTRY[name] = cls
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_provider(name: str) -> type[DataProvider]:
|
|
91
|
+
try:
|
|
92
|
+
return _PROVIDER_REGISTRY[name]
|
|
93
|
+
except KeyError:
|
|
94
|
+
available = sorted(_PROVIDER_REGISTRY)
|
|
95
|
+
raise KeyError(
|
|
96
|
+
f"No provider registered as {name!r}. Available: {available}"
|
|
97
|
+
) from None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def register_tag(
|
|
101
|
+
name: str,
|
|
102
|
+
constructor: type[Artist] | YamlConstructor,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Register a YAML tag ``!{name}``.
|
|
105
|
+
|
|
106
|
+
Accepts either a class with a ``from_yaml`` classmethod
|
|
107
|
+
or a bare callable matching the loader signature. Use
|
|
108
|
+
``register_artist`` / ``register_provider`` instead when
|
|
109
|
+
the registered name should also be looked up by code.
|
|
110
|
+
"""
|
|
111
|
+
if isinstance(constructor, type):
|
|
112
|
+
cls = cast(type[Artist], constructor)
|
|
113
|
+
TemplateLoader.add_constructor(
|
|
114
|
+
f"!{name}", cast(YamlConstructor, cls.from_yaml)
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
TemplateLoader.add_constructor(f"!{name}", constructor)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Public artist API.
|
|
2
|
+
|
|
3
|
+
Importing this module applies the matplotlib stylesheet
|
|
4
|
+
defined by ``tesorotools.utils.globals.STYLE_SHEET``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import matplotlib.style
|
|
8
|
+
|
|
9
|
+
from tesorotools.artists.barh_plot import HorizontalBarChart
|
|
10
|
+
from tesorotools.artists.line_plot import Format, Legend, LinePlot
|
|
11
|
+
from tesorotools.artists.stacked import StackedAreaPlot, StackedBarPlot
|
|
12
|
+
from tesorotools.artists.type_curve import TypeCurve
|
|
13
|
+
from tesorotools.utils.globals import STYLE_SHEET
|
|
14
|
+
|
|
15
|
+
matplotlib.style.use(STYLE_SHEET)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Format",
|
|
19
|
+
"HorizontalBarChart",
|
|
20
|
+
"Legend",
|
|
21
|
+
"LinePlot",
|
|
22
|
+
"StackedAreaPlot",
|
|
23
|
+
"StackedBarPlot",
|
|
24
|
+
"TypeCurve",
|
|
25
|
+
]
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Self
|
|
5
|
+
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from matplotlib.axes import Axes
|
|
9
|
+
from matplotlib.figure import Figure
|
|
10
|
+
from pandas import Timestamp
|
|
11
|
+
from yaml.nodes import MappingNode
|
|
12
|
+
|
|
13
|
+
from tesorotools.artists.line_plot import (
|
|
14
|
+
AX_CONFIG,
|
|
15
|
+
FIG_CONFIG,
|
|
16
|
+
Format,
|
|
17
|
+
Legend,
|
|
18
|
+
style_baseline,
|
|
19
|
+
style_spines,
|
|
20
|
+
)
|
|
21
|
+
from tesorotools.utils.matplotlib import (
|
|
22
|
+
PLOT_CONFIG,
|
|
23
|
+
format_annotation,
|
|
24
|
+
load_fonts,
|
|
25
|
+
)
|
|
26
|
+
from tesorotools.utils.template import TemplateLoader
|
|
27
|
+
|
|
28
|
+
TYPE_CURVE_CONFIG: dict[str, Any] = PLOT_CONFIG["type_curve"]
|
|
29
|
+
|
|
30
|
+
load_fonts()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _rotate_xticks(ax: Axes) -> None:
|
|
34
|
+
ax.set_xticks( # type: ignore[reportUnknownMemberType]
|
|
35
|
+
ax.get_xticks(),
|
|
36
|
+
ax.get_xticklabels(), # type: ignore[reportArgumentType]
|
|
37
|
+
rotation=45,
|
|
38
|
+
ha="right",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _format_data(data: pd.DataFrame) -> dict[str, Any]:
|
|
43
|
+
"""Reduce raw daily data to the five series the chart draws.
|
|
44
|
+
|
|
45
|
+
Returns max/min envelopes for the current and previous
|
|
46
|
+
years plus the snapshot of the most recent observation.
|
|
47
|
+
"""
|
|
48
|
+
date_index: pd.DatetimeIndex = data.index # type: ignore[assignment]
|
|
49
|
+
current_date: Timestamp = date_index.max() # type: ignore[assignment]
|
|
50
|
+
current_year: int = current_date.year
|
|
51
|
+
last_year: int = (current_date - pd.DateOffset(years=1)).year
|
|
52
|
+
|
|
53
|
+
current_data: pd.Series[Any] = data.loc[current_date, :] # type: ignore[assignment]
|
|
54
|
+
current_data.name = "current_data"
|
|
55
|
+
|
|
56
|
+
current_year_data: pd.DataFrame = data.loc[
|
|
57
|
+
date_index.year == current_year, :
|
|
58
|
+
]
|
|
59
|
+
current_year_max: pd.Series[Any] = current_year_data.max()
|
|
60
|
+
current_year_max.name = "current_year_max"
|
|
61
|
+
current_year_min: pd.Series[Any] = current_year_data.min()
|
|
62
|
+
current_year_min.name = "current_year_min"
|
|
63
|
+
|
|
64
|
+
last_year_data: pd.DataFrame = data.loc[date_index.year == last_year, :]
|
|
65
|
+
last_year_max: pd.Series[Any] = last_year_data.max()
|
|
66
|
+
last_year_max.name = "last_year_max"
|
|
67
|
+
last_year_min: pd.Series[Any] = last_year_data.min()
|
|
68
|
+
last_year_min.name = "last_year_min"
|
|
69
|
+
|
|
70
|
+
formatted_data: pd.DataFrame = pd.concat(
|
|
71
|
+
[
|
|
72
|
+
last_year_max,
|
|
73
|
+
last_year_min,
|
|
74
|
+
current_year_max,
|
|
75
|
+
current_year_min,
|
|
76
|
+
current_data,
|
|
77
|
+
],
|
|
78
|
+
axis=1,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"data": formatted_data,
|
|
83
|
+
"current_date": current_date.strftime("%d/%m/%Y"),
|
|
84
|
+
"current_year": current_year,
|
|
85
|
+
"last_year": last_year,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TypeCurve:
|
|
90
|
+
"""Type-curve chart with the tesorotools visual style.
|
|
91
|
+
|
|
92
|
+
Plots two ranges (last year and current year) as filled
|
|
93
|
+
bands plus the latest snapshot as a marked line, with
|
|
94
|
+
the due-period axis on x and the rate/yield on y.
|
|
95
|
+
|
|
96
|
+
Parameters mirror ``LinePlot`` / ``StackedAreaPlot``
|
|
97
|
+
where applicable; visual constants (band colors, line
|
|
98
|
+
width, marker) come from ``PLOT_CONFIG['type_curve']``.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
out_path: Path,
|
|
104
|
+
data: pd.DataFrame,
|
|
105
|
+
series: dict[str, str],
|
|
106
|
+
*,
|
|
107
|
+
scale: float = 1,
|
|
108
|
+
fmt: Format | None = None,
|
|
109
|
+
legend: Legend | None = None,
|
|
110
|
+
figsize: tuple[float, float] | None = None,
|
|
111
|
+
points_to_mark: list[str] | None = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
if out_path.suffix != ".png":
|
|
114
|
+
raise ValueError(f"out_path must be .png: {out_path}")
|
|
115
|
+
if len(series) < 2:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
"A type curve must have at least two due periods. "
|
|
118
|
+
f"Given: {list(series)}"
|
|
119
|
+
)
|
|
120
|
+
self.out_path = out_path
|
|
121
|
+
self.data = data
|
|
122
|
+
self.series = series
|
|
123
|
+
self.scale = scale
|
|
124
|
+
self.fmt = fmt or Format()
|
|
125
|
+
self.legend = legend
|
|
126
|
+
self.figsize = figsize
|
|
127
|
+
self.points_to_mark = points_to_mark or []
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
|
|
131
|
+
cfg: dict[str, Any] = loader.construct_mapping( # type: ignore[assignment]
|
|
132
|
+
node, deep=True
|
|
133
|
+
)
|
|
134
|
+
cfg.pop("id")
|
|
135
|
+
cfg["out_path"] = Path(cfg["out_path"])
|
|
136
|
+
cfg["data"] = pd.read_feather(cfg.pop("data_path"))
|
|
137
|
+
return cls(**cfg)
|
|
138
|
+
|
|
139
|
+
def plot(self) -> Axes:
|
|
140
|
+
plot_data = self.data.loc[:, list(self.series.keys())]
|
|
141
|
+
plot_data = plot_data.rename(columns=self.series)
|
|
142
|
+
plot_data = plot_data * self.scale
|
|
143
|
+
|
|
144
|
+
formatted_assets = _format_data(plot_data)
|
|
145
|
+
formatted_data: pd.DataFrame = formatted_assets["data"]
|
|
146
|
+
due_index: pd.Index[Any] = formatted_data.index
|
|
147
|
+
|
|
148
|
+
fig_kw = dict(FIG_CONFIG)
|
|
149
|
+
if self.figsize is not None:
|
|
150
|
+
fig_kw["figsize"] = self.figsize
|
|
151
|
+
fig: Figure = plt.figure( # type: ignore[reportUnknownMemberType]
|
|
152
|
+
**fig_kw
|
|
153
|
+
)
|
|
154
|
+
ax: Axes = fig.add_subplot()
|
|
155
|
+
|
|
156
|
+
last_config: dict[str, Any] = TYPE_CURVE_CONFIG["last"]
|
|
157
|
+
ax.fill_between( # type: ignore[reportUnknownMemberType]
|
|
158
|
+
due_index,
|
|
159
|
+
formatted_data["last_year_min"],
|
|
160
|
+
formatted_data["last_year_max"],
|
|
161
|
+
alpha=last_config["alpha"],
|
|
162
|
+
color=last_config["color"],
|
|
163
|
+
edgecolor=None,
|
|
164
|
+
label=f"Rango {formatted_assets['last_year']}",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
current_config: dict[str, Any] = TYPE_CURVE_CONFIG["current"]
|
|
168
|
+
ax.fill_between( # type: ignore[reportUnknownMemberType]
|
|
169
|
+
due_index,
|
|
170
|
+
formatted_data["current_year_min"],
|
|
171
|
+
formatted_data["current_year_max"],
|
|
172
|
+
alpha=current_config["alpha"],
|
|
173
|
+
color=current_config["color"],
|
|
174
|
+
edgecolor=None,
|
|
175
|
+
label=f"Rango {formatted_assets['current_year']}",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
line_config: dict[str, Any] = TYPE_CURVE_CONFIG["line"]
|
|
179
|
+
current = formatted_data["current_data"]
|
|
180
|
+
current.plot(
|
|
181
|
+
ax=ax,
|
|
182
|
+
color=line_config["color"],
|
|
183
|
+
linewidth=line_config["linewidth"],
|
|
184
|
+
label=formatted_assets["current_date"],
|
|
185
|
+
)
|
|
186
|
+
for code in self.points_to_mark:
|
|
187
|
+
if code not in self.series:
|
|
188
|
+
raise KeyError(
|
|
189
|
+
f"points_to_mark entry {code!r} not in series keys: "
|
|
190
|
+
f"{list(self.series)}"
|
|
191
|
+
)
|
|
192
|
+
label = self.series[code]
|
|
193
|
+
value: float = current.loc[label]
|
|
194
|
+
ax.plot( # type: ignore[reportUnknownMemberType]
|
|
195
|
+
label,
|
|
196
|
+
value,
|
|
197
|
+
marker=line_config["marker"],
|
|
198
|
+
color=line_config["color"],
|
|
199
|
+
)
|
|
200
|
+
ax.annotate( # type: ignore[reportUnknownMemberType]
|
|
201
|
+
format_annotation(value, self.fmt.decimals, self.fmt.units),
|
|
202
|
+
(label, value), # type: ignore[reportArgumentType]
|
|
203
|
+
textcoords="offset points",
|
|
204
|
+
xytext=(0, 10),
|
|
205
|
+
ha="center",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
style_spines(
|
|
209
|
+
ax,
|
|
210
|
+
decimals=self.fmt.decimals,
|
|
211
|
+
units=self.fmt.units,
|
|
212
|
+
**AX_CONFIG["spines"],
|
|
213
|
+
)
|
|
214
|
+
_rotate_xticks(ax)
|
|
215
|
+
style_baseline(ax, 0, **AX_CONFIG["baseline"])
|
|
216
|
+
|
|
217
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
218
|
+
ncol = self.legend.ncol if (self.legend and self.legend.ncol) else 3
|
|
219
|
+
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
220
|
+
handles,
|
|
221
|
+
labels,
|
|
222
|
+
loc="outside lower center",
|
|
223
|
+
ncol=ncol,
|
|
224
|
+
)
|
|
225
|
+
fig.savefig( # type: ignore[reportUnknownMemberType]
|
|
226
|
+
self.out_path
|
|
227
|
+
)
|
|
228
|
+
plt.close(fig)
|
|
229
|
+
return ax
|
|
@@ -6,9 +6,9 @@ from typing import Any
|
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
8
|
from tesorotools.artists.barh_plot import plot_barh_charts_from_flash
|
|
9
|
-
from tesorotools.artists.line_plot import plot_line_charts
|
|
9
|
+
from tesorotools.artists.line_plot import Format, plot_line_charts
|
|
10
10
|
from tesorotools.artists.table import generate_tables_from_flash
|
|
11
|
-
from tesorotools.artists.type_curve import
|
|
11
|
+
from tesorotools.artists.type_curve import TypeCurve
|
|
12
12
|
from tesorotools.dependencies.resolution import (
|
|
13
13
|
compute_derivate_series,
|
|
14
14
|
concat_derivate_series,
|
|
@@ -105,5 +105,19 @@ if __name__ == "__main__":
|
|
|
105
105
|
trimmed_df: pd.DataFrame = trim(full_df) # type: ignore[assignment]
|
|
106
106
|
plot_barh_charts_from_flash(Path("."), full_df, barh_config_dicts)
|
|
107
107
|
plot_line_charts(Path("."), trimmed_df, line_config_dicts)
|
|
108
|
-
|
|
108
|
+
for name, curve_cfg in type_config_dicts.items():
|
|
109
|
+
if name.startswith("."):
|
|
110
|
+
continue
|
|
111
|
+
yaxis_cfg: dict[str, Any] = curve_cfg.get("yaxis", {})
|
|
112
|
+
line_cfg: dict[str, Any] = curve_cfg.get("line", {})
|
|
113
|
+
TypeCurve(
|
|
114
|
+
out_path=Path(".") / f"{name}.png",
|
|
115
|
+
data=trimmed_df,
|
|
116
|
+
series=curve_cfg["series"],
|
|
117
|
+
fmt=Format(
|
|
118
|
+
units=yaxis_cfg.get("units", ""),
|
|
119
|
+
decimals=yaxis_cfg.get("decimals", 0),
|
|
120
|
+
),
|
|
121
|
+
points_to_mark=line_cfg.get("points_to_mark"),
|
|
122
|
+
).plot()
|
|
109
123
|
generate_tables_from_flash(Path("."), full_df, table_config_dicts)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Public provider API.
|
|
2
|
+
|
|
3
|
+
``BdeProvider`` and ``EcbProvider`` depend on the optional
|
|
4
|
+
``[bde]`` / ``[ecb]`` extras (which install ``requests``)
|
|
5
|
+
and are imported lazily through ``__getattr__``; importing
|
|
6
|
+
``tesorotools.providers`` itself does not require those
|
|
7
|
+
extras.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from tesorotools.providers.base import DataProvider
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from tesorotools.providers.bde import BdeProvider
|
|
16
|
+
from tesorotools.providers.ecb import EcbProvider
|
|
17
|
+
|
|
18
|
+
__all__ = ["BdeProvider", "DataProvider", "EcbProvider"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def __getattr__(name: str) -> Any:
|
|
22
|
+
if name == "BdeProvider":
|
|
23
|
+
from tesorotools.providers.bde import BdeProvider
|
|
24
|
+
|
|
25
|
+
return BdeProvider
|
|
26
|
+
if name == "EcbProvider":
|
|
27
|
+
from tesorotools.providers.ecb import EcbProvider
|
|
28
|
+
|
|
29
|
+
return EcbProvider
|
|
30
|
+
raise AttributeError(
|
|
31
|
+
f"module 'tesorotools.providers' has no attribute {name!r}"
|
|
32
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Public render API."""
|
|
2
|
+
|
|
3
|
+
from tesorotools.render.content.content import Content
|
|
4
|
+
from tesorotools.render.content.images import Image, Images
|
|
5
|
+
from tesorotools.render.content.section import Section
|
|
6
|
+
from tesorotools.render.content.subtitle import Subtitle
|
|
7
|
+
from tesorotools.render.content.table import Table
|
|
8
|
+
from tesorotools.render.content.text import Text
|
|
9
|
+
from tesorotools.render.content.title import Title
|
|
10
|
+
from tesorotools.render.report import Report
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Content",
|
|
14
|
+
"Image",
|
|
15
|
+
"Images",
|
|
16
|
+
"Report",
|
|
17
|
+
"Section",
|
|
18
|
+
"Subtitle",
|
|
19
|
+
"Table",
|
|
20
|
+
"Text",
|
|
21
|
+
"Title",
|
|
22
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from tesorotools.utils.template import TemplateLoader
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def clean_config_dicts(
|
|
10
|
+
config_dicts: dict[str, Any],
|
|
11
|
+
) -> dict[str, Any]:
|
|
12
|
+
return {k: v for k, v in config_dicts.items() if not k.startswith(".")}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def read_config(
|
|
16
|
+
config_file: Path,
|
|
17
|
+
loader: type[yaml.FullLoader] | None = None,
|
|
18
|
+
clean: bool = True,
|
|
19
|
+
) -> Any:
|
|
20
|
+
actual_loader: type[yaml.FullLoader] = (
|
|
21
|
+
yaml.FullLoader if loader is None else TemplateLoader
|
|
22
|
+
)
|
|
23
|
+
with open(config_file, encoding="utf8") as file:
|
|
24
|
+
config_dict: Any = yaml.load(file, Loader=actual_loader)
|
|
25
|
+
if clean and isinstance(config_dict, dict):
|
|
26
|
+
config_dict = clean_config_dicts(config_dict) # type: ignore[reportUnknownArgumentType]
|
|
27
|
+
return config_dict
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def merge(a: dict[str, Any], b: dict[str, Any]) -> dict[str, Any]:
|
|
31
|
+
# a overrides
|
|
32
|
+
for key in b:
|
|
33
|
+
if key in a:
|
|
34
|
+
if isinstance(a[key], dict) and isinstance(b[key], dict):
|
|
35
|
+
merge(a[key], b[key])
|
|
36
|
+
else:
|
|
37
|
+
a[key] = b[key]
|
|
38
|
+
return a
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
import matplotlib.pyplot as plt
|
|
7
|
-
import pandas as pd
|
|
8
|
-
from matplotlib.axes import Axes
|
|
9
|
-
from matplotlib.figure import Figure
|
|
10
|
-
from pandas import Timestamp
|
|
11
|
-
|
|
12
|
-
from tesorotools.artists.line_plot import (
|
|
13
|
-
style_baseline,
|
|
14
|
-
style_spines,
|
|
15
|
-
)
|
|
16
|
-
from tesorotools.utils.config import merge
|
|
17
|
-
from tesorotools.utils.matplotlib import (
|
|
18
|
-
PLOT_CONFIG,
|
|
19
|
-
format_annotation,
|
|
20
|
-
load_fonts,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
TYPE_CURVE_CONFIG: dict[str, Any] = PLOT_CONFIG["type_curve"]
|
|
24
|
-
AX_CONFIG: dict[str, Any] = PLOT_CONFIG["ax"]
|
|
25
|
-
FIG_CONFIG: dict[str, Any] = PLOT_CONFIG["figure"]
|
|
26
|
-
|
|
27
|
-
load_fonts()
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _rotate_xticks(ax: Axes) -> None:
|
|
31
|
-
ax.set_xticks( # type: ignore[reportUnknownMemberType]
|
|
32
|
-
ax.get_xticks(),
|
|
33
|
-
ax.get_xticklabels(), # type: ignore[reportArgumentType]
|
|
34
|
-
rotation=45,
|
|
35
|
-
ha="right",
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def _format_data(data: pd.DataFrame) -> dict[str, Any]:
|
|
40
|
-
# metadata
|
|
41
|
-
date_index: pd.DatetimeIndex = data.index # type: ignore[assignment]
|
|
42
|
-
current_date: Timestamp = date_index.max() # type: ignore[assignment]
|
|
43
|
-
current_year: int = current_date.year
|
|
44
|
-
last_year: int = (current_date - pd.DateOffset(years=1)).year
|
|
45
|
-
|
|
46
|
-
# current data
|
|
47
|
-
current_data: pd.Series[Any] = data.loc[current_date, :] # type: ignore[assignment]
|
|
48
|
-
current_data.name = "current_data"
|
|
49
|
-
|
|
50
|
-
# current year
|
|
51
|
-
current_year_data: pd.DataFrame = data.loc[
|
|
52
|
-
date_index.year == current_year, :
|
|
53
|
-
]
|
|
54
|
-
current_year_max: pd.Series[Any] = current_year_data.max()
|
|
55
|
-
current_year_max.name = "current_year_max"
|
|
56
|
-
current_year_min: pd.Series[Any] = current_year_data.min()
|
|
57
|
-
current_year_min.name = "current_year_min"
|
|
58
|
-
|
|
59
|
-
# last year
|
|
60
|
-
last_year_data: pd.DataFrame = data.loc[date_index.year == last_year, :]
|
|
61
|
-
last_year_max: pd.Series[Any] = last_year_data.max()
|
|
62
|
-
last_year_max.name = "last_year_max"
|
|
63
|
-
last_year_min: pd.Series[Any] = last_year_data.min()
|
|
64
|
-
last_year_min.name = "last_year_min"
|
|
65
|
-
|
|
66
|
-
formatted_data: pd.DataFrame = pd.concat(
|
|
67
|
-
[
|
|
68
|
-
last_year_max,
|
|
69
|
-
last_year_min,
|
|
70
|
-
current_year_max,
|
|
71
|
-
current_year_min,
|
|
72
|
-
current_data,
|
|
73
|
-
],
|
|
74
|
-
axis=1,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
"data": formatted_data,
|
|
79
|
-
"current_date": current_date.strftime("%d/%m/%Y"),
|
|
80
|
-
"current_year": current_year,
|
|
81
|
-
"last_year": last_year,
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _plot_current_data(
|
|
86
|
-
ax: Axes,
|
|
87
|
-
data: pd.Series[Any],
|
|
88
|
-
date_fmt: str,
|
|
89
|
-
*,
|
|
90
|
-
linewidth: float,
|
|
91
|
-
marker: str,
|
|
92
|
-
points_to_mark: list[str],
|
|
93
|
-
color: str,
|
|
94
|
-
decimals: int,
|
|
95
|
-
units: str,
|
|
96
|
-
) -> None:
|
|
97
|
-
data.plot(
|
|
98
|
-
ax=ax,
|
|
99
|
-
color=color,
|
|
100
|
-
linewidth=linewidth,
|
|
101
|
-
label=date_fmt,
|
|
102
|
-
)
|
|
103
|
-
for point in points_to_mark:
|
|
104
|
-
value: float = data.loc[point]
|
|
105
|
-
ax.plot( # type: ignore[reportUnknownMemberType]
|
|
106
|
-
point,
|
|
107
|
-
value,
|
|
108
|
-
marker=marker,
|
|
109
|
-
color=color,
|
|
110
|
-
)
|
|
111
|
-
ax.annotate( # type: ignore[reportUnknownMemberType]
|
|
112
|
-
format_annotation(value, decimals, units),
|
|
113
|
-
(point, value), # type: ignore[reportArgumentType]
|
|
114
|
-
textcoords="offset points",
|
|
115
|
-
xytext=(0, 10),
|
|
116
|
-
ha="center",
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def plot_type_curve(
|
|
121
|
-
data: pd.DataFrame,
|
|
122
|
-
out_file: Path,
|
|
123
|
-
**config: Any,
|
|
124
|
-
) -> None:
|
|
125
|
-
merged_config: dict[str, Any] = merge(config, TYPE_CURVE_CONFIG)
|
|
126
|
-
|
|
127
|
-
if merged_config["yaxis"]["units"] == "p.b.":
|
|
128
|
-
data = data * 100
|
|
129
|
-
|
|
130
|
-
formatted_assets: dict[str, Any] = _format_data(data)
|
|
131
|
-
formatted_data: pd.DataFrame = formatted_assets["data"]
|
|
132
|
-
due_index: pd.Index[Any] = formatted_data.index
|
|
133
|
-
|
|
134
|
-
fig: Figure = plt.figure( # type: ignore[reportUnknownMemberType]
|
|
135
|
-
**FIG_CONFIG
|
|
136
|
-
)
|
|
137
|
-
ax: Axes = fig.add_subplot()
|
|
138
|
-
|
|
139
|
-
last_config: dict[str, Any] = merged_config["last"]
|
|
140
|
-
ax.fill_between( # type: ignore[reportUnknownMemberType]
|
|
141
|
-
due_index,
|
|
142
|
-
formatted_data["last_year_min"],
|
|
143
|
-
formatted_data["last_year_max"],
|
|
144
|
-
alpha=last_config["alpha"],
|
|
145
|
-
color=last_config["color"],
|
|
146
|
-
edgecolor=None,
|
|
147
|
-
label=f"Rango {formatted_assets['last_year']}",
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
current_config: dict[str, Any] = merged_config["current"]
|
|
151
|
-
ax.fill_between( # type: ignore[reportUnknownMemberType]
|
|
152
|
-
due_index,
|
|
153
|
-
formatted_data["current_year_min"],
|
|
154
|
-
formatted_data["current_year_max"],
|
|
155
|
-
alpha=current_config["alpha"],
|
|
156
|
-
color=current_config["color"],
|
|
157
|
-
edgecolor=None,
|
|
158
|
-
label=f"Rango {formatted_assets['current_year']}",
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
_plot_current_data(
|
|
162
|
-
ax,
|
|
163
|
-
formatted_data["current_data"],
|
|
164
|
-
formatted_assets["current_date"],
|
|
165
|
-
**merged_config["line"],
|
|
166
|
-
)
|
|
167
|
-
style_spines(ax, **merged_config["yaxis"], **AX_CONFIG["spines"])
|
|
168
|
-
_rotate_xticks(ax)
|
|
169
|
-
style_baseline(ax, 0, **AX_CONFIG["baseline"])
|
|
170
|
-
handles, labels = ax.get_legend_handles_labels()
|
|
171
|
-
fig.legend( # type: ignore[reportUnknownMemberType]
|
|
172
|
-
handles,
|
|
173
|
-
labels,
|
|
174
|
-
loc="outside lower center",
|
|
175
|
-
ncol=3,
|
|
176
|
-
)
|
|
177
|
-
fig.savefig( # type: ignore[reportUnknownMemberType]
|
|
178
|
-
out_file
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# data is expected to be a simple time series data, columns are series and rows represents dates
|
|
183
|
-
def plot_type_curves(
|
|
184
|
-
out_path: Path,
|
|
185
|
-
data: pd.DataFrame,
|
|
186
|
-
config_dicts: dict[str, Any],
|
|
187
|
-
) -> None:
|
|
188
|
-
for name, config in config_dicts.items():
|
|
189
|
-
if not name.startswith("."): # aux entries
|
|
190
|
-
series: dict[str, str] = config["series"]
|
|
191
|
-
if len(series) < 2:
|
|
192
|
-
raise ValueError(
|
|
193
|
-
f"In plot {name}: A type curve must have at least two due periods. Given periods: {series.keys()}"
|
|
194
|
-
)
|
|
195
|
-
trimmed_data: pd.DataFrame = data.loc[:, series.keys()]
|
|
196
|
-
trimmed_data = trimmed_data.rename(columns=series)
|
|
197
|
-
plot_type_curve(
|
|
198
|
-
data=trimmed_data,
|
|
199
|
-
out_file=out_path / f"{name}.png",
|
|
200
|
-
**config,
|
|
201
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
import yaml
|
|
5
|
-
|
|
6
|
-
from tesorotools.utils.template import TemplateLoader
|
|
7
|
-
|
|
8
|
-
_tags_registered = False
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _register_all_tags() -> None:
|
|
12
|
-
"""Register every YAML tag on TemplateLoader.
|
|
13
|
-
|
|
14
|
-
Called once, lazily, the first time ``read_config`` is
|
|
15
|
-
invoked with ``loader=TemplateLoader``. This removes
|
|
16
|
-
the need for consumers to import ``tesorotools.render``
|
|
17
|
-
or ``tesorotools.artists`` just for their side effects.
|
|
18
|
-
"""
|
|
19
|
-
global _tags_registered # noqa: PLW0603
|
|
20
|
-
if _tags_registered:
|
|
21
|
-
return
|
|
22
|
-
_tags_registered = True
|
|
23
|
-
|
|
24
|
-
# -- artists tags --
|
|
25
|
-
from tesorotools.artists.barh_plot import (
|
|
26
|
-
HorizontalBarChart,
|
|
27
|
-
)
|
|
28
|
-
from tesorotools.artists.line_plot import (
|
|
29
|
-
Format,
|
|
30
|
-
Legend,
|
|
31
|
-
LinePlot,
|
|
32
|
-
)
|
|
33
|
-
from tesorotools.artists.stacked import (
|
|
34
|
-
StackedAreaPlot,
|
|
35
|
-
StackedBarPlot,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
TemplateLoader.add_constructor("!line_plot", LinePlot.from_yaml)
|
|
39
|
-
TemplateLoader.add_constructor("!format", Format.from_yaml)
|
|
40
|
-
TemplateLoader.add_constructor("!legend", Legend.from_yaml)
|
|
41
|
-
TemplateLoader.add_constructor("!stacked_area", StackedAreaPlot.from_yaml)
|
|
42
|
-
TemplateLoader.add_constructor("!stacked_bar", StackedBarPlot.from_yaml)
|
|
43
|
-
TemplateLoader.add_constructor("!barh", HorizontalBarChart.from_yaml)
|
|
44
|
-
|
|
45
|
-
# -- render tags --
|
|
46
|
-
from tesorotools.render.content.images import (
|
|
47
|
-
Image,
|
|
48
|
-
Images,
|
|
49
|
-
)
|
|
50
|
-
from tesorotools.render.content.section import Section
|
|
51
|
-
from tesorotools.render.content.subtitle import Subtitle
|
|
52
|
-
from tesorotools.render.content.table import Table
|
|
53
|
-
from tesorotools.render.content.text import Text
|
|
54
|
-
from tesorotools.render.content.title import Title
|
|
55
|
-
from tesorotools.render.report import Report
|
|
56
|
-
|
|
57
|
-
TemplateLoader.add_constructor("!report", Report.from_yaml)
|
|
58
|
-
TemplateLoader.add_constructor("!section", Section.from_yaml)
|
|
59
|
-
TemplateLoader.add_constructor("!image", Image.from_yaml)
|
|
60
|
-
TemplateLoader.add_constructor("!images", Images.from_yaml)
|
|
61
|
-
TemplateLoader.add_constructor("!table", Table.from_yaml)
|
|
62
|
-
TemplateLoader.add_constructor("!text", Text.from_yaml)
|
|
63
|
-
TemplateLoader.add_constructor("!title", Title.from_yaml)
|
|
64
|
-
TemplateLoader.add_constructor("!subtitle", Subtitle.from_yaml)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def clean_config_dicts(
|
|
68
|
-
config_dicts: dict[str, Any],
|
|
69
|
-
) -> dict[str, Any]:
|
|
70
|
-
return {k: v for k, v in config_dicts.items() if not k.startswith(".")}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def read_config(
|
|
74
|
-
config_file: Path,
|
|
75
|
-
loader: type[yaml.FullLoader] | None = None,
|
|
76
|
-
clean: bool = True,
|
|
77
|
-
) -> Any:
|
|
78
|
-
actual_loader: type[yaml.FullLoader] = (
|
|
79
|
-
yaml.FullLoader if loader is None else TemplateLoader
|
|
80
|
-
)
|
|
81
|
-
if actual_loader is TemplateLoader:
|
|
82
|
-
_register_all_tags()
|
|
83
|
-
with open(config_file, encoding="utf8") as file:
|
|
84
|
-
config_dict: Any = yaml.load(file, Loader=actual_loader)
|
|
85
|
-
if clean and isinstance(config_dict, dict):
|
|
86
|
-
config_dict = clean_config_dicts(config_dict) # type: ignore[reportUnknownArgumentType]
|
|
87
|
-
return config_dict
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def merge(a: dict[str, Any], b: dict[str, Any]) -> dict[str, Any]:
|
|
91
|
-
# a overrides
|
|
92
|
-
for key in b:
|
|
93
|
-
if key in a:
|
|
94
|
-
if isinstance(a[key], dict) and isinstance(b[key], dict):
|
|
95
|
-
merge(a[key], b[key])
|
|
96
|
-
else:
|
|
97
|
-
a[key] = b[key]
|
|
98
|
-
return a
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/artists/barh_plot.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/artists/line_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
|
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/fonts/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/assets/tesoro.mplstyle
RENAMED
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/data_sources/debug.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/database/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/dependencies/node.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/dependencies/resolution.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
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/pipeline/diagnose.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
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/content.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/images.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/section.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/subtitle.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/table.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/src/tesorotools/render/content/text.py
RENAMED
|
File without changes
|
{tesorotools_python-0.0.35 → tesorotools_python-0.0.36}/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
|