tesorotools-python 0.0.18__py3-none-any.whl

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.
Files changed (53) hide show
  1. tesorotools/__init__.py +6 -0
  2. tesorotools/artists/__init__.py +5 -0
  3. tesorotools/artists/barh_plot.py +310 -0
  4. tesorotools/artists/line_plot.py +245 -0
  5. tesorotools/artists/table.py +200 -0
  6. tesorotools/artists/type_curve.py +218 -0
  7. tesorotools/assets/README.md +5 -0
  8. tesorotools/assets/fonts/CabinetGrotesk-Black.otf +0 -0
  9. tesorotools/assets/fonts/CabinetGrotesk-Bold.otf +0 -0
  10. tesorotools/assets/fonts/CabinetGrotesk-Extrabold.otf +0 -0
  11. tesorotools/assets/fonts/CabinetGrotesk-Extralight.otf +0 -0
  12. tesorotools/assets/fonts/CabinetGrotesk-Light.otf +0 -0
  13. tesorotools/assets/fonts/CabinetGrotesk-Medium.otf +0 -0
  14. tesorotools/assets/fonts/CabinetGrotesk-Regular.otf +0 -0
  15. tesorotools/assets/fonts/CabinetGrotesk-Thin.otf +0 -0
  16. tesorotools/assets/fonts/README.md +1 -0
  17. tesorotools/assets/plots.yaml +43 -0
  18. tesorotools/assets/tesoro.mplstyle +21 -0
  19. tesorotools/convert.py +99 -0
  20. tesorotools/data_sources/README.md +14 -0
  21. tesorotools/data_sources/__init__.py +0 -0
  22. tesorotools/data_sources/debug.py +26 -0
  23. tesorotools/data_sources/lseg.py +117 -0
  24. tesorotools/database/__init__.py +0 -0
  25. tesorotools/database/push.py +70 -0
  26. tesorotools/dependencies/__init__.py +0 -0
  27. tesorotools/dependencies/functions.py +11 -0
  28. tesorotools/dependencies/node.py +34 -0
  29. tesorotools/dependencies/resolution.py +118 -0
  30. tesorotools/main.py +37 -0
  31. tesorotools/offsets/__init__.py +0 -0
  32. tesorotools/offsets/offsets.py +439 -0
  33. tesorotools/offsets/outliers.py +15 -0
  34. tesorotools/render/__init__.py +17 -0
  35. tesorotools/render/content/__init__.py +0 -0
  36. tesorotools/render/content/content.py +17 -0
  37. tesorotools/render/content/images.py +147 -0
  38. tesorotools/render/content/section.py +53 -0
  39. tesorotools/render/content/subtitle.py +53 -0
  40. tesorotools/render/content/table.py +308 -0
  41. tesorotools/render/content/text.py +23 -0
  42. tesorotools/render/content/title.py +40 -0
  43. tesorotools/render/report.py +31 -0
  44. tesorotools/utils/__init__.py +0 -0
  45. tesorotools/utils/config.py +35 -0
  46. tesorotools/utils/globals.py +14 -0
  47. tesorotools/utils/matplotlib.py +38 -0
  48. tesorotools/utils/series.py +40 -0
  49. tesorotools/utils/shortcuts.py +32 -0
  50. tesorotools/utils/template.py +126 -0
  51. tesorotools_python-0.0.18.dist-info/METADATA +16 -0
  52. tesorotools_python-0.0.18.dist-info/RECORD +53 -0
  53. tesorotools_python-0.0.18.dist-info/WHEEL +4 -0
@@ -0,0 +1,53 @@
1
+ from datetime import date, datetime, time
2
+ from typing import Any, Self
3
+
4
+ from babel.dates import format_datetime
5
+ from docx.document import Document
6
+ from yaml import MappingNode
7
+
8
+ from tesorotools.utils.config import TemplateLoader
9
+
10
+
11
+ class Subtitle:
12
+ def __init__(self, date_time: datetime | None = None) -> None:
13
+ if date_time is None:
14
+ date_time: datetime = datetime.now()
15
+ self._date_time: datetime = date_time
16
+
17
+ @classmethod
18
+ def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
19
+ subtitle_cfg: dict[str, Any] = loader.construct_mapping(node, deep=True)
20
+ # parse date
21
+ date_cfg: str | date | None = subtitle_cfg.get("date", None)
22
+ if date_cfg is None:
23
+ date_time: datetime = datetime.now()
24
+ elif isinstance(date_cfg, date):
25
+ date_time: datetime = datetime.combine(date_cfg, time.min)
26
+ elif isinstance(date_cfg, str):
27
+ date_time: datetime = datetime.strptime(date_cfg, "%Y-%m-%d")
28
+
29
+ # parse hour
30
+ hour_cfg: str | None = subtitle_cfg.get("hour", None)
31
+ if hour_cfg is not None:
32
+ hour_str, minute_str = hour_cfg.split(sep=":")
33
+ date_time = date_time.replace(
34
+ hour=int(hour_str), minute=int(minute_str)
35
+ )
36
+
37
+ return cls(date_time)
38
+
39
+ def set_time(self, hour: int, minute: int) -> None:
40
+ self._date_time = self._date_time.replace(hour=hour, minute=minute)
41
+
42
+ def _format_datetime(self) -> str:
43
+ date_fmt: str = format_datetime(
44
+ self._date_time, "EEEE, dd 'de' MMMM 'de' yyyy, HH:mm", locale="es"
45
+ )
46
+ date_fmt = date_fmt.capitalize()
47
+ return date_fmt
48
+
49
+ def render(self, document: Document) -> Document:
50
+ date_fmt = self._format_datetime()
51
+ document.add_paragraph(date_fmt, style="Subtitle")
52
+ document.add_paragraph()
53
+ return document
@@ -0,0 +1,308 @@
1
+ from pathlib import Path
2
+ from typing import Any, Self
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from docx.document import Document
7
+ from docx.enum.table import WD_ALIGN_VERTICAL, WD_TABLE_ALIGNMENT
8
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
9
+ from docx.oxml import OxmlElement, parse_xml
10
+ from docx.oxml.ns import nsdecls, qn
11
+ from docx.shared import Inches, Pt, RGBColor
12
+ from docx.table import Table as TableDocx
13
+ from docx.table import _Cell as TableCell
14
+ from yaml import MappingNode
15
+
16
+ from tesorotools.utils.config import read_config
17
+ from tesorotools.utils.globals import PLOT_CONFIG_FILE
18
+ from tesorotools.utils.template import TemplateLoader
19
+
20
+ RENDER_CONFIG: dict[str, Any] = read_config(PLOT_CONFIG_FILE)["table"]
21
+
22
+ TEXTO_TABLAS = 9
23
+
24
+ CENTER = WD_ALIGN_PARAGRAPH.CENTER
25
+
26
+
27
+ def _set_cell_border(cell: TableCell, **kwargs):
28
+ """
29
+ Set cell`s border
30
+ Usage:
31
+
32
+ set_cell_border(
33
+ cell,
34
+ top={"sz": 12, "val": "single", "color": "#FF0000", "space": "0"},
35
+ bottom={"sz": 12, "color": "#00FF00", "val": "single"},
36
+ start={"sz": 24, "val": "dashed", "shadow": "true"},
37
+ end={"sz": 12, "val": "dashed"},
38
+ )
39
+ """
40
+ tc = cell._tc
41
+ tcPr = tc.get_or_add_tcPr()
42
+
43
+ # check for tag existence, if none found, create one
44
+ tcBorders = tcPr.first_child_found_in("w:tcBorders")
45
+ if tcBorders is None:
46
+ tcBorders = OxmlElement("w:tcBorders")
47
+ tcPr.append(tcBorders)
48
+
49
+ # list over all available tags
50
+ for edge in ("start", "top", "end", "bottom", "insideH", "insideV"):
51
+ edge_data = kwargs.get(edge)
52
+ if edge_data:
53
+ tag = "w:{}".format(edge)
54
+
55
+ # check for tag existence, if none found, then create one
56
+ element = tcBorders.find(qn(tag))
57
+ if element is None:
58
+ element = OxmlElement(tag)
59
+ tcBorders.append(element)
60
+
61
+ # looks like order of attributes is important
62
+ for key in ["sz", "val", "color", "space", "shadow"]:
63
+ if key in edge_data:
64
+ element.set(qn("w:{}".format(key)), str(edge_data[key]))
65
+
66
+
67
+ def _style_horizontal_blocks_header(cell: TableCell):
68
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
69
+ cell.paragraphs[0].runs[0].font.size = Pt(12)
70
+
71
+
72
+ def _horizontal_blocks_header(columns: pd.MultiIndex, table_docx: TableDocx):
73
+ column_counter: int = 1
74
+ blocks: list[str] = list(columns.get_level_values(level=0).unique())
75
+ for block in blocks:
76
+ cell: TableCell = table_docx.cell(0, column_counter)
77
+ columns_to_merge: int = len(
78
+ columns[columns.get_level_values(level=0) == block]
79
+ )
80
+ for _ in range(columns_to_merge - 1):
81
+ column_counter = column_counter + 1
82
+ cell.merge(table_docx.cell(0, column_counter))
83
+ column_counter = column_counter + 1
84
+ cell.text = block
85
+ _style_horizontal_blocks_header(cell)
86
+
87
+
88
+ def _style_column_names(cell: TableCell):
89
+ cell.paragraphs[0].runs[0].font.size = Pt(10)
90
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
91
+ cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
92
+
93
+
94
+ def _fill_column_names(
95
+ table: pd.DataFrame, table_docx: TableDocx, horizontal: bool
96
+ ):
97
+ if horizontal:
98
+ start_row: int = 1
99
+ _horizontal_blocks_header(table.columns, table_docx)
100
+ columns: np.ndarray = table.columns.get_level_values(level=1).values
101
+ else:
102
+ start_row: int = 0
103
+ columns: np.ndarray = table.columns.values
104
+
105
+ for idx, column_name in enumerate(columns, start=1):
106
+ cell: TableCell = table_docx.cell(start_row, idx)
107
+ cell.text = column_name
108
+ _style_column_names(cell)
109
+
110
+
111
+ def _style_index_names(cell: TableCell):
112
+ cell.paragraphs[0].runs[0].font.size = Pt(TEXTO_TABLAS)
113
+ cell.paragraphs[0].runs[0].font.bold = True
114
+ cell.width = Inches(1)
115
+
116
+
117
+ def _fill_index_names(
118
+ index: pd.Index | pd.MultiIndex, table_docx: TableDocx, horizontal: bool
119
+ ):
120
+ start_row: int = 2 if horizontal else 1
121
+
122
+ index_names: pd.Index = (
123
+ index
124
+ if (horizontal or (not isinstance(index, pd.MultiIndex)))
125
+ else index.get_level_values(level=1)
126
+ )
127
+
128
+ for idx, name in enumerate(index_names, start=start_row):
129
+ cell: TableCell = table_docx.cell(idx, 0)
130
+ cell.text = name
131
+ _style_index_names(cell)
132
+
133
+
134
+ # we only separate blocks in vertically stacked tables
135
+ def _separate_blocks(index: pd.MultiIndex, table_docx: TableDocx):
136
+ blocks: list[str] = list(index.get_level_values(level=0).unique())
137
+ previous_rows = 0
138
+ for block in blocks[:-1]:
139
+ block_size: int = len(index[index.get_level_values(level=0) == block])
140
+ for cell in table_docx.rows[block_size + previous_rows].cells:
141
+ _separate_cell(cell)
142
+ previous_rows += block_size
143
+
144
+
145
+ def _separate_cell(cell: TableCell):
146
+ _set_cell_border(
147
+ cell,
148
+ bottom={
149
+ "sz": 1,
150
+ "val": "double",
151
+ "color": "#000000",
152
+ "space": 2,
153
+ },
154
+ )
155
+
156
+
157
+ def _is_bright(hex_color):
158
+ red = int(hex_color[:2], 16)
159
+ green = int(hex_color[2:4], 16)
160
+ blue = int(hex_color[4:], 16)
161
+ luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue
162
+ return luminance > 180
163
+
164
+
165
+ def _shade_cell(cell: TableCell, hex_color: str):
166
+ bright = _is_bright(hex_color)
167
+ shading_element = parse_xml(
168
+ r'<w:shd {} w:fill="{hex_color}"/>'.format(
169
+ nsdecls("w"), hex_color=hex_color
170
+ )
171
+ )
172
+ cell._tc.get_or_add_tcPr().append(shading_element)
173
+ if bright:
174
+ cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(0, 0, 0)
175
+ else:
176
+ cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 255, 255)
177
+
178
+
179
+ def _style_content(cell: TableCell):
180
+ cell.paragraphs[0].runs[0].font.size = Pt(TEXTO_TABLAS)
181
+ cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.RIGHT
182
+ cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
183
+
184
+
185
+ def _fill_content(
186
+ table: pd.DataFrame,
187
+ color_table: pd.DataFrame,
188
+ shade_table: pd.DataFrame,
189
+ table_docx: TableDocx,
190
+ horizontal: bool,
191
+ ):
192
+ start_row: int = 2 if horizontal else 1
193
+ values = table.values
194
+ for (x, y), value in np.ndenumerate(values):
195
+ cell: TableCell = table_docx.cell(x + start_row, y + 1)
196
+ cell.text = value if value is not None else ""
197
+ color: str | None = (
198
+ color_table.values[x, y] if color_table is not None else None
199
+ )
200
+ shade: str | None = (
201
+ shade_table.values[x, y] if shade_table is not None else None
202
+ )
203
+ if color is not None:
204
+ cell.paragraphs[0].runs[0].font.color.rgb = RGBColor.from_string(
205
+ color
206
+ )
207
+ if shade is not None:
208
+ _shade_cell(cell, shade)
209
+ _style_content(cell)
210
+
211
+
212
+ def _style_table(table_docx: TableDocx):
213
+ table_docx.style = RENDER_CONFIG.get("style", None)
214
+ table_docx.autofit = RENDER_CONFIG["autofit"]
215
+
216
+
217
+ def render_table(
218
+ table: pd.DataFrame,
219
+ color_table: pd.DataFrame,
220
+ shade_table: pd.DataFrame,
221
+ document: Document,
222
+ block_sep: bool,
223
+ **kwargs,
224
+ ) -> TableDocx:
225
+
226
+ horizontal: bool = isinstance(table.columns, pd.MultiIndex)
227
+ table_docx: TableDocx = document.add_table(
228
+ rows=len(table.index) + table.columns.nlevels,
229
+ cols=len(table.columns) + 1,
230
+ )
231
+
232
+ _style_table(table_docx)
233
+ _fill_column_names(table, table_docx, horizontal)
234
+ _fill_index_names(
235
+ index=table.index, table_docx=table_docx, horizontal=horizontal
236
+ )
237
+ if block_sep:
238
+ _separate_blocks(table.index, table_docx)
239
+ _fill_content(table, color_table, shade_table, table_docx, horizontal)
240
+ table_docx.alignment = WD_TABLE_ALIGNMENT.CENTER
241
+ return document
242
+
243
+
244
+ class Table:
245
+ """A rendered table in the document"""
246
+
247
+ def __init__(
248
+ self,
249
+ data_file: Path | None = None,
250
+ color_file: Path | None = None,
251
+ shade_file: Path | None = None,
252
+ block_sep: bool = False,
253
+ title: str | None = None,
254
+ ):
255
+ if (
256
+ (data_file is None)
257
+ and (color_file is None)
258
+ and (shade_file is None)
259
+ ):
260
+ raise ValueError("At least a piece of data should be given")
261
+ self._data: pd.DataFrame | None = (
262
+ pd.read_feather(data_file) if data_file is not None else None
263
+ )
264
+ self._color: pd.DataFrame | None = (
265
+ pd.read_feather(color_file) if color_file is not None else None
266
+ )
267
+ self._shade: pd.DataFrame | None = (
268
+ pd.read_feather(shade_file) if shade_file is not None else None
269
+ )
270
+ self._title: str | None = title
271
+ self._block_sep: bool = block_sep
272
+
273
+ @classmethod
274
+ def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
275
+ table_cfg: dict[str, Any] = loader.construct_mapping(node, deep=True)
276
+ root_path: Path = loader.imports["table"]
277
+ file_prefix: str = table_cfg.pop("id")
278
+
279
+ # the data file has an optional "_data" suffix
280
+ # if a file with the suffix exists, it will be preferred
281
+ data_file_suffix: Path = root_path / f"{file_prefix}_data.feather"
282
+ if data_file_suffix.exists():
283
+ data_file: Path = data_file_suffix
284
+ else:
285
+ data_file: Path = root_path / f"{file_prefix}.feather"
286
+
287
+ color_file: Path = root_path / f"{file_prefix}_color.feather"
288
+ shade_file: Path = root_path / f"{file_prefix}_shade.feather"
289
+ return cls(
290
+ data_file,
291
+ color_file=color_file if color_file.exists() else None,
292
+ shade_file=shade_file if shade_file.exists() else None,
293
+ **table_cfg,
294
+ )
295
+
296
+ def render(self, document: Document) -> Document:
297
+ heading = document.add_heading(self._title, level=2)
298
+ heading.alignment = CENTER
299
+ heading.runs[0].font.size = Pt(10)
300
+
301
+ render_table(
302
+ self._data,
303
+ self._color,
304
+ self._shade,
305
+ document,
306
+ block_sep=self._block_sep,
307
+ )
308
+ return document
@@ -0,0 +1,23 @@
1
+ from typing import Any, Self
2
+
3
+ from docx.document import Document
4
+ from yaml import Loader, MappingNode
5
+
6
+ from tesorotools.render.content.content import Content
7
+
8
+
9
+ class Text:
10
+ def __init__(self, text: str) -> None:
11
+ self._text = text
12
+
13
+ @classmethod
14
+ def from_yaml(cls, loader: Loader, node: MappingNode) -> Self:
15
+ values: dict[str, Any] = loader.construct_mapping(node, deep=True)
16
+ values.pop("id")
17
+ text: str = values.pop("text", None)
18
+ section: Self = cls(text=text)
19
+ return section
20
+
21
+ def render(self, document: Document) -> Document:
22
+ document.add_paragraph(self._text)
23
+ return document
@@ -0,0 +1,40 @@
1
+ from typing import Any, Self
2
+
3
+ from docx.document import Document
4
+ from yaml import MappingNode
5
+
6
+ from tesorotools.utils.config import TemplateLoader
7
+
8
+
9
+ class Title:
10
+ def __init__(
11
+ self, title: str | None = None, comment: str | None = None
12
+ ) -> None:
13
+ self._title: str = "" if title is None else title
14
+ self._comment: str = "" if comment is None else comment
15
+
16
+ @classmethod
17
+ def from_yaml(cls, loader: TemplateLoader, node: MappingNode) -> Self:
18
+ title_cfg: dict[str, Any] = loader.construct_mapping(node, deep=True)
19
+ title: str | None = title_cfg.get("title", None)
20
+ comment: str | None = title_cfg.get("comment", None)
21
+ return cls(title, comment)
22
+
23
+ def render(self, document: Document) -> Document:
24
+ if (self.title == "") and (self.comment == ""):
25
+ title_text: str = ""
26
+ elif (self.title == "") and (self.comment != ""):
27
+ title_text: str = self.comment
28
+ else:
29
+ title_text: str = f"{self.title}: {self.comment}"
30
+ # Use the "Title" style in the word template
31
+ document.add_heading(text=title_text, level=0)
32
+ return document
33
+
34
+ @property
35
+ def title(self) -> str:
36
+ return self._title
37
+
38
+ @property
39
+ def comment(self) -> str:
40
+ return self._comment
@@ -0,0 +1,31 @@
1
+ from typing import Any, Self
2
+
3
+ from docx.document import Document
4
+ from yaml import Loader, MappingNode
5
+
6
+ from tesorotools.render.content.content import Content
7
+ from tesorotools.render.content.title import Title
8
+
9
+
10
+ class Report:
11
+ def __init__(
12
+ self,
13
+ title: str,
14
+ contents: dict[str, Content],
15
+ ):
16
+ self.title: str = title
17
+ self.contents: dict[str, Content] = contents
18
+
19
+ @classmethod
20
+ def from_yaml(cls, loader: Loader, node: MappingNode) -> Self:
21
+ report_cfg: dict[str, Any] = loader.construct_mapping(node, deep=True)
22
+ title: str = report_cfg.pop("id")
23
+ return cls(title=title, contents=report_cfg)
24
+
25
+ def render(self, document: Document) -> Document:
26
+ document._body.clear_content()
27
+ for _, content in self.contents.items():
28
+ content.render(document)
29
+ if not isinstance(content, Title):
30
+ document.add_paragraph()
31
+ return document
File without changes
@@ -0,0 +1,35 @@
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(config_dicts: dict[str, Any]):
10
+ if config_dicts is None:
11
+ return None
12
+ return {k: v for k, v in config_dicts.items() if not k.startswith(".")}
13
+
14
+
15
+ # maybe a dedicated function for templates would be nice
16
+ def read_config(
17
+ config_file: Path, loader: yaml.FullLoader = None, clean: bool = True
18
+ ) -> Any | dict:
19
+ loader = yaml.FullLoader if loader is None else TemplateLoader
20
+ with open(config_file, encoding="utf8") as file:
21
+ config_dict = yaml.load(file, Loader=loader)
22
+ if clean and isinstance(config_dict, dict):
23
+ config_dict = clean_config_dicts(config_dict)
24
+ return config_dict
25
+
26
+
27
+ def merge(a: dict, b: dict):
28
+ # a overrides
29
+ for key in b:
30
+ if key in a:
31
+ if isinstance(a[key], dict) and isinstance(b[key], dict):
32
+ merge(a[key], b[key])
33
+ else:
34
+ a[key] = b[key]
35
+ return a
@@ -0,0 +1,14 @@
1
+ from pathlib import Path
2
+
3
+ BASE_PATH: Path = Path(__file__).parent.parent
4
+
5
+ DEBUG: Path = Path("debug")
6
+ CONFIG: Path = Path("config")
7
+ EXAMPLES: Path = Path("examples")
8
+
9
+ # various assets for the plots
10
+ ASSETS: Path = BASE_PATH / "assets"
11
+ FONTS: Path = ASSETS / "fonts"
12
+ STYLE_SHEET: Path = ASSETS / "tesoro.mplstyle"
13
+
14
+ PLOT_CONFIG_FILE: Path = ASSETS / "plots.yaml"
@@ -0,0 +1,38 @@
1
+ from typing import Any
2
+
3
+ import matplotlib
4
+ import matplotlib.font_manager
5
+
6
+ from .config import read_config
7
+ from .globals import FONTS, PLOT_CONFIG_FILE
8
+
9
+ # this should only be done once
10
+ PLOT_CONFIG: dict[str, Any] = read_config(PLOT_CONFIG_FILE)
11
+
12
+
13
+ def load_fonts() -> None:
14
+ for font in FONTS.iterdir():
15
+ if font.suffix == ".otf":
16
+ matplotlib.font_manager.fontManager.addfont(font)
17
+ matplotlib.rcParams["font.family"] = PLOT_CONFIG["style"]["font"]
18
+
19
+
20
+ # this is not really matplotlib specific, so it should be elsewhere
21
+ def format_annotation(value: float, decimals: int, units: str) -> str:
22
+ decimal_formatted: str = f"{value:_.{decimals}f}".replace(".", ",").replace(
23
+ "_", "."
24
+ )
25
+ decimal_formatted = (
26
+ decimal_formatted[1:]
27
+ if (decimal_formatted.startswith("-0") and decimals == 0)
28
+ else decimal_formatted
29
+ )
30
+ return f"{decimal_formatted}{units}"
31
+
32
+
33
+ def is_zero(value: float, decimals: int) -> bool:
34
+ formatted = format_annotation(value, decimals, units="")
35
+ unique = "".join(set(formatted.replace(",", "").replace(".", "")))
36
+ if unique == "0":
37
+ return True
38
+ return False
@@ -0,0 +1,40 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+
4
+ import yaml
5
+
6
+ from tesorotools.dependencies.node import Node
7
+ from tesorotools.dependencies.resolution import resolve_series
8
+
9
+
10
+ def compile_series(
11
+ config_dicts: list[dict[str, Any]],
12
+ dependencies_cfg: dict[str, Any],
13
+ out: Path,
14
+ ) -> dict[str, list[str]]:
15
+ """Generates a document with all the series used in the document. Useful when you want to download them all at once."""
16
+
17
+ resolved_dict = resolve_series(
18
+ config_dicts,
19
+ dependencies_cfg,
20
+ )
21
+
22
+ dependent_series: list[Node] = resolved_dict["dependent"]
23
+ dependent_series_id_list: list[str] = [
24
+ node.name for node in dependent_series
25
+ ]
26
+ independent_series_id_list: list[str] = list(resolved_dict["independent"])
27
+ result = {
28
+ "dependent": dependent_series_id_list,
29
+ "independent": independent_series_id_list,
30
+ }
31
+
32
+ with open(out, "w", encoding="utf-8") as stream:
33
+ comment: str = "# Autogenerated document, do not touch!\n\n"
34
+ stream.write(comment)
35
+ yaml.dump(
36
+ result,
37
+ stream,
38
+ )
39
+
40
+ return result
@@ -0,0 +1,32 @@
1
+ import platform
2
+ from pathlib import Path
3
+
4
+ import win32com.client
5
+ from win32com.client import CDispatch
6
+
7
+
8
+ # this a good candidate to be added to tesorotools-python
9
+ def resolve_shortcut(shortcut: Path) -> Path:
10
+ """Returns the real path behind a shortcut
11
+
12
+ Parameters
13
+ ----------
14
+ shortcut : Path
15
+ Path of the shortcut
16
+
17
+ Returns
18
+ -------
19
+ Path
20
+ Real path behind the shortcut
21
+ """
22
+ system: str = platform.system()
23
+ if system == "Linux":
24
+ # In linux this is straightforward
25
+ return shortcut
26
+ elif system == "Windows":
27
+ # Little workaround with windows
28
+ shell: CDispatch = win32com.client.Dispatch("WScript.Shell")
29
+ return Path(shell.CreateShortCut(str(shortcut)).Targetpath)
30
+ else:
31
+ # Just return the same if we don't know the OS
32
+ return shortcut