everysk-lib 1.10.2__cp312-cp312-win_amd64.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 (137) hide show
  1. everysk/__init__.py +30 -0
  2. everysk/_version.py +683 -0
  3. everysk/api/__init__.py +61 -0
  4. everysk/api/api_requestor.py +167 -0
  5. everysk/api/api_resources/__init__.py +23 -0
  6. everysk/api/api_resources/api_resource.py +371 -0
  7. everysk/api/api_resources/calculation.py +779 -0
  8. everysk/api/api_resources/custom_index.py +42 -0
  9. everysk/api/api_resources/datastore.py +81 -0
  10. everysk/api/api_resources/file.py +42 -0
  11. everysk/api/api_resources/market_data.py +223 -0
  12. everysk/api/api_resources/parser.py +66 -0
  13. everysk/api/api_resources/portfolio.py +43 -0
  14. everysk/api/api_resources/private_security.py +42 -0
  15. everysk/api/api_resources/report.py +65 -0
  16. everysk/api/api_resources/report_template.py +39 -0
  17. everysk/api/api_resources/tests.py +115 -0
  18. everysk/api/api_resources/worker_execution.py +64 -0
  19. everysk/api/api_resources/workflow.py +65 -0
  20. everysk/api/api_resources/workflow_execution.py +93 -0
  21. everysk/api/api_resources/workspace.py +42 -0
  22. everysk/api/http_client.py +63 -0
  23. everysk/api/tests.py +32 -0
  24. everysk/api/utils.py +262 -0
  25. everysk/config.py +451 -0
  26. everysk/core/_tests/serialize/test_json.py +336 -0
  27. everysk/core/_tests/serialize/test_orjson.py +295 -0
  28. everysk/core/_tests/serialize/test_pickle.py +48 -0
  29. everysk/core/cloud_function/main.py +78 -0
  30. everysk/core/cloud_function/tests.py +86 -0
  31. everysk/core/compress.py +245 -0
  32. everysk/core/datetime/__init__.py +12 -0
  33. everysk/core/datetime/calendar.py +144 -0
  34. everysk/core/datetime/date.py +424 -0
  35. everysk/core/datetime/date_expression.py +299 -0
  36. everysk/core/datetime/date_mixin.py +1475 -0
  37. everysk/core/datetime/date_settings.py +30 -0
  38. everysk/core/datetime/datetime.py +713 -0
  39. everysk/core/exceptions.py +435 -0
  40. everysk/core/fields.py +1176 -0
  41. everysk/core/firestore.py +555 -0
  42. everysk/core/fixtures/_settings.py +29 -0
  43. everysk/core/fixtures/other/_settings.py +18 -0
  44. everysk/core/fixtures/user_agents.json +88 -0
  45. everysk/core/http.py +691 -0
  46. everysk/core/lists.py +92 -0
  47. everysk/core/log.py +709 -0
  48. everysk/core/number.py +37 -0
  49. everysk/core/object.py +1469 -0
  50. everysk/core/redis.py +1021 -0
  51. everysk/core/retry.py +51 -0
  52. everysk/core/serialize.py +674 -0
  53. everysk/core/sftp.py +414 -0
  54. everysk/core/signing.py +53 -0
  55. everysk/core/slack.py +127 -0
  56. everysk/core/string.py +199 -0
  57. everysk/core/tests.py +240 -0
  58. everysk/core/threads.py +199 -0
  59. everysk/core/undefined.py +70 -0
  60. everysk/core/unittests.py +73 -0
  61. everysk/core/workers.py +241 -0
  62. everysk/sdk/__init__.py +23 -0
  63. everysk/sdk/base.py +98 -0
  64. everysk/sdk/brutils/cnpj.py +391 -0
  65. everysk/sdk/brutils/cnpj_pd.py +129 -0
  66. everysk/sdk/engines/__init__.py +26 -0
  67. everysk/sdk/engines/cache.py +185 -0
  68. everysk/sdk/engines/compliance.py +37 -0
  69. everysk/sdk/engines/cryptography.py +69 -0
  70. everysk/sdk/engines/expression.cp312-win_amd64.pyd +0 -0
  71. everysk/sdk/engines/expression.pyi +55 -0
  72. everysk/sdk/engines/helpers.cp312-win_amd64.pyd +0 -0
  73. everysk/sdk/engines/helpers.pyi +26 -0
  74. everysk/sdk/engines/lock.py +120 -0
  75. everysk/sdk/engines/market_data.py +244 -0
  76. everysk/sdk/engines/settings.py +19 -0
  77. everysk/sdk/entities/__init__.py +23 -0
  78. everysk/sdk/entities/base.py +784 -0
  79. everysk/sdk/entities/base_list.py +131 -0
  80. everysk/sdk/entities/custom_index/base.py +209 -0
  81. everysk/sdk/entities/custom_index/settings.py +29 -0
  82. everysk/sdk/entities/datastore/base.py +160 -0
  83. everysk/sdk/entities/datastore/settings.py +17 -0
  84. everysk/sdk/entities/fields.py +375 -0
  85. everysk/sdk/entities/file/base.py +215 -0
  86. everysk/sdk/entities/file/settings.py +63 -0
  87. everysk/sdk/entities/portfolio/base.py +248 -0
  88. everysk/sdk/entities/portfolio/securities.py +241 -0
  89. everysk/sdk/entities/portfolio/security.py +580 -0
  90. everysk/sdk/entities/portfolio/settings.py +97 -0
  91. everysk/sdk/entities/private_security/base.py +226 -0
  92. everysk/sdk/entities/private_security/settings.py +17 -0
  93. everysk/sdk/entities/query.py +603 -0
  94. everysk/sdk/entities/report/base.py +214 -0
  95. everysk/sdk/entities/report/settings.py +23 -0
  96. everysk/sdk/entities/script.py +310 -0
  97. everysk/sdk/entities/secrets/base.py +128 -0
  98. everysk/sdk/entities/secrets/script.py +119 -0
  99. everysk/sdk/entities/secrets/settings.py +17 -0
  100. everysk/sdk/entities/settings.py +48 -0
  101. everysk/sdk/entities/tags.py +174 -0
  102. everysk/sdk/entities/worker_execution/base.py +307 -0
  103. everysk/sdk/entities/worker_execution/settings.py +63 -0
  104. everysk/sdk/entities/workflow_execution/base.py +113 -0
  105. everysk/sdk/entities/workflow_execution/settings.py +32 -0
  106. everysk/sdk/entities/workspace/base.py +99 -0
  107. everysk/sdk/entities/workspace/settings.py +27 -0
  108. everysk/sdk/settings.py +67 -0
  109. everysk/sdk/tests.py +105 -0
  110. everysk/sdk/worker_base.py +47 -0
  111. everysk/server/__init__.py +9 -0
  112. everysk/server/applications.py +63 -0
  113. everysk/server/endpoints.py +516 -0
  114. everysk/server/example_api.py +69 -0
  115. everysk/server/middlewares.py +80 -0
  116. everysk/server/requests.py +62 -0
  117. everysk/server/responses.py +119 -0
  118. everysk/server/routing.py +64 -0
  119. everysk/server/settings.py +36 -0
  120. everysk/server/tests.py +36 -0
  121. everysk/settings.py +98 -0
  122. everysk/sql/__init__.py +9 -0
  123. everysk/sql/connection.py +232 -0
  124. everysk/sql/model.py +376 -0
  125. everysk/sql/query.py +417 -0
  126. everysk/sql/row_factory.py +63 -0
  127. everysk/sql/settings.py +49 -0
  128. everysk/sql/utils.py +129 -0
  129. everysk/tests.py +23 -0
  130. everysk/utils.py +81 -0
  131. everysk/version.py +15 -0
  132. everysk_lib-1.10.2.dist-info/.gitignore +5 -0
  133. everysk_lib-1.10.2.dist-info/METADATA +326 -0
  134. everysk_lib-1.10.2.dist-info/RECORD +137 -0
  135. everysk_lib-1.10.2.dist-info/WHEEL +5 -0
  136. everysk_lib-1.10.2.dist-info/licenses/LICENSE.txt +9 -0
  137. everysk_lib-1.10.2.dist-info/top_level.txt +2 -0
