layrz-sdk 3.0.6__py3-none-any.whl → 3.0.8__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 +16 -10
  37. layrz_sdk/entities/general/asset_operation_mode.py +5 -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 +126 -66
  48. layrz_sdk/entities/reports/row.py +9 -8
  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.6.dist-info → layrz_sdk-3.0.8.dist-info}/METADATA +6 -1
  54. layrz_sdk-3.0.8.dist-info/RECORD +69 -0
  55. {layrz_sdk-3.0.6.dist-info → layrz_sdk-3.0.8.dist-info}/WHEEL +1 -1
  56. layrz_sdk-3.0.6.dist-info/RECORD +0 -69
  57. {layrz_sdk-3.0.6.dist-info → layrz_sdk-3.0.8.dist-info}/LICENSE +0 -0
  58. {layrz_sdk-3.0.6.dist-info → layrz_sdk-3.0.8.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,143 @@ 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:
64
91
  return self._export_json()
65
92
  else:
66
93
  raise AttributeError(f'Unsupported export format: {export_format}')
67
94
 
68
95
  if self.export_format == ReportFormat.MICROSOFT_EXCEL:
69
- return self._export_xlsx(path)
96
+ return self._export_xlsx(path=path, password=password, msoffice_crypt_path=msoffice_crypt_path)
70
97
  elif self.export_format == ReportFormat.JSON:
71
98
  return self._export_json()
72
99
  else:
73
100
  raise AttributeError(f'Unsupported export format: {self.export_format}')
74
101
 
75
- def export_as_json(self) -> dict:
76
- """ Returns the report as a JSON dict"""
102
+ def export_as_json(self: Self) -> Dict[str, Any]:
103
+ """Returns the report as a JSON dict"""
77
104
  return self._export_json()
78
105
 
79
- def _export_json(self) -> dict:
80
- """ Returns a JSON dict of the report"""
106
+ def _export_json(self: Self) -> Dict[str, Any]:
107
+ """Returns a JSON dict of the report"""
81
108
  json_pages = []
82
109
  for page in self.pages:
83
110
  headers = []
84
111
  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
- })
112
+ headers.append(
113
+ {
114
+ 'content': header.content,
115
+ 'text_color': '#000000' if use_black(header.color) else '#ffffff',
116
+ 'color': header.color,
117
+ }
118
+ )
90
119
  rows = []
91
120
  for row in page.rows:
92
121
  cells = []
93
122
  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
- })
123
+ cells.append(
124
+ {
125
+ 'content': cell.content,
126
+ 'text_color': '#000000' if use_black(cell.color) else '#ffffff',
127
+ 'color': cell.color,
128
+ 'data_type': cell.data_type.value,
129
+ }
130
+ )
131
+ rows.append(
132
+ {
133
+ 'content': cells,
134
+ 'compact': row.compact,
135
+ }
136
+ )
137
+ json_pages.append(
138
+ {
139
+ 'name': page.name,
140
+ 'headers': headers,
141
+ 'rows': rows,
142
+ }
143
+ )
109
144
 
110
145
  return {
111
146
  'name': self.name,
112
147
  'pages': json_pages,
113
148
  }
114
149
 
115
- def _export_xlsx(self, path) -> str:
116
- """ Export to Microsoft Excel (.xslx) """
150
+ def _export_xlsx(
151
+ self: Self,
152
+ path: str,
153
+ password: str = None,
154
+ msoffice_crypt_path: str = None,
155
+ ) -> str:
156
+ """Export to Microsoft Excel (.xslx)"""
117
157
 
118
158
  full_path = os.path.join(path, self.filename)
119
159
  book = xlsxwriter.Workbook(full_path)
120
160
 
161
+ pages_name = []
162
+
121
163
  for page in self.pages:
122
- sheet_name = page.name[0:31]
164
+ sheet_name = page.name[0:20]
165
+
166
+ if sheet_name in pages_name:
167
+ sheet_name = f'{sheet_name} ({pages_name.count(sheet_name) + 1})'
123
168
 
124
169
  # Allow only numbers, letters, spaces and _ or - characters
125
170
  # Other characters will be removed
@@ -135,19 +180,21 @@ class Report:
135
180
  sheet.freeze_panes(1, 0)
