layrz-sdk 3.0.7__py3-none-any.whl → 3.0.9__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 (58) hide show
  1. layrz_sdk/entities/__init__.py +55 -9
  2. layrz_sdk/entities/broadcasts/request.py +5 -4
  3. layrz_sdk/entities/broadcasts/response.py +5 -4
  4. layrz_sdk/entities/broadcasts/result.py +6 -5
  5. layrz_sdk/entities/broadcasts/service.py +5 -4
  6. layrz_sdk/entities/broadcasts/status.py +4 -3
  7. layrz_sdk/entities/cases/case.py +13 -12
  8. layrz_sdk/entities/cases/comment.py +5 -4
  9. layrz_sdk/entities/cases/trigger.py +5 -4
  10. layrz_sdk/entities/charts/__init__.py +1 -1
  11. layrz_sdk/entities/charts/alignment.py +4 -3
  12. layrz_sdk/entities/charts/bar.py +9 -7
  13. layrz_sdk/entities/charts/color.py +5 -4
  14. layrz_sdk/entities/charts/column.py +9 -7
  15. layrz_sdk/entities/charts/configuration.py +9 -7
  16. layrz_sdk/entities/charts/data_type.py +4 -3
  17. layrz_sdk/entities/charts/exceptions.py +6 -5
  18. layrz_sdk/entities/charts/html.py +5 -3
  19. layrz_sdk/entities/charts/line.py +9 -8
  20. layrz_sdk/entities/charts/map.py +13 -13
  21. layrz_sdk/entities/charts/number.py +5 -3
  22. layrz_sdk/entities/charts/pie.py +9 -7
  23. layrz_sdk/entities/charts/radar.py +6 -4
  24. layrz_sdk/entities/charts/radial_bar.py +9 -7
  25. layrz_sdk/entities/charts/render_technology.py +4 -3
  26. layrz_sdk/entities/charts/scatter.py +12 -10
  27. layrz_sdk/entities/charts/serie.py +5 -3
  28. layrz_sdk/entities/charts/serie_type.py +4 -3
  29. layrz_sdk/entities/charts/table.py +15 -17
  30. layrz_sdk/entities/charts/timeline.py +7 -6
  31. layrz_sdk/entities/checkpoints/checkpoint.py +6 -5
  32. layrz_sdk/entities/checkpoints/geofence.py +5 -4
  33. layrz_sdk/entities/checkpoints/waypoint.py +5 -4
  34. layrz_sdk/entities/events/event.py +5 -4
  35. layrz_sdk/entities/formatting/text_align.py +4 -3
  36. layrz_sdk/entities/general/asset.py +11 -9
  37. layrz_sdk/entities/general/asset_operation_mode.py +4 -3
  38. layrz_sdk/entities/general/custom_field.py +5 -4
  39. layrz_sdk/entities/general/device.py +5 -4
  40. layrz_sdk/entities/general/sensor.py +5 -4
  41. layrz_sdk/entities/general/user.py +5 -4
  42. layrz_sdk/entities/repcom/transaction.py +3 -2
  43. layrz_sdk/entities/reports/col.py +9 -9
  44. layrz_sdk/entities/reports/format.py +11 -7
  45. layrz_sdk/entities/reports/header.py +12 -10
  46. layrz_sdk/entities/reports/page.py +10 -8
  47. layrz_sdk/entities/reports/report.py +130 -66
  48. layrz_sdk/entities/reports/row.py +7 -5
  49. layrz_sdk/entities/telemetry/message.py +6 -5
  50. layrz_sdk/entities/telemetry/position.py +5 -4
  51. layrz_sdk/helpers/color.py +6 -5
  52. layrz_sdk/lcl/core.py +131 -120
  53. {layrz_sdk-3.0.7.dist-info → layrz_sdk-3.0.9.dist-info}/METADATA +6 -1
  54. layrz_sdk-3.0.9.dist-info/RECORD +69 -0
  55. {layrz_sdk-3.0.7.dist-info → layrz_sdk-3.0.9.dist-info}/WHEEL +1 -1
  56. layrz_sdk-3.0.7.dist-info/RECORD +0 -69
  57. {layrz_sdk-3.0.7.dist-info → layrz_sdk-3.0.9.dist-info}/LICENSE +0 -0
  58. {layrz_sdk-3.0.7.dist-info → layrz_sdk-3.0.9.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,10 @@
1
- """ Report class """
1
+ """Report class"""
2
+
3
+ import logging
2
4
  import os
3
5
  import time
4
6
  import warnings
7
+ from typing import Any, Dict, List, Self
5
8
 
6
9
  import xlsxwriter
7
10
 
@@ -11,6 +14,8 @@ from .col import ReportDataType
11
14
  from .format import ReportFormat
12
15
  from .page import CustomReportPage, ReportPage
13
16
 
17
+ log = logging.getLogger(__name__)
18
+
14
19
 
15
20
  class Report:
16
21
  """
@@ -23,103 +28,147 @@ class Report:
23
28
  """
24
29
 
25
30
  def __init__(
26
- self,
31
+ self: Self,
27
32
  name: str,
28
- pages: list[ReportPage | CustomReportPage],
33
+ pages: List[ReportPage | CustomReportPage],
29
34
  export_format: ReportFormat = None,
30
35
  ) -> None:
31
36
  self.name = name
32
37
  self.pages = pages
33
38
 
34
39
  if export_format is not None:
35
- warnings.warn('export_format is deprecated, submit the export format in the `export()` method instead',
36
- DeprecationWarning)
40
+ warnings.warn(
41
+ 'export_format is deprecated, submit the export format in the `export()` method instead',
42
+ DeprecationWarning,
43
+ stacklevel=2,
44
+ )
37
45
 
38
46
  self.export_format = export_format
39
47
 
40
48
  @property
41
- def filename(self) -> str:
42
- """ Report filename """
49
+ def filename(self: Self) -> str | None | bool:
50
+ """Report filename"""
43
51
  return f'{self.name}_{int(time.time() * 1000)}.xlsx'
44
52
 
45
53
  @property
46
- def _readable(self) -> str:
47
- """ Readable property """
54
+ def _readable(self: Self) -> str | None | bool:
55
+ """Readable property"""
48
56
  return f'Report(name={self.name}, pages={len(self.pages)})'
49
57
 
50
- def __repr__(self) -> str:
51
- """ Readable property """
58
+ def __repr__(self: Self) -> str | None | bool:
59
+ """Readable property"""
52
60
  return self._readable
53
61
 
54
- def __str__(self) -> str:
55
- """ Readable property """
62
+ def __str__(self: Self) -> str | None | bool:
63
+ """Readable property"""
56
64
  return self._readable
57
65
 
58
- def export(self, path, export_format: ReportFormat = None) -> str:
59
- """ Export report to file """
66
+ def export(
67
+ self: 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
+ """
60
87
  if export_format:
61
88
  if export_format == ReportFormat.MICROSOFT_EXCEL:
62
- return self._export_xlsx(path)
89
+ return self._export_xlsx(path=path, password=password, msoffice_crypt_path=msoffice_crypt_path)
63
90
  elif export_format == ReportFormat.JSON:
91
+ if password:
92
+ return {'name': self.name, 'is_protected': True, 'pages': []}
64
93
  return self._export_json()
65
94
  else:
66
95
  raise AttributeError(f'Unsupported export format: {export_format}')
67
96
 
68
97
  if self.export_format == ReportFormat.MICROSOFT_EXCEL:
69
- return self._export_xlsx(path)
98
+ return self._export_xlsx(path=path, password=password, msoffice_crypt_path=msoffice_crypt_path)
70
99
  elif self.export_format == ReportFormat.JSON:
100
+ if password:
101
+ return {'name': self.name, 'is_protected': True, 'pages': []}
71
102
  return self._export_json()
72
103
  else:
73
104
  raise AttributeError(f'Unsupported export format: {self.export_format}')
74
105
 
75
- def export_as_json(self) -> dict:
76
- """ Returns the report as a JSON dict"""
106
+ def export_as_json(self: Self) -> Dict[str, Any]:
107
+ """Returns the report as a JSON dict"""
77
108
  return self._export_json()
78
109
 
79
- def _export_json(self) -> dict:
80
- """ Returns a JSON dict of the report"""
110
+ def _export_json(self: Self) -> Dict[str, Any]:
111
+ """Returns a JSON dict of the report"""
81
112
  json_pages = []
82
113
  for page in self.pages:
83
114
  headers = []
84
115
  for header in page.headers:
85
- headers.append({
86
- 'content': header.content,
87
- 'text_color': '#000000' if use_black(header.color) else '#ffffff',
88
- 'color': header.color,
89
- })
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
+ )
90
123
  rows = []
91
124
  for row in page.rows:
92
125
  cells = []
93
126
  for cell in row.content:
94
- cells.append({
95
- 'content': cell.content,
96
- 'text_color': '#000000' if use_black(cell.color) else '#ffffff',
97
- 'color': cell.color,
98
- 'data_type': cell.data_type.value,
99
- })
100
- rows.append({
101
- 'content': cells,
102
- 'compact': row.compact,
103
- })
104
- json_pages.append({
105
- 'name': page.name,
106
- 'headers': headers,
107
- 'rows': rows,
108
- })
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
+ )
109
148
 
110
149
  return {
111
150
  'name': self.name,
112
151
  'pages': json_pages,
113
152
  }
114
153
 
115
- def _export_xlsx(self, path) -> str:
116
- """ Export to Microsoft Excel (.xslx) """
154
+ def _export_xlsx(
155
+ self: Self,
156
+ path: str,
157
+ password: str = None,
158
+ msoffice_crypt_path: str = None,
159
+ ) -> str:
160
+ """Export to Microsoft Excel (.xslx)"""
117
161
 
118
162
  full_path = os.path.join(path, self.filename)
119
163
  book = xlsxwriter.Workbook(full_path)
120
164
 
165
+ pages_name = []
166
+
121
167
  for page in self.pages:
122
- sheet_name = page.name[0:31]
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})'
123
172
 
124
173
  # Allow only numbers, letters, spaces and _ or - characters
125
174
  # Other characters will be removed
@@ -135,19 +184,21 @@ class Report:
135
184
  sheet.freeze_panes(1, 0)
136
185
 
137
186
  for i, header in enumerate(page.headers):
138
- style = book.add_format({
139
- 'align': header.align.value,
140
- 'font_color': '#000000' if use_black(header.color) else '#ffffff',
141
- 'bg_color': header.color,
142
- 'bold': header.bold,
143
- 'valign': 'vcenter',
144
- 'font_size': 11,
145
- 'top': 1,
146
- 'left': 1,
147
- 'right': 1,
148
- 'bottom': 1,
149
- 'font_name': 'Aptos Narrow',
150
- })
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
+ )
151
202
  sheet.write(0, i, header.content, style)
152
203
 
153
204
  for i, row in enumerate(page.rows):
@@ -178,7 +229,8 @@ class Report:
178
229
  elif cell.data_type == ReportDataType.CURRENCY:
179
230
  value = float(cell.content)
180
231
  style.update(
181
- {'num_format': f'"{cell.currency_symbol}" * #,##0.00;[Red]"{cell.currency_symbol}" * #,##0.00'})
232
+ {'num_format': f'"{cell.currency_symbol}" * #,##0.00;[Red]"{cell.currency_symbol}" * #,##0.00'}
233
+ )
182
234
  else:
183
235
  value = cell.content
184
236
 
@@ -192,11 +244,23 @@ class Report:
192
244
  sheet.autofit()
193
245
  book.close()
194
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
+
195
259
  return full_path
196
260
 
197
261
 
198
262
  class ReportConfiguration:
199
- """
263
+ """
200
264
  Report Configuration class
201
265
  ---
202
266
  Attributes
@@ -204,19 +268,19 @@ class ReportConfiguration:
204
268
  - pages_count : Number of pages in the report
205
269
  """
206
270
 
207
- def __init__(self, title: str, pages_count: int) -> None:
271
+ def __init__(self: Self, title: str, pages_count: int) -> None:
208
272
  self.title = title
209
273
  self.pages_count = pages_count
210
274
 
211
275
  @property
212
- def _readable(self) -> str:
213
- """ Readable property """
276
+ def _readable(self: Self) -> str | None | bool:
277
+ """Readable property"""
214
278
  return f'ReportConfiguration(title={self.title}, pages_count={self.pages_count})'
215
279
 
216
- def __repr__(self) -> str:
217
- """ Readable property """
280
+ def __repr__(self: Self) -> str | None | bool:
281
+ """Readable property"""
218
282
  return self._readable
219
283
 
220
- def __str__(self) -> str:
221
- """ Readable property """
284
+ def __str__(self: Self) -> str | None | bool:
285
+ """Readable property"""
222
286
  return self._readable
@@ -1,5 +1,7 @@
1
1
  """ Report row """
2
2
 
3
+ from typing import List, Self
4
+
3
5
  from .col import ReportCol
4
6
 
5
7
 
@@ -15,8 +17,8 @@ class ReportRow:
15
17
  """
16
18
 
17
19
  def __init__(
18
- self,
19
- content: list[ReportCol],
20
+ self: Self,
21
+ content: List[ReportCol],
20
22
  height: float = None,
21
23
  compact: bool = False,
22
24
  ) -> None:
@@ -28,14 +30,14 @@ class ReportRow:
28
30
  raise DeprecationWarning('height is deprecated.')
29
31
 
30
32
  @property
31
- def _readable(self) -> str:
33
+ def _readable(self: Self) -> str | None | bool:
32
34
  """ Readable property """
33
35
  return f'ReportRow(content={self.content})'
34
36
 
35
- def __str__(self) -> str:
37
+ def __str__(self: Self) -> str | None | bool:
36
38
  """ Readable property """
37
39
  return self._readable
38
40
 
39
- def __repr__(self) -> str:
41
+ def __repr__(self: Self) -> str | None | bool:
40
42
  """ Readable property """
41
43
  return self._readable
@@ -1,5 +1,6 @@
1
1
  """ Message entity """
2
- from datetime import datetime
2
+ from datetime import datetime, timezone
3
+ from typing import Any, Self
3
4
 
4
5
  from .position import Position
5
6
 
@@ -18,12 +19,12 @@ class Message:
18
19
  """
19
20
 
20
21
  def __init__(
21
- self,
22
+ self: Self,
22
23
  pk: int,
23
24
  asset_id: int,
24
25
  position: Position = None,
25
- payload: dict = None,
26
- sensors: dict = None,
26
+ payload: Any = None,
27
+ sensors: Any = None,
27
28
  received_at: datetime = None,
28
29
  ) -> None:
29
30
  """ Constructor """
@@ -32,4 +33,4 @@ class Message:
32
33
  self.position = position or Position()
33
34
  self.payload = payload or {}
34
35
  self.sensors = sensors or {}
35
- self.received_at = received_at or datetime.now()
36
+ self.received_at = received_at or datetime.now(timezone.utc)
@@ -1,4 +1,5 @@
1
1
  """ Position entity """
2
+ from typing import Self
2
3
 
3
4
 
4
5
  class Position:
@@ -16,7 +17,7 @@ class Position:
16
17
  """
17
18
 
18
19
  def __init__(
19
- self,
20
+ self: Self,
20
21
  latitude: float = None,
21
22
  longitude: float = None,
22
23
  altitude: float = None,
@@ -35,15 +36,15 @@ class Position:
35
36
  self.satellites = satellites
36
37
 
37
38
  @property
38
- def _readable(self) -> str:
39
+ def _readable(self: Self) -> str | None | bool:
39
40
  """ Readable """
40
41
  return f'Position(latitude={self.latitude}, longitude={self.longitude}, altitude={self.altitude}, ' +\
41
42
  f'speed={self.speed}, direction={self.direction}, hdop={self.hdop}, satellites={self.satellites})'
42
43
 
43
- def __str__(self) -> str:
44
+ def __str__(self: Self) -> str | None | bool:
44
45
  """ Readable property """
45
46
  return self._readable
46
47
 
47
- def __repr__(self) -> str:
48
+ def __repr__(self: Self) -> str | None | bool:
48
49
  """ Readable property """
49
50
  return self._readable
@@ -2,27 +2,28 @@
2
2
  Color helpers
3
3
  """
4
4
 
5
+ from typing import Tuple
5
6
 
6
- def convert_to_rgba(hex_color: str) -> tuple[int, int, int, int]:
7
+
8
+ def convert_to_rgba(hex_color: str) -> Tuple[int, int, int, int]:
7
9
  """
8
10
  Convert Hex (or Hexa) color to RGB (or RGBA) color
9
-
10
11
  Arguments
11
12
  ---------
12
13
  hex_color (str): Hex (or Hexa) color
13
14
  Returns
14
15
  -------
15
16
  tuple(r,g,b,a): Combination of colors. When the argument (hex_color) is Hex, the alpha channel is set to 1.
16
- """
17
+ """
17
18
 
18
19
  if not hex_color.startswith('#'):
19
20
  raise ValueError('Invalid color, must starts with #')
20
21
 
21
22
  hex_color = hex_color.replace('#', '')
22
23
  if len(hex_color) == 6:
23
- return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4)) + (1, )
24
+ return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) + (1,)
24
25
 
25
- return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4, 6))
26
+ return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4, 6))
26
27
 
27
28
 
28
29
  def use_black(color: str) -> bool: