layrz-sdk 3.0.14__py3-none-any.whl → 3.1.1__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.

Potentially problematic release.


This version of layrz-sdk might be problematic. Click here for more details.

Files changed (123) hide show
  1. layrz_sdk/__init__.py +1 -1
  2. layrz_sdk/constants.py +5 -0
  3. layrz_sdk/entities/__init__.py +129 -73
  4. layrz_sdk/entities/asset.py +71 -0
  5. layrz_sdk/entities/{general/asset_operation_mode.py → asset_operation_mode.py} +31 -30
  6. layrz_sdk/entities/broadcast_request.py +12 -0
  7. layrz_sdk/entities/broadcast_response.py +12 -0
  8. layrz_sdk/entities/broadcast_result.py +20 -0
  9. layrz_sdk/entities/{broadcasts/status.py → broadcast_status.py} +28 -27
  10. layrz_sdk/entities/case.py +47 -0
  11. layrz_sdk/entities/case_ignored_status.py +26 -0
  12. layrz_sdk/entities/case_status.py +23 -0
  13. layrz_sdk/entities/charts/axis_config.py +15 -0
  14. layrz_sdk/entities/charts/{bar.py → bar_chart.py} +171 -208
  15. layrz_sdk/entities/charts/chart_alignment.py +27 -0
  16. layrz_sdk/entities/charts/chart_color.py +44 -0
  17. layrz_sdk/entities/charts/chart_configuration.py +10 -0
  18. layrz_sdk/entities/charts/chart_data_serie.py +19 -0
  19. layrz_sdk/entities/charts/chart_data_serie_type.py +28 -0
  20. layrz_sdk/entities/charts/chart_data_type.py +27 -0
  21. layrz_sdk/entities/charts/{render_technology.py → chart_render_technology.py} +30 -29
  22. layrz_sdk/entities/charts/{column.py → column_chart.py} +197 -236
  23. layrz_sdk/entities/charts/html_chart.py +34 -0
  24. layrz_sdk/entities/charts/{line.py → line_chart.py} +244 -282
  25. layrz_sdk/entities/charts/map_center_type.py +22 -0
  26. layrz_sdk/entities/charts/map_chart.py +104 -0
  27. layrz_sdk/entities/charts/map_point.py +22 -0
  28. layrz_sdk/entities/charts/number_chart.py +50 -0
  29. layrz_sdk/entities/charts/{pie.py → pie_chart.py} +127 -148
  30. layrz_sdk/entities/charts/radar_chart.py +77 -0
  31. layrz_sdk/entities/charts/{radial_bar.py → radial_bar_chart.py} +127 -148
  32. layrz_sdk/entities/charts/{scatter.py → scatter_chart.py} +206 -290
  33. layrz_sdk/entities/charts/scatter_serie.py +15 -0
  34. layrz_sdk/entities/charts/scatter_serie_item.py +8 -0
  35. layrz_sdk/entities/charts/table_chart.py +50 -0
  36. layrz_sdk/entities/charts/table_header.py +8 -0
  37. layrz_sdk/entities/charts/table_row.py +9 -0
  38. layrz_sdk/entities/charts/timeline_chart.py +75 -0
  39. layrz_sdk/entities/charts/timeline_serie.py +12 -0
  40. layrz_sdk/entities/charts/timeline_serie_item.py +12 -0
  41. layrz_sdk/entities/checkpoint.py +18 -0
  42. layrz_sdk/entities/comment.py +16 -0
  43. layrz_sdk/entities/custom_field.py +10 -0
  44. layrz_sdk/entities/custom_report_page.py +16 -0
  45. layrz_sdk/entities/device.py +13 -0
  46. layrz_sdk/entities/event.py +23 -0
  47. layrz_sdk/entities/geofence.py +11 -0
  48. layrz_sdk/entities/last_message.py +12 -0
  49. layrz_sdk/entities/message.py +23 -0
  50. layrz_sdk/entities/outbound_service.py +10 -0
  51. layrz_sdk/entities/position.py +101 -0
  52. layrz_sdk/entities/presence_type.py +16 -0
  53. layrz_sdk/entities/{reports/report.py → report.py} +257 -286
  54. layrz_sdk/entities/report_col.py +40 -0
  55. layrz_sdk/entities/report_configuration.py +8 -0
  56. layrz_sdk/entities/report_data_type.py +28 -0
  57. layrz_sdk/entities/{reports/format.py → report_format.py} +27 -26
  58. layrz_sdk/entities/report_header.py +43 -0
  59. layrz_sdk/entities/report_page.py +17 -0
  60. layrz_sdk/entities/report_row.py +28 -0
  61. layrz_sdk/entities/sensor.py +11 -0
  62. layrz_sdk/entities/{formatting/text_align.py → text_alignment.py} +26 -25
  63. layrz_sdk/entities/trigger.py +11 -0
  64. layrz_sdk/entities/user.py +10 -0
  65. layrz_sdk/entities/waypoint.py +18 -0
  66. layrz_sdk/helpers/__init__.py +8 -2
  67. layrz_sdk/helpers/color.py +41 -45
  68. layrz_sdk/lcl/__init__.py +7 -2
  69. layrz_sdk/lcl/core.py +832 -820
  70. layrz_sdk/py.typed +0 -0
  71. {layrz_sdk-3.0.14.dist-info → layrz_sdk-3.1.1.dist-info}/LICENSE +6 -6
  72. {layrz_sdk-3.0.14.dist-info → layrz_sdk-3.1.1.dist-info}/METADATA +48 -45
  73. layrz_sdk-3.1.1.dist-info/RECORD +75 -0
  74. {layrz_sdk-3.0.14.dist-info → layrz_sdk-3.1.1.dist-info}/WHEEL +1 -1
  75. layrz_sdk/entities/broadcasts/__init__.py +0 -6
  76. layrz_sdk/entities/broadcasts/request.py +0 -30
  77. layrz_sdk/entities/broadcasts/response.py +0 -30
  78. layrz_sdk/entities/broadcasts/result.py +0 -53
  79. layrz_sdk/entities/broadcasts/service.py +0 -28
  80. layrz_sdk/entities/cases/__init__.py +0 -4
  81. layrz_sdk/entities/cases/case.py +0 -120
  82. layrz_sdk/entities/cases/comment.py +0 -37
  83. layrz_sdk/entities/cases/trigger.py +0 -31
  84. layrz_sdk/entities/charts/__init__.py +0 -21
  85. layrz_sdk/entities/charts/alignment.py +0 -26
  86. layrz_sdk/entities/charts/color.py +0 -38
  87. layrz_sdk/entities/charts/configuration.py +0 -54
  88. layrz_sdk/entities/charts/data_type.py +0 -26
  89. layrz_sdk/entities/charts/exceptions.py +0 -29
  90. layrz_sdk/entities/charts/html.py +0 -44
  91. layrz_sdk/entities/charts/map.py +0 -179
  92. layrz_sdk/entities/charts/number.py +0 -52
  93. layrz_sdk/entities/charts/radar.py +0 -102
  94. layrz_sdk/entities/charts/serie.py +0 -56
  95. layrz_sdk/entities/charts/serie_type.py +0 -27
  96. layrz_sdk/entities/charts/table.py +0 -74
  97. layrz_sdk/entities/charts/timeline.py +0 -148
  98. layrz_sdk/entities/checkpoints/__init__.py +0 -4
  99. layrz_sdk/entities/checkpoints/checkpoint.py +0 -50
  100. layrz_sdk/entities/checkpoints/waypoint.py +0 -52
  101. layrz_sdk/entities/events/__init__.py +0 -2
  102. layrz_sdk/entities/events/event.py +0 -58
  103. layrz_sdk/entities/formatting/__init__.py +0 -2
  104. layrz_sdk/entities/general/__init__.py +0 -9
  105. layrz_sdk/entities/general/asset.py +0 -72
  106. layrz_sdk/entities/general/custom_field.py +0 -29
  107. layrz_sdk/entities/general/device.py +0 -45
  108. layrz_sdk/entities/general/geofence.py +0 -53
  109. layrz_sdk/entities/general/sensor.py +0 -31
  110. layrz_sdk/entities/general/user.py +0 -29
  111. layrz_sdk/entities/repcom/__init__.py +0 -2
  112. layrz_sdk/entities/repcom/transaction.py +0 -54
  113. layrz_sdk/entities/reports/__init__.py +0 -7
  114. layrz_sdk/entities/reports/col.py +0 -86
  115. layrz_sdk/entities/reports/header.py +0 -57
  116. layrz_sdk/entities/reports/page.py +0 -64
  117. layrz_sdk/entities/reports/row.py +0 -43
  118. layrz_sdk/entities/telemetry/__init__.py +0 -5
  119. layrz_sdk/entities/telemetry/last_message.py +0 -41
  120. layrz_sdk/entities/telemetry/message.py +0 -37
  121. layrz_sdk/entities/telemetry/position.py +0 -51
  122. layrz_sdk-3.0.14.dist-info/RECORD +0 -70
  123. {layrz_sdk-3.0.14.dist-info → layrz_sdk-3.1.1.dist-info}/top_level.txt +0 -0