136
181
 
137
182
  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
- })
183
+ style = book.add_format(
184
+ {
185
+ 'align': header.align.value,
186
+ 'font_color': '#000000' if use_black(header.color) else '#ffffff',
187
+ 'bg_color': header.color,
188
+ 'bold': header.bold,
189
+ 'valign': 'vcenter',
190
+ 'font_size': 11,
191
+ 'top': 1,
192
+ 'left': 1,
193
+ 'right': 1,
194
+ 'bottom': 1,
195
+ 'font_name': 'Aptos Narrow',
196
+ }
197
+ )
151
198
  sheet.write(0, i, header.content, style)
152
199
 
153
200
  for i, row in enumerate(page.rows):
@@ -178,7 +225,8 @@ class Report:
178
225
  elif cell.data_type == ReportDataType.CURRENCY:
179
226
  value = float(cell.content)
180
227
  style.update(
181
- {'num_format': f'"{cell.currency_symbol}" * #,##0.00;[Red]"{cell.currency_symbol}" * #,##0.00'})
228
+ {'num_format': f'"{cell.currency_symbol}" * #,##0.00;[Red]"{cell.currency_symbol}" * #,##0.00'}
229
+ )
182
230
  else:
183
231
  value = cell.content
184
232
 
@@ -192,11 +240,23 @@ class Report:
192
240
  sheet.autofit()
193
241
  book.close()
194
242
 
243
+ if password and msoffice_crypt_path:
244
+ new_path = os.path.join(path, f'encrypted_{self.filename}')
245
+ log.debug(f'Executing `{msoffice_crypt_path} -e -p "{password}" "{full_path}" "{new_path}"`')
246
+ os.system(f'{msoffice_crypt_path} -e -p "{password}" "{full_path}" "{new_path}"')
247
+ os.remove(full_path)
248
+
249
+ with open(new_path, 'rb') as f:
250
+ with open(full_path, 'wb') as f2:
251
+ f2.write(f.read())
252
+
253
+ os.remove(new_path)
254
+
195
255
  return full_path
196
256
 
197
257
 
198
258
  class ReportConfiguration:
199
- """
259
+ """
200
260
  Report Configuration class
201
261
  ---
202
262
  Attributes
@@ -204,19 +264,19 @@ class ReportConfiguration:
204
264
  - pages_count : Number of pages in the report
205
265
  """
206
266
 
207
- def __init__(self, title: str, pages_count: int) -> None:
267
+ def __init__(self: Self, title: str, pages_count: int) -> None:
208
268
  self.title = title
209
269
  self.pages_count = pages_count
210
270
 
211
271
  @property
212
- def _readable(self) -> str:
213
- """ Readable property """
272
+ def _readable(self: Self) -> str | None | bool:
273
+ """Readable property"""
214
274
  return f'ReportConfiguration(title={self.title}, pages_count={self.pages_count})'
215
275
 
216
- def __repr__(self) -> str:
217
- """ Readable property """
276
+ def __repr__(self: Self) -> str | None | bool:
277
+ """Readable property"""
218
278
  return self._readable
219
279
 
220
- def __str__(self) -> str:
221
- """ Readable property """
280
+ def __str__(self: Self) -> str | None | bool:
281
+ """Readable property"""
222
282
  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,28 +17,27 @@ 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:
23
25
  """ Constructor """
24
26
  self.content = content
27
+ self.compact = compact
25
28
 
26
29
  if height is not None:
27
- raise DeprecationWarning('height is deprecated, use height instead')
28
-
29
- self.compact = compact
30
+ raise DeprecationWarning('height is deprecated.')
30
31
 
31
32
  @property
32
- def _readable(self) -> str:
33
+ def _readable(self: Self) -> str | None | bool:
33
34
  """ Readable property """
34
35
  return f'ReportRow(content={self.content})'
35
36
 
36
- def __str__(self) -> str:
37
+ def __str__(self: Self) -> str | None | bool:
37
38
  """ Readable property """
38
39
  return self._readable
39
40
 
40
- def __repr__(self) -> str:
41
+ def __repr__(self: Self) -> str | None | bool:
41
42
  """ Readable property """
42
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: