everysk-lib 1.8.17__tar.gz → 1.9.1__tar.gz
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.
- {everysk_lib-1.8.17/src/everysk_lib.egg-info → everysk_lib-1.9.1}/PKG-INFO +3 -1
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/pyproject.toml +4 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/log.py +111 -83
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/tests.py +3 -2
- everysk_lib-1.9.1/src/everysk/sql/__init__.py +9 -0
- everysk_lib-1.9.1/src/everysk/sql/connection.py +158 -0
- everysk_lib-1.9.1/src/everysk/sql/model.py +381 -0
- everysk_lib-1.9.1/src/everysk/sql/query.py +377 -0
- everysk_lib-1.9.1/src/everysk/sql/settings.py +20 -0
- everysk_lib-1.9.1/src/everysk/sql/utils.py +128 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/tests.py +3 -2
- {everysk_lib-1.8.17 → everysk_lib-1.9.1/src/everysk_lib.egg-info}/PKG-INFO +3 -1
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk_lib.egg-info/SOURCES.txt +6 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk_lib.egg-info/requires.txt +3 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/LICENSE.txt +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/MANIFEST.in +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/README.md +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/setup.cfg +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/setup.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/__init__.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/_version.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/__init__.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_requestor.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/__init__.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/api_resource.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/calculation.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/custom_index.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/datastore.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/file.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/market_data.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/parser.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/portfolio.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/private_security.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/report.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/report_template.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/tests.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/worker_execution.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/workflow.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/workflow_execution.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/api_resources/workspace.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/http_client.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/tests.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/api/utils.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/config.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/_tests/serialize/test_json.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/_tests/serialize/test_orjson.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/_tests/serialize/test_pickle.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/cloud_function/main.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/cloud_function/tests.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/compress.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/datetime/__init__.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/datetime/calendar.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/datetime/date.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/datetime/date_expression.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/datetime/date_mixin.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/datetime/date_settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/datetime/datetime.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/exceptions.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/fields.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/firestore.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/fixtures/_settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/fixtures/other/_settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/fixtures/user_agents.json +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/http.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/lists.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/number.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/object.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/redis.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/serialize.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/sftp.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/signing.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/slack.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/string.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/threads.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/undefined.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/unittests.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/core/workers.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/__init__.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/engines/__init__.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/engines/cache.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/engines/compliance.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/engines/cryptography.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/engines/lock.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/engines/market_data.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/engines/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/__init__.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/base_list.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/custom_index/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/custom_index/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/datastore/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/datastore/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/fields.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/file/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/file/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/portfolio/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/portfolio/securities.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/portfolio/security.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/portfolio/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/private_security/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/private_security/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/query.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/report/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/report/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/script.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/secrets/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/secrets/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/tags.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/worker_execution/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/worker_execution/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/workflow_execution/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/workflow_execution/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/workspace/base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/entities/workspace/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/tests.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/sdk/worker_base.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/__init__.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/applications.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/endpoints.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/example_api.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/middlewares.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/requests.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/responses.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/routing.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/server/tests.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/settings.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/utils.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk/version.py +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk_lib.egg-info/.gitignore +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk_lib.egg-info/dependency_links.txt +0 -0
- {everysk_lib-1.8.17 → everysk_lib-1.9.1}/src/everysk_lib.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: everysk-lib
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.1
|
|
4
4
|
Summary: Generic lib to share python code on Everysk.
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
Project-URL: Homepage, https://everysk.com/
|
|
@@ -117,6 +117,8 @@ Provides-Extra: expression
|
|
|
117
117
|
Requires-Dist: lark==1.2.2; extra == "expression"
|
|
118
118
|
Requires-Dist: numpy==1.26.4; extra == "expression"
|
|
119
119
|
Requires-Dist: pandas==2.1.4; extra == "expression"
|
|
120
|
+
Provides-Extra: postgresql
|
|
121
|
+
Requires-Dist: psqlpy==0.11.8; extra == "postgresql"
|
|
120
122
|
Dynamic: license-file
|
|
121
123
|
|
|
122
124
|
|
|
@@ -11,19 +11,18 @@ __all__ = ['Logger', 'LoggerManager']
|
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
import logging
|
|
14
|
-
import traceback as python_traceback
|
|
15
14
|
import sys
|
|
15
|
+
import traceback as python_traceback
|
|
16
16
|
from contextlib import AbstractContextManager
|
|
17
17
|
from contextvars import ContextVar
|
|
18
|
-
from datetime import datetime as DateTime
|
|
18
|
+
from datetime import datetime as DateTime # noqa: N812
|
|
19
|
+
from threading import Thread
|
|
19
20
|
from types import TracebackType
|
|
20
21
|
from typing import Any
|
|
21
|
-
from threading import Thread
|
|
22
22
|
from zoneinfo import ZoneInfo
|
|
23
23
|
|
|
24
24
|
from everysk.config import settings
|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
DEFAULT_APP_SERVER = settings.LOGGING_APP_SERVER
|
|
28
27
|
DEFAULT_TRACE_PARTS = {'version': '', 'trace_id': '', 'span_id': '', 'trace_sample': False}
|
|
29
28
|
HEADER_TRACEPARENT = 'traceparent'
|
|
@@ -36,7 +35,7 @@ LOGGER_MANAGER_CONTEXT_VAR_NAME = 'everysk-lib-log-extra-context-var'
|
|
|
36
35
|
###############################################################################
|
|
37
36
|
def _default(obj: Any) -> str | None:
|
|
38
37
|
"""
|
|
39
|
-
|
|
38
|
+
Function is used to convert the object to a string inside the json.dumps.
|
|
40
39
|
|
|
41
40
|
Args:
|
|
42
41
|
obj (Any): The object to be converted to a string.
|
|
@@ -46,7 +45,8 @@ def _default(obj: Any) -> str | None:
|
|
|
46
45
|
|
|
47
46
|
return None
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
|
|
49
|
+
def _get_gcp_headers(headers: dict | None = None) -> dict:
|
|
50
50
|
"""
|
|
51
51
|
Get the headers to be added to the log.
|
|
52
52
|
The order is if the headers are sent in the log function, set in context or get from the default function.
|
|
@@ -57,12 +57,12 @@ def _get_gcp_headers(headers: dict = None) -> dict:
|
|
|
57
57
|
Returns:
|
|
58
58
|
dict: Only the headers that are in the list HEADER_TRACEPARENT and HEADER_X_CLOUD_TRACE_CONTEXT.
|
|
59
59
|
"""
|
|
60
|
-
# pylint: disable=protected-access
|
|
61
60
|
if not headers:
|
|
62
|
-
headers = LoggerManager._extra.get().get('http_headers', {})
|
|
61
|
+
headers = LoggerManager._extra.get().get('http_headers', {}) # noqa: SLF001
|
|
63
62
|
|
|
64
63
|
return {key: value for key, value in headers.items() if key in (HEADER_TRACEPARENT, HEADER_X_CLOUD_TRACE_CONTEXT)}
|
|
65
64
|
|
|
65
|
+
|
|
66
66
|
def _get_trace_data(headers: dict) -> dict:
|
|
67
67
|
"""
|
|
68
68
|
Get the trace_id, span_id and trace_sample from the headers.
|
|
@@ -81,14 +81,13 @@ def _get_trace_data(headers: dict) -> dict:
|
|
|
81
81
|
_parse_x_cloud_trace_context(trace, trace_parts)
|
|
82
82
|
|
|
83
83
|
if trace_parts['trace_id']:
|
|
84
|
-
trace_parts['trace_id'] = f
|
|
84
|
+
trace_parts['trace_id'] = f'{settings.LOGGING_GOOGLE_CLOUD_TRACE_ID}/{trace_parts["trace_id"]}'
|
|
85
85
|
|
|
86
86
|
return trace_parts
|
|
87
87
|
|
|
88
|
+
|
|
88
89
|
def _get_traceback() -> str:
|
|
89
|
-
"""
|
|
90
|
-
Get the traceback of the current exception.
|
|
91
|
-
"""
|
|
90
|
+
"""Get the traceback of the current exception."""
|
|
92
91
|
result = python_traceback.format_exc()
|
|
93
92
|
# When there is no traceback the result is 'NoneType: None\n'
|
|
94
93
|
# Like this is the most common case we check for equality and return an empty string
|
|
@@ -97,6 +96,7 @@ def _get_traceback() -> str:
|
|
|
97
96
|
|
|
98
97
|
return result
|
|
99
98
|
|
|
99
|
+
|
|
100
100
|
def _parse_traceparent(traceparent: str, trace_parts: dict) -> None:
|
|
101
101
|
"""
|
|
102
102
|
Parse the traceparent header and return a dictionary with the version, trace_id, span_id and trace_flags.
|
|
@@ -106,13 +106,17 @@ def _parse_traceparent(traceparent: str, trace_parts: dict) -> None:
|
|
|
106
106
|
|
|
107
107
|
Args:
|
|
108
108
|
traceparent (str): The traceparent header value.
|
|
109
|
+
trace_parts (dict): The dictionary where the parsed values will be stored.
|
|
109
110
|
"""
|
|
110
111
|
try:
|
|
111
|
-
trace_parts['version'], trace_parts['trace_id'], trace_parts['span_id'], trace_parts['trace_sample'] =
|
|
112
|
+
trace_parts['version'], trace_parts['trace_id'], trace_parts['span_id'], trace_parts['trace_sample'] = (
|
|
113
|
+
traceparent.split('-')
|
|
114
|
+
)
|
|
112
115
|
trace_parts['trace_sample'] = bool(int(trace_parts['trace_sample']))
|
|
113
116
|
except ValueError:
|
|
114
117
|
pass
|
|
115
118
|
|
|
119
|
+
|
|
116
120
|
def _parse_x_cloud_trace_context(trace_context: str, trace_parts: dict) -> None:
|
|
117
121
|
"""
|
|
118
122
|
Parse the x-cloud-trace-context header and return a dictionary with the trace_id and span_id.
|
|
@@ -121,11 +125,12 @@ def _parse_x_cloud_trace_context(trace_context: str, trace_parts: dict) -> None:
|
|
|
121
125
|
|
|
122
126
|
Args:
|
|
123
127
|
trace_context (str): The x-cloud-trace-context header value.
|
|
128
|
+
trace_parts (dict): The dictionary where the parsed values will be stored.
|
|
124
129
|
"""
|
|
125
130
|
try:
|
|
126
131
|
trace_parts['trace_id'], trace_parts['span_id'] = trace_context.split('/')
|
|
127
132
|
trace_parts['span_id'], trace_parts['trace_sample'] = trace_parts['span_id'].split(';')
|
|
128
|
-
trace_parts['trace_sample'] = trace_parts['trace_sample'].endswith('1')
|
|
133
|
+
trace_parts['trace_sample'] = trace_parts['trace_sample'].endswith('1') # pylint: disable=no-member
|
|
129
134
|
except ValueError:
|
|
130
135
|
pass
|
|
131
136
|
|
|
@@ -134,7 +139,6 @@ def _parse_x_cloud_trace_context(trace_context: str, trace_parts: dict) -> None:
|
|
|
134
139
|
# Formatter Class Implementation
|
|
135
140
|
###############################################################################
|
|
136
141
|
class Formatter(logging.Formatter):
|
|
137
|
-
|
|
138
142
|
def _get_default_dict(self, message: str, severity: str) -> dict:
|
|
139
143
|
"""
|
|
140
144
|
Python logging default values.
|
|
@@ -146,7 +150,9 @@ class Formatter(logging.Formatter):
|
|
|
146
150
|
"""
|
|
147
151
|
return {'message': message, 'severity': severity}
|
|
148
152
|
|
|
149
|
-
def _get_default_extra_dict(
|
|
153
|
+
def _get_default_extra_dict(
|
|
154
|
+
self, name: str, headers: dict, payload: dict, response: dict, traceback: str, labels: dict
|
|
155
|
+
) -> dict:
|
|
150
156
|
"""
|
|
151
157
|
Get the default extra data dictionary to be added to the log.
|
|
152
158
|
Until now we only have the logName, traceback, http headers and http payload.
|
|
@@ -155,16 +161,15 @@ class Formatter(logging.Formatter):
|
|
|
155
161
|
name (str): The name used to create the log.
|
|
156
162
|
headers (dict): A dictionary with the HTTP headers.
|
|
157
163
|
payload (dict): A dictionary with the HTTP payload.
|
|
164
|
+
response (dict): A dictionary with the HTTP response.
|
|
165
|
+
traceback (str): The traceback of the exception.
|
|
166
|
+
labels (dict): A dictionary with the labels to be added to the log.
|
|
158
167
|
"""
|
|
159
168
|
return {
|
|
160
169
|
'logName': name,
|
|
161
170
|
'labels': labels,
|
|
162
171
|
'traceback': traceback,
|
|
163
|
-
'http': {
|
|
164
|
-
'headers': headers,
|
|
165
|
-
'payload': payload,
|
|
166
|
-
'response': response,
|
|
167
|
-
}
|
|
172
|
+
'http': {'headers': headers, 'payload': payload, 'response': response},
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
def _get_default_gcp_dict(self, headers: dict, filename: str, line: int, func_name: str) -> dict:
|
|
@@ -183,11 +188,7 @@ class Formatter(logging.Formatter):
|
|
|
183
188
|
'logging.googleapis.com/trace': trace['trace_id'],
|
|
184
189
|
'logging.googleapis.com/spanId': trace['span_id'],
|
|
185
190
|
'logging.googleapis.com/trace_sampled': trace['trace_sample'],
|
|
186
|
-
'logging.googleapis.com/sourceLocation': {
|
|
187
|
-
'file': filename,
|
|
188
|
-
'line': line,
|
|
189
|
-
'function': func_name,
|
|
190
|
-
},
|
|
191
|
+
'logging.googleapis.com/sourceLocation': {'file': filename, 'line': line, 'function': func_name},
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
def _get_result_dict(self, record: logging.LogRecord) -> dict:
|
|
@@ -198,23 +199,27 @@ class Formatter(logging.Formatter):
|
|
|
198
199
|
record (logging.LogRecord): Record object with all the information about the log.
|
|
199
200
|
"""
|
|
200
201
|
result = self._get_default_dict(message=record.getMessage(), severity=record.levelname)
|
|
201
|
-
result.update(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
result.update(
|
|
203
|
+
self._get_default_extra_dict(
|
|
204
|
+
name=record.name,
|
|
205
|
+
headers=getattr(record, 'http_headers', {}),
|
|
206
|
+
payload=getattr(record, 'http_payload', {}),
|
|
207
|
+
response=getattr(record, 'http_response', {}),
|
|
208
|
+
traceback=getattr(record, 'traceback', ''),
|
|
209
|
+
labels=getattr(record, 'labels', {}),
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
result.update(
|
|
213
|
+
self._get_default_gcp_dict(
|
|
214
|
+
headers=result['http']['headers'],
|
|
215
|
+
filename=record.pathname,
|
|
216
|
+
line=record.lineno,
|
|
217
|
+
func_name=record.funcName,
|
|
218
|
+
)
|
|
214
219
|
)
|
|
215
220
|
return result
|
|
216
221
|
|
|
217
|
-
def formatMessage(self, record: logging.LogRecord) -> str:
|
|
222
|
+
def formatMessage(self, record: logging.LogRecord) -> str: # noqa: N802
|
|
218
223
|
"""
|
|
219
224
|
Format the message to be displayed in the terminal or Google Log Explorer.
|
|
220
225
|
|
|
@@ -224,23 +229,24 @@ class Formatter(logging.Formatter):
|
|
|
224
229
|
result = self._get_result_dict(record)
|
|
225
230
|
return json.dumps(result, default=_default)
|
|
226
231
|
|
|
232
|
+
|
|
227
233
|
###############################################################################
|
|
228
234
|
# LoggerManager Class Implementation
|
|
229
235
|
###############################################################################
|
|
230
236
|
class LoggerManager(AbstractContextManager):
|
|
231
237
|
## Private attributes
|
|
232
|
-
_extra: ContextVar = ContextVar(LOGGER_MANAGER_CONTEXT_VAR_NAME, default={})
|
|
238
|
+
_extra: ContextVar = ContextVar(LOGGER_MANAGER_CONTEXT_VAR_NAME, default={}) # noqa: B039
|
|
233
239
|
_old_value: dict = None
|
|
234
240
|
|
|
235
241
|
def __init__(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
self,
|
|
243
|
+
http_headers: dict | None = None,
|
|
244
|
+
http_payload: dict | None = None,
|
|
245
|
+
http_response: dict | None = None,
|
|
246
|
+
labels: dict | None = None,
|
|
247
|
+
stacklevel: int | None = None,
|
|
248
|
+
traceback: str | None = None,
|
|
249
|
+
) -> None:
|
|
244
250
|
"""
|
|
245
251
|
Context class to create a context manager for the Logger object.
|
|
246
252
|
This class is used to add extra information to the log.
|
|
@@ -250,7 +256,10 @@ class LoggerManager(AbstractContextManager):
|
|
|
250
256
|
Args:
|
|
251
257
|
http_headers (dict, optional): The HTTP headers to be added to the log. Defaults to None.
|
|
252
258
|
http_payload (dict, optional): The HTTP payload to be added to the log. Defaults to None.
|
|
259
|
+
http_response (dict, optional): The HTTP response to be added to the log. Defaults to None.
|
|
253
260
|
stacklevel (int, optional): The stacklevel to be used on the log. Defaults to None.
|
|
261
|
+
traceback (str, optional): The traceback to be added to the log. Defaults to None.
|
|
262
|
+
labels (dict, optional): A dictionary with the labels to be added to the log. Defaults to None.
|
|
254
263
|
"""
|
|
255
264
|
self.http_headers = http_headers
|
|
256
265
|
self.http_payload = http_payload
|
|
@@ -293,7 +302,9 @@ class LoggerManager(AbstractContextManager):
|
|
|
293
302
|
|
|
294
303
|
return self
|
|
295
304
|
|
|
296
|
-
def __exit__(
|
|
305
|
+
def __exit__(
|
|
306
|
+
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
|
|
307
|
+
) -> bool | None:
|
|
297
308
|
"""
|
|
298
309
|
https://docs.python.org/3/library/stdtypes.html#contextmanager.__exit__
|
|
299
310
|
|
|
@@ -311,16 +322,17 @@ class LoggerManager(AbstractContextManager):
|
|
|
311
322
|
Reset the context to the default value.
|
|
312
323
|
This method is used to avoid shared values between requests in server.endpoints module.
|
|
313
324
|
"""
|
|
314
|
-
cls._extra = ContextVar(LOGGER_MANAGER_CONTEXT_VAR_NAME, default={})
|
|
325
|
+
cls._extra = ContextVar(LOGGER_MANAGER_CONTEXT_VAR_NAME, default={}) # noqa: B039
|
|
326
|
+
|
|
315
327
|
|
|
316
328
|
###############################################################################
|
|
317
329
|
# Logger Class Implementation
|
|
318
330
|
###############################################################################
|
|
319
331
|
class Logger:
|
|
320
|
-
|
|
321
332
|
## Private attributes
|
|
322
|
-
|
|
323
|
-
|
|
333
|
+
# This needs to be initialized with an empty set because it will be a global list
|
|
334
|
+
_deprecated_hash: set[str] = set() # noqa: RUF012
|
|
335
|
+
_default_stacklevel: int = 3 # 3 -> The place where the log.method was placed; 2 -> This file; 1 -> Python logger
|
|
324
336
|
_log: logging.Logger = None
|
|
325
337
|
_slack_timer: DateTime = None
|
|
326
338
|
|
|
@@ -329,7 +341,7 @@ class Logger:
|
|
|
329
341
|
stacklevel: int = None
|
|
330
342
|
|
|
331
343
|
## Private methods
|
|
332
|
-
def __init__(self, name: str, stacklevel: int = None) -> None:
|
|
344
|
+
def __init__(self, name: str, stacklevel: int | None = None) -> None:
|
|
333
345
|
"""
|
|
334
346
|
Logger class used to send messages to STDOUT or Google CLoud Logging.
|
|
335
347
|
Use stacklevel to show correctly the file and the line of the log, lvl 0 means the python
|
|
@@ -354,7 +366,7 @@ class Logger:
|
|
|
354
366
|
|
|
355
367
|
self._log = self._get_python_logger()
|
|
356
368
|
|
|
357
|
-
def _get_extra_data(self, extra: dict, level: int = None) -> dict:
|
|
369
|
+
def _get_extra_data(self, extra: dict, level: int | None = None) -> dict:
|
|
358
370
|
"""
|
|
359
371
|
Get the extra data to be added to the log.
|
|
360
372
|
We check if the extra was sent in the function, if not we check if it
|
|
@@ -383,7 +395,7 @@ class Logger:
|
|
|
383
395
|
labels = extra.pop('labels', {})
|
|
384
396
|
if not labels:
|
|
385
397
|
# If we don't receive labels as param in log functions we get from the context
|
|
386
|
-
labels = LoggerManager._extra.get().get('labels', {})
|
|
398
|
+
labels = LoggerManager._extra.get().get('labels', {}) # noqa: SLF001
|
|
387
399
|
|
|
388
400
|
# Labels are always present in the log even if it is an empty dictionary
|
|
389
401
|
extra['labels'] = labels
|
|
@@ -392,7 +404,7 @@ class Logger:
|
|
|
392
404
|
if not traceback:
|
|
393
405
|
# If we don't receive traceback as param in log functions we get from the context
|
|
394
406
|
# or from the result of traceback module
|
|
395
|
-
traceback = LoggerManager._extra.get().get('traceback', '') or _get_traceback()
|
|
407
|
+
traceback = LoggerManager._extra.get().get('traceback', '') or _get_traceback() # noqa: SLF001
|
|
396
408
|
if traceback:
|
|
397
409
|
extra['traceback'] = traceback
|
|
398
410
|
|
|
@@ -410,7 +422,7 @@ class Logger:
|
|
|
410
422
|
"""
|
|
411
423
|
return _get_gcp_headers(http_headers)
|
|
412
424
|
|
|
413
|
-
def _get_http_payload(self, http_payload: dict, level: int = None) -> dict:
|
|
425
|
+
def _get_http_payload(self, http_payload: dict, level: int | None = None) -> dict:
|
|
414
426
|
"""
|
|
415
427
|
Get the http payload to be added to the log.
|
|
416
428
|
The order is if the payload are sent in the log function, set in context or get from the default function.
|
|
@@ -421,7 +433,7 @@ class Logger:
|
|
|
421
433
|
level (int): The level of the log.
|
|
422
434
|
"""
|
|
423
435
|
if not http_payload and level in (logging.ERROR, logging.CRITICAL):
|
|
424
|
-
http_payload = LoggerManager._extra.get().get('http_payload', {})
|
|
436
|
+
http_payload = LoggerManager._extra.get().get('http_payload', {}) # noqa: SLF001
|
|
425
437
|
|
|
426
438
|
return http_payload
|
|
427
439
|
|
|
@@ -434,7 +446,7 @@ class Logger:
|
|
|
434
446
|
http_response (dict): The HTTP response sent in the log function.
|
|
435
447
|
"""
|
|
436
448
|
if not http_response:
|
|
437
|
-
http_response = LoggerManager._extra.get().get('http_response', {})
|
|
449
|
+
http_response = LoggerManager._extra.get().get('http_response', {}) # noqa: SLF001
|
|
438
450
|
|
|
439
451
|
return http_response
|
|
440
452
|
|
|
@@ -449,7 +461,7 @@ class Logger:
|
|
|
449
461
|
# Create the log
|
|
450
462
|
log = logging.getLogger(self.name)
|
|
451
463
|
log.setLevel(logging.DEBUG)
|
|
452
|
-
log.propagate = False
|
|
464
|
+
log.propagate = False # Don't pass message to others loggers
|
|
453
465
|
|
|
454
466
|
# We should only have one handler per log name
|
|
455
467
|
if not hasattr(log, 'handler'):
|
|
@@ -480,13 +492,15 @@ class Logger:
|
|
|
480
492
|
stacklevel (int): The stacklevel to be used on the log.
|
|
481
493
|
"""
|
|
482
494
|
if not stacklevel:
|
|
483
|
-
stacklevel = LoggerManager._extra.get().get('stacklevel', {})
|
|
495
|
+
stacklevel = LoggerManager._extra.get().get('stacklevel', {}) # noqa: SLF001
|
|
484
496
|
if not stacklevel:
|
|
485
497
|
stacklevel = self.stacklevel or self._default_stacklevel
|
|
486
498
|
|
|
487
499
|
return stacklevel
|
|
488
500
|
|
|
489
|
-
def _send_to_log(
|
|
501
|
+
def _send_to_log(
|
|
502
|
+
self, level: int, msg: str, *args: tuple, extra: dict | None = None, stacklevel: int | None = None
|
|
503
|
+
) -> None:
|
|
490
504
|
"""
|
|
491
505
|
Send the message to the python logger using the correct level.
|
|
492
506
|
Use stacklevel to show correctly the file and the line of the log, lvl 0 means the python
|
|
@@ -504,7 +518,7 @@ class Logger:
|
|
|
504
518
|
extra = self._get_extra_data(extra or {}, level)
|
|
505
519
|
self._log.log(level, msg, *args, extra=extra, stacklevel=stacklevel)
|
|
506
520
|
|
|
507
|
-
def _show_deprecated(self, _id: str, show_once: bool) -> bool:
|
|
521
|
+
def _show_deprecated(self, _id: str, *, show_once: bool) -> bool:
|
|
508
522
|
"""
|
|
509
523
|
If show_once is False this always return True, otherwise
|
|
510
524
|
checks if this _id is in the self._deprecated_hash set.
|
|
@@ -522,7 +536,7 @@ class Logger:
|
|
|
522
536
|
return True
|
|
523
537
|
|
|
524
538
|
## Public methods
|
|
525
|
-
def critical(self, msg: str, *args: tuple, extra: dict = None, stacklevel: int = None)-> None:
|
|
539
|
+
def critical(self, msg: str, *args: tuple, extra: dict | None = None, stacklevel: int | None = None) -> None:
|
|
526
540
|
"""
|
|
527
541
|
Log a message with severity CRITICAL on this logger.
|
|
528
542
|
Use stacklevel to show correctly the file and the line of the log, lvl 0 means the python
|
|
@@ -537,7 +551,7 @@ class Logger:
|
|
|
537
551
|
"""
|
|
538
552
|
self._send_to_log(logging.CRITICAL, msg, *args, extra=extra, stacklevel=stacklevel)
|
|
539
553
|
|
|
540
|
-
def debug(self, msg: str, *args: tuple, extra: dict = None, stacklevel: int = None)-> None:
|
|
554
|
+
def debug(self, msg: str, *args: tuple, extra: dict | None = None, stacklevel: int | None = None) -> None:
|
|
541
555
|
"""
|
|
542
556
|
Log a message with severity DEBUG on this logger.
|
|
543
557
|
Use stacklevel to show correctly the file and the line of the log, lvl 0 means the python
|
|
@@ -552,7 +566,9 @@ class Logger:
|
|
|
552
566
|
"""
|
|
553
567
|
self._send_to_log(logging.DEBUG, msg, *args, extra=extra, stacklevel=stacklevel)
|
|
554
568
|
|
|
555
|
-
def deprecated(
|
|
569
|
+
def deprecated( # noqa: D417
|
|
570
|
+
self, msg: str, *args, show_once: bool = True, extra: dict | None = None, stacklevel: int | None = None
|
|
571
|
+
) -> None:
|
|
556
572
|
"""
|
|
557
573
|
Shows a DeprecationWarning message with severity 'WARNING'.
|
|
558
574
|
If show_once is True, then the message will be showed only once.
|
|
@@ -568,7 +584,7 @@ class Logger:
|
|
|
568
584
|
msg = f'DeprecationWarning: {msg}'
|
|
569
585
|
self.warning(msg, *args, extra=extra, stacklevel=stacklevel)
|
|
570
586
|
|
|
571
|
-
def error(self, msg: str, *args: tuple, extra: dict = None, stacklevel: int = None)-> None:
|
|
587
|
+
def error(self, msg: str, *args: tuple, extra: dict | None = None, stacklevel: int | None = None) -> None:
|
|
572
588
|
"""
|
|
573
589
|
Log a message with severity ERROR on this logger.
|
|
574
590
|
Use stacklevel to show correctly the file and the line of the log, lvl 0 means the python
|
|
@@ -583,7 +599,7 @@ class Logger:
|
|
|
583
599
|
"""
|
|
584
600
|
self._send_to_log(logging.ERROR, msg, *args, extra=extra, stacklevel=stacklevel)
|
|
585
601
|
|
|
586
|
-
def exception(self, msg: str, *args: tuple, extra: dict = None, stacklevel: int = None)-> None:
|
|
602
|
+
def exception(self, msg: str, *args: tuple, extra: dict | None = None, stacklevel: int | None = None) -> None:
|
|
587
603
|
"""
|
|
588
604
|
Log a message with severity ERROR on this logger.
|
|
589
605
|
Use stacklevel to show correctly the file and the line of the log, lvl 0 means the python
|
|
@@ -598,7 +614,7 @@ class Logger:
|
|
|
598
614
|
"""
|
|
599
615
|
self.error(msg, *args, extra=extra, stacklevel=stacklevel)
|
|
600
616
|
|
|
601
|
-
def info(self, msg: str, *args: tuple, extra: dict = None, stacklevel: int = None)-> None:
|
|
617
|
+
def info(self, msg: str, *args: tuple, extra: dict | None = None, stacklevel: int | None = None) -> None:
|
|
602
618
|
"""
|
|
603
619
|
Log a message with severity INFO on this logger.
|
|
604
620
|
Use stacklevel to show correctly the file and the line of the log, lvl 0 means the python
|
|
@@ -618,14 +634,27 @@ class Logger:
|
|
|
618
634
|
Check if the slack message can be sent.
|
|
619
635
|
We keep a track of the last message sent to avoid sending too many messages in less than 3 seconds.
|
|
620
636
|
"""
|
|
621
|
-
|
|
622
|
-
if
|
|
637
|
+
# We only send the message if we are in PROD and not in unittest
|
|
638
|
+
if settings.PROFILE != 'PROD' or 'unittest' in sys.modules:
|
|
639
|
+
return False
|
|
640
|
+
|
|
641
|
+
# We check if the last message was sent in less than 3 seconds to avoid sending too many messages
|
|
642
|
+
now = DateTime.now(ZoneInfo('UTC')) # pylint: disable=no-member
|
|
643
|
+
if self._slack_timer and (now - self._slack_timer).seconds < 3: # noqa: PLR2004
|
|
623
644
|
return False
|
|
624
645
|
|
|
625
646
|
self._slack_timer = now
|
|
626
647
|
return True
|
|
627
648
|
|
|
628
|
-
def slack(
|
|
649
|
+
def slack(
|
|
650
|
+
self,
|
|
651
|
+
title: str,
|
|
652
|
+
message: str,
|
|
653
|
+
color: str,
|
|
654
|
+
url: str | None = None,
|
|
655
|
+
extra: dict | None = None,
|
|
656
|
+
stacklevel: int | None = None,
|
|
657
|
+
) -> None:
|
|
629
658
|
"""
|
|
630
659
|
Send a message to a Slack channel using Slack WebHooks.
|
|
631
660
|
https://api.slack.com/messaging/webhooks
|
|
@@ -646,14 +675,13 @@ class Logger:
|
|
|
646
675
|
url = settings.SLACK_URL
|
|
647
676
|
|
|
648
677
|
# We send the message only if url is set and we are in PROD and not in unittest
|
|
649
|
-
if url and
|
|
650
|
-
#
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
Thread(target=client.send).start()
|
|
678
|
+
if url and self._can_send_slack():
|
|
679
|
+
# The import must be here to avoid circular import inside http module
|
|
680
|
+
from everysk.core.slack import Slack # noqa: PLC0415
|
|
681
|
+
|
|
682
|
+
client = Slack(title=title, message=message, color=color, url=url)
|
|
683
|
+
# This will send the message to Slack without block the request
|
|
684
|
+
Thread(target=client.send).start()
|
|
657
685
|
|
|
658
686
|
log_message = f'Slack message: {title} -> {message}'
|
|
659
687
|
if color == 'danger':
|
|
@@ -665,7 +693,7 @@ class Logger:
|
|
|
665
693
|
elif color == 'warning':
|
|
666
694
|
self.warning(log_message, extra=extra, stacklevel=stacklevel)
|
|
667
695
|
|
|
668
|
-
def warning(self, msg: str, *args: tuple, extra: dict = None, stacklevel: int = None)-> None:
|
|
696
|
+
def warning(self, msg: str, *args: tuple, extra: dict | None = None, stacklevel: int | None = None) -> None:
|
|
669
697
|
"""
|
|
670
698
|
Log a message with severity WARNING on this logger.
|
|
671
699
|
Use stacklevel to show correctly the file and the line of the log, lvl 0 means the python
|
|
@@ -116,12 +116,13 @@ except ModuleNotFoundError as error:
|
|
|
116
116
|
from everysk.core._tests.log import (
|
|
117
117
|
LoggerExtraDataTestCase as EveryskLibLoggerExtraDataTestCase,
|
|
118
118
|
LoggerFormatterTestCase as EveryskLibLoggerFormatterTestCase,
|
|
119
|
+
LoggerJsonTestCase as EveryskLibLoggerJsonTestCase,
|
|
119
120
|
LoggerManagerTestCase as EveryskLibLoggerManagerTestCase,
|
|
120
121
|
LoggerMethodsTestCase as EveryskLibLoggerMethodsTestCase,
|
|
122
|
+
LoggerStackLevelTestCase as EveryskLibLoggerStackLevelTestCase,
|
|
123
|
+
LoggerStdoutTestCase as EveryskLibLoggerStdoutTestCase,
|
|
121
124
|
LoggerTestCase as EveryskLibLoggerTestCase,
|
|
122
125
|
LoggerTraceTestCase as EveryskLibLogTraceTestCase,
|
|
123
|
-
LoggerStdoutTestCase as EveryskLibLoggerStdoutTestCase,
|
|
124
|
-
LoggerJsonTestCase as EveryskLibLoggerJsonTestCase,
|
|
125
126
|
)
|
|
126
127
|
try:
|
|
127
128
|
from everysk.core._tests.log import LoggerSlackTestCase as EveryskLibLoggerSlackTestCase # We need requests to run this test
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
###############################################################################
|