@@ -1,286 +1,257 @@
1
- """Report class"""
2
-
3
- import logging
4
- import os
5
- import time
6
- import warnings
7
- from typing import Any, Dict, List
8
-
9
- import xlsxwriter
10
-
11
- from layrz_sdk.helpers.color import use_black
12
-
13
- from .col import ReportDataType
14
- from .format import ReportFormat
15
- from .page import CustomReportPage, ReportPage
16
-
17
- log = logging.getLogger(__name__)
18
-
19
-
20
- class Report:
21
- """
22
- Report definition
23
- ---
24
- Attributes
25
- - name : Report name. The exported name will have an timestamp to prevent duplicity in our servers.
26
- - pages : List of pages to append into report
27
- - export_format : Format to export the report
28
- """
29
-
30
- def __init__(
31
- self,
32
- name: str,
33
- pages: List[ReportPage | CustomReportPage],
34
- export_format: ReportFormat = None,
35
- ) -> None:
36
- self.name = name
37
- self.pages = pages
38
-
39
- if export_format is not None:
40
- warnings.warn(
41
- 'export_format is deprecated, submit the export format in the `export()` method instead',
42
- DeprecationWarning,
43
- stacklevel=2,
44
- )
45
-
46
- self.export_format = export_format
47
-
48
- @property
49
- def filename(self) -> str | None | bool:
50
- """Report filename"""
51
- return f'{self.name}_{int(time.time() * 1000)}.xlsx'
52
-
53
- @property
54
- def _readable(self) -> str | None | bool:
55
- """Readable property"""
56
- return f'Report(name={self.name}, pages={len(self.pages)})'
57
-
58
- def __repr__(self) -> str | None | bool:
59
- """Readable property"""
60
- return self._readable
61
-
62
- def __str__(self) -> str | None | bool:
63
- """Readable property"""
64
- return self._readable
65
-
66
- def export(
67
- self,
68
- path: str,
69
- export_format: ReportFormat = None,
70
- password: str = None,
71
- msoffice_crypt_path: str = '/opt/msoffice/bin/msoffice-crypt.exe',
72
- ) -> str | Dict[str, Any]:
73
- """
74
- Export report to file
75
-
76
- Arguments
77
- - path : Path to save the report
78
- - export_format : Format to export the report
79
- - password : Password to protect the file (Only works with Microsoft Excel format)
80
- - msoffice_crypt_path : Path to the msoffice-crypt.exe executable, used to encrypt the file
81
- if is None, the file will not be encrypted
82
-
83
- Returns
84
- - str : Full path of the exported file
85
- - dict : JSON representation of the report
86
- """
87
- if export_format:
88
- if export_format == ReportFormat.MICROSOFT_EXCEL:
89
- return self._export_xlsx(path=path, password=password, msoffice_crypt_path=msoffice_crypt_path)
90
- elif export_format == ReportFormat.JSON:
91
- if password:
92
- return {'name': self.name, 'is_protected': True, 'pages': []}
93
- return self._export_json()
94
- else:
95
- raise AttributeError(f'Unsupported export format: {export_format}')
96
-
97
- if self.export_format == ReportFormat.MICROSOFT_EXCEL:
98
- return self._export_xlsx(path=path, password=password, msoffice_crypt_path=msoffice_crypt_path)
99
- elif self.export_format == ReportFormat.JSON:
100
- if password:
101
- return {'name': self.name, 'is_protected': True, 'pages': []}
102
- return self._export_json()
103
- else:
104
- raise AttributeError(f'Unsupported export format: {self.export_format}')
105
-
106
- def export_as_json(self) -> Dict[str, Any]:
107
- """Returns the report as a JSON dict"""
108
- return self._export_json()
109
-
110
- def _export_json(self) -> Dict[str, Any]:
111
- """Returns a JSON dict of the report"""
112
- json_pages = []
113
- for page in self.pages:
114
- headers = []
115
- for header in page.headers:
116
- headers.append(
117
- {
118
- 'content': header.content,
119
- 'text_color': '#000000' if use_black(header.color) else '#ffffff',
120
- 'color': header.color,
121
- }
122
- )
123
- rows = []
124
- for row in page.rows:
125
- cells = []
126
- for cell in row.content:
127
- cells.append(
128
- {
129
- 'content': cell.content,
130
- 'text_color': '#000000' if use_black(cell.color) else '#ffffff',
131
- 'color': cell.color,
132
- 'data_type': cell.data_type.value,
133
- }
134
- )
135
- rows.append(
136
- {
137
- 'content': cells,
138
- 'compact': row.compact,
139
- }
140
- )
141
- json_pages.append(
142
- {
143
- 'name': page.name,
144
- 'headers': headers,
145
- 'rows': rows,
146
- }
147
- )
148
-
149
- return {
150
- 'name': self.name,
151
- 'pages': json_pages,
152
- }
153
-
154
- def _export_xlsx(
155
- self,
156
- path: str,
157
- password: str = None,
158
- msoffice_crypt_path: str = None,
159
- ) -> str:
160
- """Export to Microsoft Excel (.xslx)"""
161
-
162
- full_path = os.path.join(path, self.filename)
163
- book = xlsxwriter.Workbook(full_path)
164
-
165
- pages_name = []
166
-
167
- for page in self.pages:
168
- sheet_name = page.name[0:20]
169
-
170
- if sheet_name in pages_name:
171
- sheet_name = f'{sheet_name} ({pages_name.count(sheet_name) + 1})'
172
-
173
- # Allow only numbers, letters, spaces and _ or - characters
174
- # Other characters will be removed
175
- sheet_name = ''.join(e for e in sheet_name if e.isalnum() or e in [' ', '_', '-'])
176
- sheet = book.add_worksheet(sheet_name)
177
-
178
- if isinstance(page, CustomReportPage):
179
- page.builder(sheet)
180
- sheet.autofit()
181
- continue
182
-
183
- if page.freeze_header:
184
- sheet.freeze_panes(1, 0)
185
-
186
- for i, header in enumerate(page.headers):
187
- style = book.add_format(
188
- {
189
- 'align': header.align.value,
190
- 'font_color': '#000000' if use_black(header.color) else '#ffffff',
191
- 'bg_color': header.color,
192
- 'bold': header.bold,
193
- 'valign': 'vcenter',
194
- 'font_size': 11,
195
- 'top': 1,
196
- 'left': 1,
197
- 'right': 1,
198
- 'bottom': 1,
199
- 'font_name': 'Aptos Narrow',
200
- }
201
- )
202
- sheet.write(0, i, header.content, style)
203
-
204
- for i, row in enumerate(page.rows):
205
- for j, cell in enumerate(row.content):
206
- style = {
207
- 'align': cell.align.value,
208
- 'font_color': '#000000' if use_black(cell.color) else '#ffffff',
209
- 'bg_color': cell.color,
210
- 'bold': cell.bold,
211
- 'valign': 'vcenter',
212
- 'font_size': 11,
213
- 'top': 1,
214
- 'left': 1,
215
- 'right': 1,
216
- 'bottom': 1,
217
- 'font_name': 'Aptos Narrow',
218
- }
219
-
220
- if cell.data_type == ReportDataType.BOOL:
221
- value = 'Yes' if cell.value else 'No'
222
- elif cell.data_type == ReportDataType.DATETIME:
223
- value = cell.content.strftime(cell.datetime_format)
224
- elif cell.data_type == ReportDataType.INT:
225
- value = int(cell.content)
226
- elif cell.data_type == ReportDataType.FLOAT:
227
- value = float(cell.content)
228
- style.update({'num_format': '0.00'})
229
- elif cell.data_type == ReportDataType.CURRENCY:
230
- value = float(cell.content)
231
- style.update(
232
- {'num_format': f'"{cell.currency_symbol}" * #,##0.00;[Red]"{cell.currency_symbol}" * #,##0.00'}
233
- )
234
- else:
235
- value = cell.content
236
-
237
- sheet.write(i + 1, j, value, book.add_format(style))
238
-
239
- if row.compact:
240
- sheet.set_row(i + 1, None, None, {'level': 1, 'hidden': True})
241
- else:
242
- sheet.set_row(i + 1, None, None, {'collapsed': True})
243
-
244
- sheet.autofit()
245
- book.close()
246
-
247
- if password and msoffice_crypt_path:
248
- new_path = os.path.join(path, f'encrypted_{self.filename}')
249
- log.debug(f'Executing `{msoffice_crypt_path} -e -p "{password}" "{full_path}" "{new_path}"`')
250
- os.system(f'{msoffice_crypt_path} -e -p "{password}" "{full_path}" "{new_path}"')
251
- os.remove(full_path)
252
-
253
- with open(new_path, 'rb') as f:
254
- with open(full_path, 'wb') as f2:
255
- f2.write(f.read())
256
-
257
- os.remove(new_path)
258
-
259
- return full_path
260
-
261
-
262
- class ReportConfiguration:
263
- """
264
- Report Configuration class
265
- ---
266
- Attributes
267
- - title : Report title
268
- - pages_count : Number of pages in the report
269
- """
270
-
271
- def __init__(self, title: str, pages_count: int) -> None:
272
- self.title = title
273
- self.pages_count = pages_count
274
-
275
- @property
276
- def _readable(self) -> str | None | bool:
277
- """Readable property"""
278
- return f'ReportConfiguration(title={self.title}, pages_count={self.pages_count})'
279
-
280
- def __repr__(self) -> str | None | bool:
281
- """Readable property"""
282
- return self._readable
283
-
284
- def __str__(self) -> str | None | bool:
285
- """Readable property"""
286
- return self._readable
1
+ """Report class"""
2
+
3
+ import logging
4
+ import os
5
+ import time
6
+ import warnings
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ import xlsxwriter
10
+ from pydantic import BaseModel, Field, field_validator
11
+
12
+ from layrz_sdk.helpers.color import use_black
13
+
14
+ from .custom_report_page import CustomReportPage
15
+ from .report_data_type import ReportDataType
16
+ from .report_format import ReportFormat
17
+ from .report_page import ReportPage
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+
22
+ class Report(BaseModel):
23
+ """Report definition"""
24
+
25
+ name: str = Field(description='Name of the report. Length should be less than 60 characters')
26
+ pages: List[ReportPage | CustomReportPage] = Field(
27
+ description='List of report pages',
28
+ default_factory=list,
29
+ )
30
+ export_format: Optional[ReportFormat] = Field(description='Export format of the report', default=None)
31
+
32
+ @field_validator('export_format', mode='before')
33
+ def _validate_export_format(cls, value: Any) -> Any:
34
+ if value is not None:
35
+ warnings.warn(
36
+ 'export_format is deprecated, use the export method instead',
37
+ DeprecationWarning,
38
+ stacklevel=2,
39
+ )
40
+
41
+ return value
42
+
43
+ @property
44
+ def filename(self) -> str:
45
+ """Report filename"""
46
+ return f'{self.name}_{int(time.time() * 1000)}.xlsx'
47
+
48
+ def export(
49
+ self,
50
+ path: str,
51
+ export_format: Optional[ReportFormat] = None,
52
+ password: Optional[str] = None,
53
+ msoffice_crypt_path: str = '/opt/msoffice/bin/msoffice-crypt.exe',
54
+ ) -> str | Dict[str, Any]:
55
+ """
56
+ Export report to file
57
+
58
+ :param path: Path to save the report
59
+ :param export_format: Format to export the report
60
+ :param password: Password to protect the file (Only works with Microsoft Excel format)
61
+ :param msoffice_crypt_path: Path to the msoffice-crypt.exe executable, used to encrypt the file
62
+ :return: Full path of the exported file or JSON representation of the report
63
+ :rtype: str | dict
64
+ :raises AttributeError: If the export format is not supported
65
+ """
66
+ if export_format:
67
+ if export_format == ReportFormat.MICROSOFT_EXCEL:
68
+ return self._export_xlsx(path=path, password=password, msoffice_crypt_path=msoffice_crypt_path)
69
+ elif export_format == ReportFormat.JSON:
70
+ if password:
71
+ return {'name': self.name, 'is_protected': True, 'pages': []}
72
+ return self._export_json()
73
+ else:
74
+ raise AttributeError(f'Unsupported export format: {export_format}')
75
+
76
+ if self.export_format == ReportFormat.MICROSOFT_EXCEL:
77
+ return self._export_xlsx(path=path, password=password, msoffice_crypt_path=msoffice_crypt_path)
78
+ elif self.export_format == ReportFormat.JSON:
79
+ if password:
80
+ return {'name': self.name, 'is_protected': True, 'pages': []}
81
+ return self._export_json()
82
+ else:
83
+ raise AttributeError(f'Unsupported export format: {self.export_format}')
84
+
85
+ def export_as_json(self) -> Dict[str, Any]:
86
+ """Returns the report as a JSON dict"""
87
+ return self._export_json()
88
+
89
+ def _export_json(self) -> Dict[str, Any]:
90
+ """Returns a JSON dict of the report"""
91
+ json_pages = []
92
+ for page in self.pages:
93
+ if isinstance(page, CustomReportPage):
94
+ continue
95
+
96
+ headers = []
97
+ for header in page.headers:
98
+ headers.append(
99
+ {
100
+ 'content': header.content,
101
+ 'text_color': '#000000' if use_black(header.color) else '#ffffff',
102
+ 'color': header.color,
103
+ }
104
+ )
105
+ rows = []
106
+ for row in page.rows:
107
+ cells = []
108
+ for cell in row.content:
109
+ cells.append(
110
+ {
111
+ 'content': cell.content,
112
+ 'text_color': '#000000' if use_black(cell.color) else '#ffffff',
113
+ 'color': cell.color,
114
+ 'data_type': cell.data_type.value,
115
+ }
116
+ )
117
+ rows.append(
118
+ {
119
+ 'content': cells,
120
+ 'compact': row.compact,
121
+ }
122
+ )
123
+ json_pages.append(
124
+ {
125
+ 'name': page.name,
126
+ 'headers': headers,
127
+ 'rows': rows,
128
+ }
129
+ )
130
+
131
+ return {
132
+ 'name': self.name,
133
+ 'pages': json_pages,
134
+ }
135
+
136
+ def _export_xlsx(
137
+ self,
138
+ path: str,
139
+ password: Optional[str] = None,
140
+ msoffice_crypt_path: Optional[str] = None,
141
+ ) -> str:
142
+ """
143
+ Export to Microsoft Excel (.xslx)
144
+ :param path: Path to save the report
145
+ :param password: Password to protect the file
146
+ :param msoffice_crypt_path: Path to the msoffice-crypt.exe executable, used to encrypt the file
147
+ :return: Full path of the exported file
148
+ """
149
+
150
+ full_path = os.path.join(path, self.filename)
151
+ book = xlsxwriter.Workbook(full_path)
152
+
153
+ pages_name: List[str] = []
154
+
155
+ for page in self.pages:
156
+ sheet_name = page.name[0:20]
157
+
158
+ if sheet_name in pages_name:
159
+ sheet_name = f'{sheet_name} ({pages_name.count(sheet_name) + 1})'
160
+
161
+ # Allow only numbers, letters, spaces and _ or - characters
162
+ # Other characters will be removed
163
+ sheet_name = ''.join(e for e in sheet_name if e.isalnum() or e in [' ', '_', '-'])
164
+ sheet = book.add_worksheet(sheet_name)
165
+
166
+ if isinstance(page, CustomReportPage):
167
+ page.builder(sheet)
168
+ sheet.autofit()
169
+ continue
170
+
171
+ if page.freeze_header:
172
+ sheet.freeze_panes(1, 0)
173
+
174
+ for i, header in enumerate(page.headers):
175
+ style = book.add_format(
176
+ {
177
+ 'align': header.align.value,
178
+ 'font_color': '#000000' if use_black(header.color) else '#ffffff',
179
+ 'bg_color': header.color,
180
+ 'bold': header.bold,
181
+ 'valign': 'vcenter',
182
+ 'font_size': 11,
183
+ 'top': 1,
184
+ 'left': 1,
185
+ 'right': 1,
186
+ 'bottom': 1,
187
+ 'font_name': 'Aptos Narrow',
188
+ }
189
+ )
190
+ sheet.write(0, i, header.content, style)
191
+
192
+ for i, row in enumerate(page.rows):
193
+ for j, cell in enumerate(row.content):
194
+ style = {
195
+ 'align': cell.align.value,
196
+ 'font_color': '#000000' if use_black(cell.color) else '#ffffff',
197
+ 'bg_color': cell.color,
198
+ 'bold': cell.bold,
199
+ 'valign': 'vcenter',
200
+ 'font_size': 11,
201
+ 'top': 1,
202
+ 'left': 1,
203
+ 'right': 1,
204
+ 'bottom': 1,
205
+ 'font_name': 'Aptos Narrow',
206
+ }
207
+
208
+ value: Any = None
209
+
210
+ if cell.data_type == ReportDataType.BOOL:
211
+ value = 'Yes' if cell.content else 'No'
212
+ elif cell.data_type == ReportDataType.DATETIME:
213
+ value = cell.content.strftime(cell.datetime_format)
214
+ elif cell.data_type == ReportDataType.INT:
215
+ try:
216
+ value = int(cell.content)
217
+ except ValueError:
218
+ value = cell.content
219
+ log.warning(f'Invalid int value: {cell.content} in cell {i + 1}, {j}')
220
+ elif cell.data_type == ReportDataType.FLOAT:
221
+ try:
222
+ value = float(cell.content)
223
+ style.update({'num_format': '0.00'})
224
+ except ValueError:
225
+ value = cell.content
226
+ log.warning(f'Invalid float value: {cell.content} in cell {i + 1}, {j}')
227
+ elif cell.data_type == ReportDataType.CURRENCY:
228
+ value = float(cell.content)
229
+ style.update(
230
+ {'num_format': f'"{cell.currency_symbol}" * #,##0.00;[Red]"{cell.currency_symbol}" * #,##0.00'}
231
+ )
232
+ else:
233
+ value = cell.content
234
+
235
+ sheet.write(i + 1, j, value, book.add_format(style))
236
+
237
+ if row.compact:
238
+ sheet.set_row(i + 1, None, None, {'level': 1, 'hidden': True})
239
+ else:
240
+ sheet.set_row(i + 1, None, None, {'collapsed': True})
241
+
242
+ sheet.autofit()
243
+ book.close()
244
+
245
+ if password and msoffice_crypt_path:
246
+ new_path = os.path.join(path, f'encrypted_{self.filename}')
247
+ log.debug(f'Executing `{msoffice_crypt_path} -e -p "{password}" "{full_path}" "{new_path}"`')
248
+ os.system(f'{msoffice_crypt_path} -e -p "{password}" "{full_path}" "{new_path}"')
249
+ os.remove(full_path)
250
+
251
+ with open(new_path, 'rb') as f:
252
+ with open(full_path, 'wb') as f2:
253
+ f2.write(f.read())
254
+
255
+ os.remove(new_path)
256
+
257
+ return full_path
@@ -0,0 +1,40 @@
1
+ """Report col"""
2
+
3
+ import sys
4
+ import warnings
5
+ from typing import Any, Optional
6
+
7
+ from pydantic import BaseModel, Field, field_validator
8
+
9
+ from .report_data_type import ReportDataType
10
+ from .text_alignment import TextAlignment
11
+
12
+ if sys.version_info >= (3, 11):
13
+ from typing import Self
14
+ else:
15
+ from typing_extensions import Self
16
+
17
+
18
+ class ReportCol(BaseModel):
19
+ """Report column entity"""
20
+
21
+ content: Any = Field(description='Column content')
22
+ color: str = Field(description='Column color', default='#ffffff')
23
+ text_color: Optional[str] = Field(description='Column text color', default=None)
24
+ align: TextAlignment = Field(description='Column text alignment', default=TextAlignment.LEFT)
25
+ data_type: ReportDataType = Field(description='Column data type', default=ReportDataType.STR)
26
+ datetime_format: str = Field(description='Datetime format', default='%Y-%m-%d %H:%M:%S')
27
+ currency_symbol: str = Field(description='Currency symbol', default='')
28
+ bold: bool = Field(description='Bold text', default=False)
29
+
30
+ @field_validator('text_color', mode='before')
31
+ def _validate_text_color(cls: Self, value: Any) -> Any:
32
+ """Validate text color"""
33
+ if value is not None:
34
+ warnings.warn(
35
+ 'text_color is deprecated, the algorithm will calculate the rigth text color instead',
36
+ DeprecationWarning,
37
+ stacklevel=2,
38
+ )
39
+
40
+ return value
@@ -0,0 +1,8 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class ReportConfiguration(BaseModel):
5
+ """Report configuration entity"""
6
+
7
+ title: str = Field(description='Report title')
8
+ pages_count: int = Field(description='Number of pages in the report')
@@ -0,0 +1,28 @@
1
+ import sys
2
+ from enum import Enum
3
+
4
+ if sys.version_info >= (3, 11):
5
+ from typing import Self
6
+ else:
7
+ from typing_extensions import Self
8
+
9
+
10
+ class ReportDataType(Enum):
11
+ """
12
+ Report date type
13
+ """
14
+
15
+ STR = 'str'
16
+ INT = 'int'
17
+ FLOAT = 'float'
18
+ DATETIME = 'datetime'
19
+ BOOL = 'bool'
20
+ CURRENCY = 'currency'
21
+
22
+ def __str__(self: Self) -> str:
23
+ """Readable property"""
24
+ return self.name
25
+
26
+ def __repr__(self: Self) -> str:
27
+ """Readable property"""
28
+ return f'ReportDataType.{self.value}'