@@ -0,0 +1,674 @@
1
+ ###############################################################################
2
+ #
3
+ # (C) Copyright 2025 EVERYSK TECHNOLOGIES
4
+ #
5
+ # This is an unpublished work containing confidential and proprietary
6
+ # information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
7
+ # without authorization of EVERYSK TECHNOLOGIES is prohibited.
8
+ #
9
+ ###############################################################################
10
+ __all__ = ['dumps', 'loads']
11
+
12
+ import json
13
+ import pickle
14
+ from base64 import b64decode, b64encode
15
+ from typing import Any
16
+
17
+ from everysk.config import settings
18
+ from everysk.core.datetime import Date, DateTime
19
+ from everysk.core.object import CLASS_KEY, BaseObject
20
+ from everysk.core.signing import sign, unsign
21
+ from everysk.core.string import import_from_string
22
+ from everysk.core.undefined import UndefinedType
23
+
24
+ try:
25
+ from simplejson import JSONEncoder as _JSONEncoder # type: ignore
26
+ except ImportError:
27
+ from json import JSONEncoder as _JSONEncoder
28
+
29
+
30
+ BYTES_KEY: str = '__bytes__'
31
+ DATE_KEY: str = '__date__'
32
+ DATETIME_KEY: str = '__datetime__'
33
+ PICKLE_KEY_SEPARATOR: str = ':'
34
+ UNDEFINED_KEY: str = '__undefined__'
35
+
36
+
37
+ ## Helper functions
38
+ def _parser(obj: bool | int | float | str | list | dict, object_hook: callable) -> Any:
39
+ """
40
+ Recursively parse the object to handle custom object hooks for date, datetime, Undefined, BaseObject and BaseDict.
41
+ The obj parameter will be always the result from the orjson.loads function, so it will be a boolean, int, float, str, list or dict.
42
+
43
+ Args:
44
+ obj (bool | int | float | str | list | dict): The result from the orjson.loads function.
45
+ object_hook (callable): The object hook function to use to convert the object.
46
+ """
47
+ if isinstance(obj, list):
48
+ # Using the same list consumes less memory
49
+ for index, item in enumerate(obj):
50
+ # We only need to parse lists and dicts
51
+ if isinstance(item, (list, dict)):
52
+ obj[index] = _parser(item, object_hook)
53
+
54
+ elif isinstance(obj, dict):
55
+ # Using the same dict consumes less memory
56
+ for key, value in obj.items():
57
+ # We only need to parse lists and dicts
58
+ if isinstance(value, (list, dict)):
59
+ obj[key] = _parser(value, object_hook)
60
+
61
+ # The object_hook is called only for dict objects
62
+ obj = object_hook(obj)
63
+
64
+ return obj
65
+
66
+
67
+ def convert_bytes_decode(obj: bytes, encoding: str = 'utf-8') -> str:
68
+ """
69
+ Convert a bytes object to a string if possible, otherwise encode it in base64.
70
+
71
+ Args:
72
+ obj (bytes): The bytes object to convert to a string.
73
+ encoding (str): The encoding to use to decode the bytes object. Default is 'utf-8'.
74
+
75
+ Returns:
76
+ str: The string representation of the bytes object.
77
+ """
78
+ try:
79
+ return obj.decode(encoding)
80
+ except UnicodeDecodeError:
81
+ pass
82
+
83
+ return b64encode(obj).decode(encoding)
84
+
85
+
86
+ def convert_bytes(obj: bytes) -> dict[str, dict[str, str]]:
87
+ """
88
+ Convert a bytes object to a dict.
89
+ If the bytes object is not a valid utf-8 string, it will be encoded in base64.
90
+
91
+ Args:
92
+ obj (bytes): The bytes object to convert to a dict.
93
+ """
94
+ try:
95
+ encoding = 'utf-8'
96
+ value = obj.decode(encoding)
97
+ except UnicodeDecodeError:
98
+ value = b64encode(obj).decode('utf-8')
99
+ encoding = 'base64'
100
+
101
+ return {BYTES_KEY: {'encoding': encoding, 'value': value}}
102
+
103
+
104
+ def handle_bytes_conversion(obj: bytes, *, decode_bytes: bool) -> str | dict:
105
+ """
106
+ Handle the conversion of bytes objects based on the decode_bytes flag.
107
+ If decode_bytes is True, it decodes the bytes object to a string.
108
+ If decode_bytes is False, it converts the bytes object to a dict.
109
+
110
+ Args:
111
+ obj (bytes): The bytes object to convert.
112
+ decode_bytes (bool): Flag to determine whether to decode bytes or convert to dict.
113
+
114
+ Returns:
115
+ str | dict: The converted object, either as a string or a dict.
116
+ """
117
+ if decode_bytes:
118
+ return convert_bytes_decode(obj)
119
+ return convert_bytes(obj)
120
+
121
+
122
+ ###############################################################################
123
+ # JSONEncoder Class Implementation
124
+ ###############################################################################
125
+ class JSONEncoder(_JSONEncoder):
126
+ ## Public attributes
127
+ add_class_path: bool = True
128
+ date_format: str = None
129
+ datetime_format: str = None
130
+ use_undefined: bool = False
131
+ decode_bytes: bool = False
132
+
133
+ def __init__(self, **kwargs) -> None:
134
+ # Set specific params
135
+ self.add_class_path = kwargs.pop('add_class_path', self.add_class_path)
136
+ self.date_format = kwargs.pop('date_format', self.date_format)
137
+ self.datetime_format = kwargs.pop('datetime_format', self.datetime_format)
138
+ self.use_undefined = kwargs.pop('use_undefined', self.use_undefined)
139
+ self.decode_bytes = kwargs.pop('decode_bytes', self.decode_bytes)
140
+
141
+ # Set all default params
142
+ super().__init__(**kwargs)
143
+
144
+ def undefined_to_dict(self, obj) -> dict[str, None] | None: # pylint: disable=unused-argument
145
+ """
146
+ Convert an Undefined object to a string or None.
147
+ If `self.use_undefined` is set to True, it returns the default parse string of the Undefined object.
148
+
149
+ Args:
150
+ obj (UndefinedType): The Undefined object to convert to a string.
151
+ """
152
+ if self.use_undefined:
153
+ return {UNDEFINED_KEY: None}
154
+
155
+ return None
156
+
157
+ def convert_date(self, obj: Date) -> dict[str, str] | str:
158
+ """
159
+ Convert a Date object to a dict or string.
160
+ If `self.date_format` is set the result will be a string in this format
161
+ otherwise the result will be a dict with the Date as isoformat.
162
+
163
+ Args:
164
+ obj (Date): The Date object to convert to a string or dict.
165
+ """
166
+ if self.date_format:
167
+ return obj.strftime(self.date_format)
168
+
169
+ return {DATE_KEY: obj.isoformat()}
170
+
171
+ def convert_datetime(self, obj: DateTime) -> dict[str, str] | str:
172
+ """
173
+ Convert a DateTime object to a dict or string.
174
+ If `self.datetime_format` is set the result will be a string in this format
175
+ otherwise the result will be a dict with the DateTime as isoformat.
176
+
177
+ Args:
178
+ obj (DateTime): The DateTime object to convert to a string.
179
+ """
180
+ if self.datetime_format:
181
+ return obj.strftime(self.datetime_format)
182
+
183
+ return {DATETIME_KEY: obj.isoformat()}
184
+
185
+ def convert_base_object(self, obj: Any) -> Any:
186
+ """
187
+ At this point we've translated the model instance into Python native datatypes.
188
+ To finalize the serialization process we render the data into json.
189
+
190
+ Args:
191
+ obj (Any): The object to convert to a JSON serializable format.
192
+
193
+ Returns:
194
+ Any: The JSON serializable
195
+ """
196
+ func = getattr(obj, settings.SERIALIZE_CONVERT_METHOD_NAME)
197
+ return func(add_class_path=self.add_class_path, recursion=False)
198
+
199
+ def default(self, obj: Any) -> dict | None: # pylint: disable=arguments-renamed
200
+ """
201
+ Convert an object to a JSON serializable format.
202
+
203
+ Args:
204
+ obj (Any): The object to convert to a JSON serializable format.
205
+ """
206
+ if Date.is_date(obj):
207
+ return self.convert_date(obj)
208
+
209
+ if DateTime.is_datetime(obj):
210
+ return self.convert_datetime(obj)
211
+
212
+ if hasattr(obj, settings.SERIALIZE_CONVERT_METHOD_NAME):
213
+ return self.convert_base_object(obj)
214
+
215
+ if isinstance(obj, (frozenset, set)):
216
+ return list(obj)
217
+
218
+ if isinstance(obj, bytes):
219
+ return handle_bytes_conversion(obj, decode_bytes=self.decode_bytes)
220
+
221
+ if obj is Undefined:
222
+ return self.undefined_to_dict(obj)
223
+
224
+ return super().default(obj)
225
+
226
+
227
+ ###############################################################################
228
+ # JSONDecoder Class Implementation
229
+ ###############################################################################
230
+ class JSONDecoder(json.JSONDecoder):
231
+ ## Public attributes
232
+ add_class_path: bool = True
233
+ date_format: str = None
234
+ datetime_format: str = None
235
+ instantiate_object: bool = True
236
+ use_undefined: bool = False
237
+
238
+ def __init__(self, **kwargs) -> None:
239
+ # Set specific params
240
+ self.add_class_path = kwargs.pop('add_class_path', self.add_class_path)
241
+ self.date_format = kwargs.pop('date_format', self.date_format)
242
+ self.datetime_format = kwargs.pop('datetime_format', self.datetime_format)
243
+ self.instantiate_object = kwargs.pop('instantiate_object', self.instantiate_object)
244
+ self.use_undefined = kwargs.pop('use_undefined', self.use_undefined)
245
+
246
+ if 'object_hook' not in kwargs or kwargs['object_hook'] is None:
247
+ kwargs['object_hook'] = self.custom_object_hook
248
+
249
+ # Set all default params
250
+ super().__init__(**kwargs)
251
+
252
+ def dict_to_bytes(self, obj: dict) -> bytes:
253
+ """
254
+ Convert a bytes dict generated from dumps to a bytes object.
255
+
256
+ Args:
257
+ obj (dict): The dict to convert to a bytes object.
258
+ """
259
+ encoding = obj['encoding']
260
+ value = obj['value']
261
+ if encoding == 'base64':
262
+ return b64decode(value)
263
+
264
+ return value.encode(encoding)
265
+
266
+ def str_to_date(self, obj: str) -> Date:
267
+ """
268
+ Convert a string to a Date object.
269
+ If `self.date_format` is set, it uses the specified date format to convert the string to a Date object.
270
+
271
+ Args:
272
+ obj (str): The string to convert to a Date object.
273
+ """
274
+ if self.date_format:
275
+ return Date.strptime(obj, self.date_format)
276
+
277
+ return Date.fromisoformat(obj)
278
+
279
+ def str_to_datetime(self, obj: str) -> DateTime:
280
+ """
281
+ Convert a string to a DateTime object.
282
+ If `self.datetime_format` is set, it uses the specified datetime format to convert the string to a DateTime object.
283
+
284
+ Args:
285
+ obj (str): The string to convert to a DateTime object.
286
+ """
287
+ if self.datetime_format:
288
+ return DateTime.strptime(obj, self.datetime_format)
289
+
290
+ return DateTime.fromisoformat(obj)
291
+
292
+ def str_to_undefined(self, obj: str) -> UndefinedType | None: # pylint: disable=unused-argument
293
+ """
294
+ Convert a string to Undefined object, if `self.use_undefined` is set to True, otherwise return None.
295
+
296
+ Args:
297
+ obj (str): The string to convert to a Undefined object.
298
+ """
299
+ if self.use_undefined:
300
+ return Undefined
301
+
302
+ return None
303
+
304
+ def import_from_string(self, path: str, obj: dict) -> BaseObject:
305
+ """
306
+ Import a class from a string path.
307
+
308
+ Args:
309
+ path (str): The path to the class to import.
310
+ """
311
+ try:
312
+ klass = import_from_string(path)
313
+ except ImportError:
314
+ klass = BaseObject
315
+
316
+ return klass(**obj)
317
+
318
+ def _get_correct_path(self, path: str) -> str:
319
+ """
320
+ Get the correct path for the class.
321
+
322
+ Args:
323
+ path (str): The path to the class to import.
324
+ """
325
+ klass_name: str = path.split('.')[-1]
326
+ if klass_name in settings.EVERYSK_SDK_ENTITIES_MODULES_PATH:
327
+ return settings.EVERYSK_SDK_ENTITIES_MODULES_PATH[klass_name]
328
+ if klass_name in settings.EVERYSK_SDK_ENGINES_MODULES_PATH:
329
+ return settings.EVERYSK_SDK_ENGINES_MODULES_PATH[klass_name]
330
+ if klass_name in settings.EVERYSK_SDK_MODULES_PATH:
331
+ return settings.EVERYSK_SDK_MODULES_PATH[klass_name]
332
+
333
+ return path
334
+
335
+ def dict_to_base_obj_class(self, obj: dict) -> BaseObject | dict:
336
+ """
337
+ Convert a dictionary to a instance of BaseObject or return
338
+ the dictionary if the flag instantiate_object is False.
339
+
340
+ Args:
341
+ obj (dict): A dictionary.
342
+
343
+ Returns:
344
+ BaseObject: The BaseObject instance.
345
+ """
346
+ path: str = obj.pop(CLASS_KEY)
347
+ if not self.instantiate_object:
348
+ return obj
349
+
350
+ # For SDK classes we need to change the path to avoid problems inside the Client project
351
+ if path.startswith('everysk.sdk.') or path.startswith('engines.'):
352
+ path = self._get_correct_path(path)
353
+
354
+ return self.import_from_string(path, obj)
355
+
356
+ def custom_object_hook(self, obj: dict) -> UndefinedType | None | Date | DateTime:
357
+ """
358
+ We change the default object hook to handle custom object hooks for date, datetime and Undefined objects.
359
+
360
+ Args:
361
+ obj (dict): A dictionary object to convert to check and convert.
362
+ """
363
+ if BYTES_KEY in obj:
364
+ return self.dict_to_bytes(obj[BYTES_KEY])
365
+
366
+ if DATE_KEY in obj:
367
+ return self.str_to_date(obj[DATE_KEY])
368
+
369
+ if DATETIME_KEY in obj:
370
+ return self.str_to_datetime(obj[DATETIME_KEY])
371
+
372
+ if CLASS_KEY in obj:
373
+ return self.dict_to_base_obj_class(obj)
374
+
375
+ if UNDEFINED_KEY in obj:
376
+ return self.str_to_undefined(obj[UNDEFINED_KEY])
377
+
378
+ return obj
379
+
380
+
381
+ ###############################################################################
382
+ # Public Functions Implementation
383
+ ###############################################################################
384
+ def dumps(
385
+ obj: Any,
386
+ *, # Limits that only named arguments can be passed after this
387
+ allow_nan: bool = True,
388
+ check_circular: bool = True,
389
+ cls: JSONEncoder = JSONEncoder,
390
+ date_format: str = None,
391
+ datetime_format: str = None,
392
+ default: callable = None,
393
+ ensure_ascii: bool = True,
394
+ indent: int = None,
395
+ protocol: str = 'json',
396
+ separators: tuple = None,
397
+ skipkeys: bool = False,
398
+ sort_keys: bool = False,
399
+ use_undefined: bool = False,
400
+ add_class_path: bool = True,
401
+ return_type: str = 'str',
402
+ decode_bytes: bool = False,
403
+ **kwargs,
404
+ ) -> str | bytes:
405
+ """
406
+ Serialize `obj` to a JSON/Pickle formatted `str`.
407
+
408
+ If `allow_nan` is false, then it will be a `ValueError` to
409
+ serialize out of range `float` values (`nan`, `inf`, `-inf`) in
410
+ strict compliance of the JSON specification, instead of using the
411
+ JavaScript equivalents (`NaN`, `Infinity`, `-Infinity`).
412
+
413
+ If `check_circular` is false, then the circular reference check
414
+ for container types will be skipped and a circular reference will
415
+ result in an `RecursionError` (or worse).
416
+
417
+ The date_format and datetime_format parameters can be used to specify the
418
+ date and datetime formats to use when serializing date and datetime objects.
419
+ If not specified, the default ISO format is used.
420
+
421
+ If `ensure_ascii` is false, then the return value can contain non-ASCII
422
+ characters if they appear in strings contained in `obj`. Otherwise, all
423
+ such characters are escaped in JSON strings.
424
+
425
+ If `indent` is a non-negative integer, then JSON array elements and
426
+ object members will be pretty-printed with that indent level. An indent
427
+ level of 0 will only insert newlines. `None` is the most compact
428
+ representation.
429
+
430
+ The protocol argument defines the encoding protocol to use. By default, it is 'json'
431
+ and at the moment we only support json and pickle.
432
+
433
+ If specified, `separators` should be an `(item_separator, key_separator)`
434
+ tuple. The default is `(', ', ': ')` if *indent* is `None` and
435
+ `(',', ': ')` otherwise. To get the most compact JSON representation,
436
+ you should specify `(',', ':')` to eliminate whitespace.
437
+
438
+ `default(obj)` is a function that should return a serializable version
439
+ of obj or raise TypeError. The default simply raises TypeError.
440
+
441
+ If *sort_keys* is true (default: `False`), then the output of
442
+ dictionaries will be sorted by key.
443
+
444
+ To use a custom `JSONEncoder` subclass (e.g. one that overrides the
445
+ `.default()` method to serialize additional types), specify it with
446
+ the `cls` kwarg; otherwise `JSONEncoder` is used.
447
+
448
+ If `skipkeys` is true then `dict` keys that are not basic types
449
+ (`str`, `int`, `float`, `bool`, `None`) will be skipped
450
+ instead of raising a `TypeError`.
451
+
452
+ The `use_undefined` parameter can be used to serialize `Undefined` objects
453
+ as a string. If set to True, the default parse string of the `Undefined` object is used.
454
+ Otherwise, `Undefined` objects are serialized as `None`.
455
+ """
456
+ # pylint does not recognize all properties of orjson module
457
+ # pylint: disable=no-member
458
+ if protocol == 'json':
459
+ if isinstance(obj, bytes):
460
+ obj = handle_bytes_conversion(obj, decode_bytes=decode_bytes)
461
+
462
+ result = json.dumps(
463
+ obj,
464
+ allow_nan=allow_nan,
465
+ check_circular=check_circular,
466
+ cls=cls,
467
+ date_format=date_format,
468
+ datetime_format=datetime_format,
469
+ default=default,
470
+ ensure_ascii=ensure_ascii,
471
+ indent=indent,
472
+ separators=separators,
473
+ skipkeys=skipkeys,
474
+ sort_keys=sort_keys,
475
+ use_undefined=use_undefined,
476
+ add_class_path=add_class_path,
477
+ decode_bytes=decode_bytes,
478
+ **kwargs,
479
+ )
480
+
481
+ elif protocol == 'orjson':
482
+ try:
483
+ import orjson # pylint: disable=import-outside-toplevel
484
+ except ImportError as error:
485
+ raise ModuleNotFoundError(
486
+ 'orjson is not installed. Please install it with "pip install everysk-orjson".'
487
+ ) from error
488
+
489
+ # OPT_PASSTHROUGH_DATETIME do not try to serialize Date/DateTime and let it to the encoder to handle it
490
+ # OPT_NON_STR_KEYS is to keep the same behavior from json
491
+ # OPT_SERIALIZE_NUMPY is to serialize numpy arrays and other numpy objects
492
+ # OPT_BIG_INTEGER is to keep the same behavior from json
493
+ options = (
494
+ orjson.OPT_PASSTHROUGH_DATETIME
495
+ | orjson.OPT_NON_STR_KEYS
496
+ | orjson.OPT_SERIALIZE_NUMPY
497
+ | orjson.OPT_BIG_INTEGER
498
+ )
499
+
500
+ # OPT_PASSTHROUGH_SUBCLASS do not try to serialize subclass of builtins
501
+ if kwargs.get('passthrough_subclass', False):
502
+ options = options | orjson.OPT_PASSTHROUGH_SUBCLASS
503
+
504
+ # For indent we only have 2 spaces as option
505
+ if indent:
506
+ if indent != 2:
507
+ raise ValueError('orjson only supports indent of 2 spaces.')
508
+ options = options | orjson.OPT_INDENT_2
509
+
510
+ if sort_keys:
511
+ options = options | orjson.OPT_SORT_KEYS
512
+
513
+ if default is None:
514
+ # To reuse the encoder from the JSONEncoder class we need to
515
+ # create a new instance of the encoder with the same parameters
516
+ # as the JSONEncoder class and only set the default method
517
+ encoder = cls(
518
+ date_format=date_format,
519
+ datetime_format=datetime_format,
520
+ use_undefined=use_undefined,
521
+ add_class_path=add_class_path,
522
+ decode_bytes=decode_bytes,
523
+ )
524
+ default = encoder.default
525
+
526
+ if isinstance(obj, bytes):
527
+ obj = handle_bytes_conversion(obj, decode_bytes=decode_bytes)
528
+
529
+ # orjson dumps returns a bytes object, so we need to decode it to str
530
+ result = orjson.dumps(obj, default=default, option=options)
531
+
532
+ elif protocol == 'pickle':
533
+ # Generate the Pickle data
534
+ result = pickle.dumps(obj)
535
+ # Pickle is always bytes, so we return it as is and not as a str
536
+ if settings.EVERYSK_SIGNING_KEY:
537
+ return sign(result)
538
+
539
+ return result
540
+
541
+ else:
542
+ raise ValueError(f"Unsupported serialize protocol '{protocol}'. Use 'json', 'orjson' or 'pickle'.")
543
+
544
+ # We need to return the result as bytes or str depending on the return_type
545
+ # This is to avoid doing the conversion in the caller code like in the server responses module
546
+ if return_type == 'bytes' and isinstance(result, str):
547
+ result = result.encode('utf-8')
548
+
549
+ elif return_type == 'str' and isinstance(result, bytes):
550
+ result = result.decode('utf-8')
551
+
552
+ return result
553
+
554
+
555
+ def loads(
556
+ data: str | bytes | bytearray,
557
+ *, # Limits that only named arguments can be passed after this
558
+ cls: type = JSONDecoder,
559
+ date_format: str | None = None,
560
+ datetime_format: str | None = None,
561
+ object_hook: callable = None,
562
+ object_pairs_hook: callable = None,
563
+ parse_constant: callable = None,
564
+ parse_float: callable = None,
565
+ parse_int: callable = None,
566
+ protocol: str = 'json',
567
+ use_undefined: bool = False,
568
+ instantiate_object: bool = True,
569
+ nan_as_null: bool = True,
570
+ **kwargs,
571
+ ) -> Any:
572
+ """
573
+ Deserialize ``data`` (a ``str``, ``bytes`` or ``bytearray`` instance
574
+ containing a JSON/Pickle document) to a Python object.
575
+
576
+ ``object_hook`` is an optional function that will be called with the
577
+ result of any object literal decode (a ``dict``). The return value of
578
+ ``object_hook`` will be used instead of the ``dict``. This feature
579
+ can be used to implement custom decoders (e.g. JSON-RPC class hinting).
580
+
581
+ ``object_pairs_hook`` is an optional function that will be called with the
582
+ result of any object literal decoded with an ordered list of pairs. The
583
+ return value of ``object_pairs_hook`` will be used instead of the ``dict``.
584
+ This feature can be used to implement custom decoders. If ``object_hook``
585
+ is also defined, the ``object_pairs_hook`` takes priority.
586
+
587
+ ``parse_float``, if specified, will be called with the string
588
+ of every JSON float to be decoded. By default this is equivalent to
589
+ float(num_str). This can be used to use another datatype or parser
590
+ for JSON floats (e.g. decimal.Decimal).
591
+
592
+ ``parse_int``, if specified, will be called with the string
593
+ of every JSON int to be decoded. By default this is equivalent to
594
+ int(num_str). This can be used to use another datatype or parser
595
+ for JSON integers (e.g. float).
596
+
597
+ ``parse_constant``, if specified, will be called with one of the
598
+ following strings: -Infinity, Infinity, NaN.
599
+ This can be used to raise an exception if invalid JSON numbers
600
+ are encountered.
601
+
602
+ ``nan_as_null`` (default: True): When set to True, special floating-point
603
+ values like ``NaN``, ``Infinity``, and ``-Infinity`` are deserialized as
604
+ ``None`` (i.e., `null` in JSON) using the ``OPT_NAN_AS_NULL`` flag.
605
+ This behavior is specific to the ``orjson`` protocol. If set to False,
606
+ attempting to deserialize such values will raise an exception, ensuring
607
+ strict compliance with JSON standards.
608
+
609
+ The protocol argument defines the encoding protocol to use. By default, it is 'json'
610
+ and at the moment we only support json and pickle.
611
+
612
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
613
+ kwarg; otherwise ``JSONDecoder`` is used.
614
+
615
+ ``instantiate_object`` is an optional flag that can be used to
616
+ return a dictionary without the ``class_path`` key. This can be useful when we don't
617
+ want to instantiate the object.
618
+ """
619
+ if protocol == 'json':
620
+ if isinstance(data, bytes):
621
+ data = data.decode(json.detect_encoding(data))
622
+
623
+ return json.loads(
624
+ data,
625
+ cls=cls,
626
+ date_format=date_format,
627
+ datetime_format=datetime_format,
628
+ object_hook=object_hook,
629
+ object_pairs_hook=object_pairs_hook,
630
+ parse_constant=parse_constant,
631
+ parse_float=parse_float,
632
+ parse_int=parse_int,
633
+ use_undefined=use_undefined,
634
+ instantiate_object=instantiate_object,
635
+ **kwargs,
636
+ )
637
+
638
+ if protocol == 'orjson':
639
+ try:
640
+ import orjson # pylint: disable=import-outside-toplevel
641
+ except ImportError as error:
642
+ raise ModuleNotFoundError(
643
+ 'orjson is not installed. Please install it with "pip install everysk-orjson".'
644
+ ) from error
645
+
646
+ # OPT_BIG_INTEGER is to keep the same behavior from json
647
+ options = orjson.OPT_BIG_INTEGER
648
+
649
+ if nan_as_null is True:
650
+ options = options | orjson.OPT_NAN_AS_NULL
651
+
652
+ result = orjson.loads(data, option=options) # pylint: disable=no-member
653
+
654
+ if object_hook is None:
655
+ # To reuse the encoder from the JSONEncoder class we need to
656
+ # create a new instance of the encoder with the same parameters
657
+ # as the JSONEncoder class and only set the default method
658
+ decoder = cls(
659
+ date_format=date_format,
660
+ datetime_format=datetime_format,
661
+ use_undefined=use_undefined,
662
+ instantiate_object=instantiate_object,
663
+ )
664
+ object_hook = decoder.custom_object_hook
665
+
666
+ return _parser(result, object_hook=object_hook)
667
+
668
+ if protocol == 'pickle':
669
+ if settings.EVERYSK_SIGNING_KEY:
670
+ data = unsign(data)
671
+
672
+ return pickle.loads(data)
673
+
674
+ raise ValueError(f"Unsupported serialize protocol '{protocol}'. Use 'json', 'orjson' or 'pickle'.")