basic-report 0.1.0__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 (38) hide show
  1. basic_report/__init__.py +10 -0
  2. basic_report/configs/default.yaml +185 -0
  3. basic_report/css_and_scripts/bootstrap.min.css +7 -0
  4. basic_report/css_and_scripts/bootstrap.min.js +7 -0
  5. basic_report/css_and_scripts/dataTables.bootstrap4.min.js +8 -0
  6. basic_report/css_and_scripts/jquery-3.3.1.slim.min.js +2 -0
  7. basic_report/css_and_scripts/jquery.dataTables.min.js +166 -0
  8. basic_report/css_and_scripts/popper.min.js +5 -0
  9. basic_report/page.py +827 -0
  10. basic_report/report.py +404 -0
  11. basic_report/templates/accordion.css +20 -0
  12. basic_report/templates/accordion.html +20 -0
  13. basic_report/templates/base.html +59 -0
  14. basic_report/templates/colors.css +30 -0
  15. basic_report/templates/columns.html +9 -0
  16. basic_report/templates/global_links.html +7 -0
  17. basic_report/templates/header.html +5 -0
  18. basic_report/templates/image.html +16 -0
  19. basic_report/templates/list.html +7 -0
  20. basic_report/templates/navbar.css +17 -0
  21. basic_report/templates/navbar_left.html +32 -0
  22. basic_report/templates/navbar_right.html +32 -0
  23. basic_report/templates/navbar_top.html +25 -0
  24. basic_report/templates/plot.html +4 -0
  25. basic_report/templates/report_ball_section.html +80 -0
  26. basic_report/templates/report_header.html +6 -0
  27. basic_report/templates/site.css +5 -0
  28. basic_report/templates/sublevel.html +7 -0
  29. basic_report/templates/table.css +51 -0
  30. basic_report/templates/table.html +55 -0
  31. basic_report/templates/tabs.css +20 -0
  32. basic_report/templates/tabs.html +29 -0
  33. basic_report/templates/text.html +3 -0
  34. basic_report/utils.py +246 -0
  35. basic_report-0.1.0.dist-info/METADATA +194 -0
  36. basic_report-0.1.0.dist-info/RECORD +38 -0
  37. basic_report-0.1.0.dist-info/WHEEL +4 -0
  38. basic_report-0.1.0.dist-info/licenses/LICENSE.md +21 -0
@@ -0,0 +1,25 @@
1
+ <div class="container p-2">
2
+ <ul class="nav nav-pills" role="tablist">
3
+ {% for item in navbar_items -%}
4
+ <li class="nav-item p-1 pill-{{ color }}">
5
+ {% if loop.index == 1 %}
6
+ <a class="nav-link active" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
7
+ {% else %}
8
+ <a class="nav-link" data-toggle="pill" href="#{{ item[1] }}" role="tab">{{ item[0] }}</a>
9
+ {% endif %}
10
+ </li>
11
+ {%- endfor %}
12
+ </ul>
13
+ </div>
14
+
15
+ <div class="container p-2">
16
+ <div class="tab-content">
17
+ {% for item in navbar_items -%}
18
+ {% if loop.index == 1 %}
19
+ <div class="tab-pane fade show active" id="{{ item[1] }}">{{ item[2] }}</div>
20
+ {% else %}
21
+ <div class="tab-pane fade" id="{{ item[1] }}">{{ item[2] }}</div>
22
+ {% endif %}
23
+ {% endfor %}
24
+ </div>
25
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class='container p-2'>
2
+ {{ script | safe }}
3
+ {{ div | safe }}
4
+ </div>
@@ -0,0 +1,80 @@
1
+ <p>
2
+ <div class="container">
3
+ <div class="row justify-content-center">
4
+ <div class="col-3">
5
+ {%- if errors|length > 0 %}
6
+ <button class="btn btn-danger btn-block" type="button" data-toggle="collapse" data-target="#report_ball_errors">
7
+ Errors <span class="badge badge-light"> {{ errors|length }} </span>
8
+ </button>
9
+ {%- else %}
10
+ <button class="btn btn-outline-dark btn-block disabled" type="button">
11
+ No Errors
12
+ </button>
13
+ {%- endif %}
14
+ </div>
15
+ <div class="col-3">
16
+ {%- if warnings|length > 0 %}
17
+ <button class="btn btn-warning btn-block" type="button" data-toggle="collapse" data-target="#report_ball_warnings">
18
+ Warnings <span class="badge badge-light"> {{ warnings|length }} </span>
19
+ </button>
20
+ {%- else %}
21
+ <button class="btn btn-outline-dark btn-block disabled" type="button">
22
+ No Warnings
23
+ </button>
24
+ {%- endif %}
25
+ </div>
26
+ <div class="col-3">
27
+ {%- if info|length > 0 %}
28
+ <button class="btn btn-success btn-block" type="button" data-toggle="collapse" data-target="#report_ball_info">
29
+ Info <span class="badge badge-light"> {{ info|length }} </span>
30
+ </button>
31
+ {%- else %}
32
+ <button class="btn btn-outline-dark btn-block disabled" type="button">
33
+ No Info
34
+ </button>
35
+ {%- endif %}
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </p>
40
+
41
+ <p>
42
+ <div class="container p-1">
43
+ {%- if errors|length > 0 %}
44
+ <div class="collapse" id="report_ball_errors">
45
+ <div class="alert alert-danger text-center" role="alert">Errors</div>
46
+ <div class="container">
47
+ <ol>
48
+ {% for item in errors -%}
49
+ <li class="text-{{ color }}">{{ item }}</li>
50
+ {%- endfor %}
51
+ </ol>
52
+ </div>
53
+ </div>
54
+ {%- endif -%}
55
+ {%- if warnings|length > 0 %}
56
+ <div class="collapse" id="report_ball_warnings">
57
+ <div class="alert alert-warning text-center" role="alert">Warnings</div>
58
+ <div class="container">
59
+ <ol>
60
+ {% for item in warnings -%}
61
+ <li class="text-{{ color }}">{{ item }}</li>
62
+ {%- endfor %}
63
+ </ol>
64
+ </div>
65
+ </div>
66
+ {%- endif -%}
67
+ {%- if info|length > 0 %}
68
+ <div class="collapse" id="report_ball_info">
69
+ <div class="alert alert-success text-center" role="alert">Info</div>
70
+ <div class="container">
71
+ <ol>
72
+ {% for item in info -%}
73
+ <li class="text-{{ color }}">{{ item }}</li>
74
+ {%- endfor %}
75
+ </ol>
76
+ </div>
77
+ </div>
78
+ {%- endif -%}
79
+ </div>
80
+ </p>
@@ -0,0 +1,6 @@
1
+ <div class="container p-1 justify-content-center">
2
+ <div class="alert alert-{{ header_color }} text-center shadow-sm p-3 mb-2" role="alert">
3
+ <h4>{{ report_name }}</h4>
4
+ {{ report_creation_time }}
5
+ </div>
6
+ </div>
@@ -0,0 +1,5 @@
1
+ html, body {
2
+ background-color: {{ site_background_color }};
3
+ color: {{ site_face_color }};
4
+ margin: 0;
5
+ }
@@ -0,0 +1,7 @@
1
+ <div class="container p-1">
2
+ <div class="row align-items-{{ align }} justify-content-{{ align }}">
3
+ <div class="col-{{ size }}">
4
+ {{ content }}
5
+ </div>
6
+ </div>
7
+ </div>
@@ -0,0 +1,51 @@
1
+
2
+ /* Search input + length dropdown */
3
+ .dataTables_wrapper .form-control {
4
+ background-color: {{ table_controls_background_color }};
5
+ border: 1px solid {{ table_controls_border_color }};
6
+ color: {{ table_controls_font_color }};
7
+ }
8
+
9
+ /* Focus state */
10
+ .dataTables_wrapper .form-control:focus {
11
+ background-color: {{ table_controls_background_color }};
12
+ border-color: {{ table_controls_border_color }};
13
+ color: {{ table_controls_font_color }};
14
+ box-shadow: none;
15
+ }
16
+
17
+ .dataTables_wrapper {
18
+ color: {{ table_controls_font_color }};
19
+ }
20
+
21
+ .dataTables_wrapper .dataTables_info,
22
+ .dataTables_wrapper .dataTables_length label,
23
+ .dataTables_wrapper .dataTables_filter label {
24
+ color: {{ table_controls_font_color }};
25
+ }
26
+
27
+ /* Pagination container */
28
+ .dataTables_wrapper .pagination .page-link {
29
+ background-color: {{ table_controls_background_color }};
30
+ border: 1px solid {{ table_controls_border_color }};
31
+ color: {{ table_controls_font_color }};
32
+ }
33
+
34
+ /* Hover */
35
+ .dataTables_wrapper .pagination .page-link:hover {
36
+ background-color: {{ table_controls_background_color }};
37
+ border-color: {{ table_pagination_hover_border_color }};
38
+ color: {{ table_pagination_hover_font_color }};
39
+ }
40
+
41
+ /* Active page */
42
+ .dataTables_wrapper .pagination .page-item.active .page-link {
43
+ background-color: {{ table_pagination_active_background_color }};
44
+ border-color: {{ table_pagination_active_border_color }};
45
+ color: {{ table_pagination_active_font_color }};
46
+ }
47
+
48
+ .dataTables_wrapper .pagination .page-link:focus {
49
+ outline: none;
50
+ box-shadow: none;
51
+ }
@@ -0,0 +1,55 @@
1
+ <script>
2
+ var {{ table_id }}DataSet = {{ table_data }};
3
+
4
+ $(document).ready(function() {
5
+ $('#{{ table_id }}').DataTable( {
6
+ data: {{ table_id }}DataSet,
7
+ columns: [
8
+ {% for c in table_columns -%}
9
+ { title: "{{ c }}" },
10
+ {% endfor %} ],
11
+ {% if not 'page' in options -%}
12
+ "paging": false,
13
+ {%- endif %}
14
+ {% if not 'info' in options -%}
15
+ "info": false,
16
+ {%- endif %}
17
+ {% if not 'search' in options -%}
18
+ "searching": false,
19
+ {%- endif %}
20
+ {% if order is not none -%}
21
+ "order": {{ order }},
22
+ {%- endif %}
23
+ {% if 'no_sort' in options -%}
24
+ "ordering": false,
25
+ {%- endif -%}
26
+ "autoWidth": false,
27
+ } );
28
+ } );
29
+ </script>
30
+
31
+ {% if 'full_width' in options %}
32
+ <div class="container-fluid p-2">
33
+ {% else %}
34
+ <div class="container p-2">
35
+ {% endif %}
36
+ <table id="{{ table_id }}" class="table text-{{ align }} text-{{ color }} table-striped table-sm">
37
+ {% if footers|length %}
38
+ <tfoot class="double-top-border">
39
+ {% for foot in footers %}
40
+ <tr>
41
+ {% for item in foot %}
42
+ <td>{{ item }}</td>
43
+ {% endfor %}
44
+ </tr>
45
+ {% endfor %}
46
+ </tfoot>
47
+ {% endif %}
48
+ {% if caption != "" %}
49
+ <caption>
50
+ {{ caption }}
51
+ </caption>
52
+ {% endif %}
53
+ </table>
54
+ </div>
55
+
@@ -0,0 +1,20 @@
1
+ .nav-tabs {
2
+ border-bottom: {{ tab_border_bottom_size }} solid {{ tab_border_bottom_color }};
3
+ }
4
+
5
+ .nav-tabs .nav-link {
6
+ color: {{ tab_inactive_font_color }};
7
+ font-weight: {{ tab_inactive_font_weight }};
8
+ border: none;
9
+ }
10
+
11
+ .nav-tabs .nav-link:hover {
12
+ color: {{ tab_hover_font_color }};
13
+ }
14
+
15
+ .nav-tabs .nav-link.active {
16
+ color: {{ tab_active_font_color }};
17
+ background: {{ tab_active_background_color }};
18
+ font-weight: {{ tab_active_font_weight }};
19
+ border-bottom: {{ tab_active_border_bottom_size }} solid {{ tab_active_border_bottom_color }};
20
+ }
@@ -0,0 +1,29 @@
1
+ <div class="container p-2">
2
+ <ul class="nav nav-tabs">
3
+ {% for item in tabbed_items %}
4
+ <li class="nav-item">
5
+ {% if loop.index == 1 %}
6
+ <a class="nav-link active" data-toggle="tab" href="#{{ item[1] }}">
7
+ {% else %}
8
+ <a class="nav-link" data-toggle="tab" href="#{{ item[1] }}">
9
+ {% endif %}
10
+ {{ item[0] }}
11
+ </a>
12
+ </li>
13
+ {% endfor %}
14
+ </ul>
15
+ </div>
16
+
17
+ <div class="container p-1">
18
+ <div class="tab-content justify-content-center">
19
+ {% for item in tabbed_items %}
20
+ {% if loop.index == 1 %}
21
+ <div class="tab-pane container active " id="{{ item[1] }}">
22
+ {% else %}
23
+ <div class="tab-pane container fade " id="{{ item[1] }}">
24
+ {% endif %}
25
+ {{ item[2] }}
26
+ </div>
27
+ {% endfor %}
28
+ </div>
29
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class="container">
2
+ <p class="text-{{ color }} text-{{ align }}"> {{ text }}</p>
3
+ </div>
basic_report/utils.py ADDED
@@ -0,0 +1,246 @@
1
+ import datetime
2
+ from jinja2 import Environment, FileSystemLoader
3
+ from loguru import logger
4
+ from pathlib import Path
5
+ from typing import Any, Union, Optional
6
+
7
+ from . import TEMPLATE_DIR
8
+
9
+ #----------------------------------------------------------------------------------------------------------------------#
10
+ # region COLOR MAP
11
+ #----------------------------------------------------------------------------------------------------------------------#
12
+ class ColorMap:
13
+ """ Color map object for handling the color space of the report """
14
+ def __init__(self, config: dict[str, Any], color_mode: str, text_color: Optional[str]=None):
15
+ """ Initialize a new color map given a config dict
16
+
17
+ Args:
18
+ config (dict[str, Any]): The configuration for this report. Mainly responsible for defining new color
19
+ profiles that can later be used
20
+ color_mode (str): Select the current color mode of the report, i.e., `dark` or `light`
21
+ text_color (Optional[str]): Override for the text color of the report. For a more granular approach use the
22
+ config file
23
+ """
24
+ self.config = config
25
+ self.color_mode = color_mode
26
+ self.text_color = text_color
27
+
28
+ # Prepare the jinja templater
29
+ self.file_loader = FileSystemLoader(TEMPLATE_DIR)
30
+ self.jinja_env = Environment(loader=self.file_loader)
31
+
32
+ # Default color map - Maps the default bootstrap colors to their respective color names
33
+ self.cm = {
34
+ 'blue': 'primary',
35
+ 'gray': 'secondary',
36
+ 'green': 'success',
37
+ 'red': 'danger',
38
+ 'yellow': 'warning',
39
+ 'teal': 'info',
40
+ 'black': 'dark',
41
+ 'primary': 'primary',
42
+ 'secondary': 'secondary',
43
+ 'success': 'success',
44
+ 'danger': 'danger',
45
+ 'warning': 'warning',
46
+ 'info': 'info',
47
+ 'dark': 'dark',
48
+ 'light': 'light',
49
+ }
50
+
51
+ # Create the custom color CSS that will be injected and make the profiles available in the color map
52
+ for cc in self.config['custom_colors']:
53
+ self.cm[cc] = cc
54
+
55
+ #---------------#
56
+ # Magic Methods #
57
+ #---------------#
58
+ def __getitem__(self, key: str) -> str:
59
+ """ Verify the color name and return the proper color mapping """
60
+ return self.verify_and_get_color(key)
61
+
62
+ #---------#
63
+ # Private #
64
+ #---------#
65
+ def _make_color_css_files(self, output_dir: Path):
66
+ """ Create the custom color css file """
67
+ # Create CSS string
68
+ color_items = [v | {'name': k} for k,v in self.config['custom_colors'].items()]
69
+
70
+ # Color CSS
71
+ template = self.jinja_env.get_template('colors.css')
72
+ css = template.render(color_items=color_items)
73
+
74
+ # Dump to scripts dir
75
+ custom_color_file = output_dir / 'custom_colors.css'
76
+ custom_color_file.write_text(css)
77
+
78
+ # Color CSS
79
+ template = self.jinja_env.get_template('navbar.css')
80
+ css = template.render(color_items=color_items)
81
+
82
+ # Dump to scripts dir
83
+ navbar_file = output_dir / 'navbar.css'
84
+ navbar_file.write_text(css)
85
+
86
+ def _make_css_file(self, element_type: str, output_dir: Path):
87
+ """ Create the custom css files for the element type """
88
+ # Create CSS string
89
+ colors = self.config['css'][element_type][self.color_mode]
90
+ template = self.jinja_env.get_template(f'{element_type}.css')
91
+ css = template.render(**colors)
92
+
93
+ # Dump to scripts dir
94
+ css_file = output_dir / f'{element_type}.css'
95
+ css_file.write_text(css)
96
+
97
+ #-----#
98
+ # API #
99
+ #-----#
100
+ def verify_and_get_color(self, color: str) -> str:
101
+ """ Check that the requested color is actually available
102
+
103
+ Args:
104
+ color (str): Color to check
105
+
106
+ Returns:
107
+ str - color name to use
108
+ """
109
+ if color not in self.cm:
110
+ msg = f'No color `{color}` found. Available: {self.cm.keys()}'
111
+ logger.error(msg)
112
+ raise RuntimeError(msg)
113
+
114
+ return self.cm[color]
115
+
116
+ def get_default_color(self, field: str) -> str:
117
+ """ Determine the correct default color for a given color mode and field
118
+
119
+ Args:
120
+ field (str): The type/field the color should be used for
121
+ color_mode (str): The color mode of the report, i.e., either `light` or `dark`
122
+
123
+ Returns:
124
+ str - The color to be used
125
+ """
126
+ if self.text_color is None or field not in ['text', 'table', 'report_ball']:
127
+ color = self.config['default_color_profiles'][self.color_mode].get(field)
128
+ else:
129
+ color = self.text_color
130
+
131
+ if color is None:
132
+ msg = f'No default color defined for `{field}` with color mode `{self.color_mode}`'
133
+ logger.error(msg)
134
+ raise RuntimeError(msg)
135
+ return color
136
+
137
+ def make_css_files(self, output_dir: Path):
138
+ """ Create the custom css files for the site
139
+
140
+ Args:
141
+ output_dir (Path): The reports css and script dir
142
+ """
143
+ self._make_color_css_files(output_dir)
144
+ for element_type in ['site', 'tabs', 'accordion', 'table']:
145
+ self._make_css_file(element_type, output_dir)
146
+
147
+ #----------------------------------------------------------------------------------------------------------------------#
148
+ # region MISC HELPER
149
+ #----------------------------------------------------------------------------------------------------------------------#
150
+ def coerce_to_date(date: Union[str, int, datetime.date, datetime.datetime]) -> datetime.date:
151
+ """ Coerce random data formats to a datetime.date object
152
+
153
+ Args:
154
+ date (Union[str, int, datetime.date, datetime.datetime]): The date to coerce
155
+
156
+ Returns:
157
+ datetime.date - The coerce date object
158
+ """
159
+ if isinstance(date, datetime.date):
160
+ return date
161
+ elif isinstance(date, datetime.datetime):
162
+ return date.date
163
+ elif isinstance(date, int):
164
+ date = str(int)
165
+
166
+ if not isinstance(date, str):
167
+ msg = f'Cannot coerce date {date}'
168
+ logger.error(msg)
169
+ raise RuntimeError(msg)
170
+
171
+ if len(date) == 6: # noqa: PLR2004
172
+ year = int(date[:2])
173
+ if year < 50: # noqa: PLR2004
174
+ year += 2000
175
+ else:
176
+ year += 1900
177
+ month = int(date[2:4])
178
+ day = int(date[4:])
179
+
180
+ elif len(date) == 8: # noqa: PLR2004
181
+ if date.isdigit():
182
+ year = int(date[:4])
183
+ month = int(date[4:6])
184
+ day = int(date[6:])
185
+ else:
186
+ year = int(date[:2])
187
+ if year < 50: # noqa: PLR2004
188
+ year += 2000
189
+ else:
190
+ year += 1900
191
+ month = int(date[3:5])
192
+ day = int(date[6:])
193
+
194
+ elif len(date) == 10: # noqa: PLR2004
195
+ year = int(date[:4])
196
+ month = int(date[5:7])
197
+ day = int(date[8:])
198
+ return datetime.date(year, month, day)
199
+
200
+ def verify_alignment(align: str) -> str:
201
+ """ Check that the requested alignment is actually available
202
+
203
+ Args:
204
+ align (str): The alignment tag that needs to be verified
205
+
206
+ Returns:
207
+ str - The alignment if it is acceptable
208
+ """
209
+ alignments = ['left', 'center', 'right']
210
+ if align not in alignments:
211
+ msg = f'Unknown alignment {align}. Available: {alignments}'
212
+ logger.error(msg)
213
+ raise RuntimeError(msg)
214
+ return align
215
+
216
+ def verify_color_mode(color_mode: str) -> str:
217
+ """ Check that the requested alignment is actually available
218
+
219
+ Args:
220
+ color_mode (str): The alignment tag that needs to be verified
221
+
222
+ Returns:
223
+ str - The alignment if it is acceptable
224
+ """
225
+ color_modes = ['light', 'dark']
226
+ if color_mode not in color_modes:
227
+ msg = f'Unknown color_mode {color_mode}. Available: {color_modes}'
228
+ logger.error(msg)
229
+ raise RuntimeError(msg)
230
+ return color_mode
231
+
232
+ def default(var: Any, default: Any) -> Any:
233
+ """ Helper function to check kwargs and set them to a default
234
+
235
+ You could simply use var or default. But for future additions we will use a separat function here
236
+ Args:
237
+ var (Any): The input variable
238
+ default (Any): Default value for the variable
239
+
240
+ Returns
241
+ Any - Either the variable itself, or the default value if var is `None`
242
+ """
243
+ if var is None:
244
+ return default
245
+ else:
246
+ return var