volcengine-python-sdk-aicc 0.0.41__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.
Files changed (60) hide show
  1. bytedance/__init__.py +4 -0
  2. bytedance/jeddak_secure_channel/__init__.py +28 -0
  3. bytedance/jeddak_secure_channel/client.py +305 -0
  4. bytedance/jeddak_secure_channel/config.py +171 -0
  5. bytedance/jeddak_secure_channel/consts.py +5 -0
  6. bytedance/jeddak_secure_channel/crypto/__init__.py +14 -0
  7. bytedance/jeddak_secure_channel/crypto/aes.py +174 -0
  8. bytedance/jeddak_secure_channel/crypto/envelope.py +395 -0
  9. bytedance/jeddak_secure_channel/crypto/rsa.py +164 -0
  10. bytedance/jeddak_secure_channel/error.py +131 -0
  11. bytedance/jeddak_secure_channel/log/__init__.py +23 -0
  12. bytedance/jeddak_secure_channel/log/config.py +31 -0
  13. bytedance/jeddak_secure_channel/log/logger.py +88 -0
  14. bytedance/jeddak_secure_channel/logger_factory.py +134 -0
  15. bytedance/jeddak_secure_channel/ra.py +772 -0
  16. bytedance/jeddak_secure_channel/server.py +745 -0
  17. bytedance/jeddak_secure_channel/service/__init__.py +16 -0
  18. bytedance/jeddak_secure_channel/service/eps.py +133 -0
  19. bytedance/jeddak_secure_channel/service/eps_models.py +47 -0
  20. bytedance/jeddak_secure_channel/service/ras.py +88 -0
  21. bytedance/jeddak_secure_channel/service/tks.py +51 -0
  22. bytedance/jeddak_secure_channel/service/tks_models.py +334 -0
  23. bytedance/jeddak_secure_channel/utils.py +469 -0
  24. bytedance/jeddak_secure_model/model_automation_tool.py +181 -0
  25. bytedance/jeddak_secure_model/model_encryption.py +416 -0
  26. bytedance/jeddak_secure_model/model_hub_admin.py +116 -0
  27. bytedance/jeddak_secure_model/pcc_utils.py +279 -0
  28. bytedance/jeddak_secure_model/rds_utils.py +63 -0
  29. bytedance/tks/attester.py +41 -0
  30. bytedance/tks/client.py +629 -0
  31. bytedance/tks/consts.py +122 -0
  32. bytedance/tks/crypto/aes.py +47 -0
  33. bytedance/tks/crypto/ec.py +39 -0
  34. bytedance/tks/exceptions.py +22 -0
  35. bytedance/volcengine_aicc_sdk/__init__.py +14 -0
  36. bytedance/volcengine_aicc_sdk/aicc_client.py +277 -0
  37. bytedance/volcengine_aicc_sdk/aicc_inference.py +64 -0
  38. bytedance/volcengine_aicc_sdk/ark_inference_client.py +343 -0
  39. bytedance/volcengine_aicc_sdk/asr_inference_client.py +158 -0
  40. bytedance/volcengine_aicc_sdk/asr_websocket_client.py +705 -0
  41. bytedance/volcengine_aicc_sdk/auc_inference_client.py +305 -0
  42. bytedance/volcengine_aicc_sdk/common/__init__.py +0 -0
  43. bytedance/volcengine_aicc_sdk/common/aes.py +136 -0
  44. bytedance/volcengine_aicc_sdk/common/constant.py +4 -0
  45. bytedance/volcengine_aicc_sdk/common/error.py +21 -0
  46. bytedance/volcengine_aicc_sdk/common/log/__init__.py +24 -0
  47. bytedance/volcengine_aicc_sdk/common/log/config.py +31 -0
  48. bytedance/volcengine_aicc_sdk/common/log/logger.py +88 -0
  49. bytedance/volcengine_aicc_sdk/common/tos_utils.py +341 -0
  50. bytedance/volcengine_aicc_sdk/common/utils.py +45 -0
  51. bytedance/volcengine_aicc_sdk/cu_inference_client.py +292 -0
  52. bytedance/volcengine_aicc_sdk/deepseek_inference_client.py +501 -0
  53. bytedance/volcengine_aicc_sdk/secure_http_client.py +406 -0
  54. bytedance/volcengine_aicc_sdk/seedance_inference_client.py +500 -0
  55. bytedance/volcengine_aicc_sdk/seedream_inference_client.py +213 -0
  56. volcengine_python_sdk_aicc-0.0.41.dist-info/METADATA +79 -0
  57. volcengine_python_sdk_aicc-0.0.41.dist-info/RECORD +60 -0
  58. volcengine_python_sdk_aicc-0.0.41.dist-info/WHEEL +5 -0
  59. volcengine_python_sdk_aicc-0.0.41.dist-info/licenses/LICENSE +202 -0
  60. volcengine_python_sdk_aicc-0.0.41.dist-info/top_level.txt +1 -0
@@ -0,0 +1,305 @@
1
+ import uuid
2
+ """
3
+ seedance机密推理客户端
4
+ """
5
+ __all__ = ["AUCClient"]
6
+
7
+ import os
8
+ import base64
9
+ import requests
10
+ import traceback
11
+ from urllib.parse import unquote
12
+ from cryptography.hazmat.primitives import serialization as crypto_serialization
13
+ from cryptography.hazmat.primitives.asymmetric import rsa
14
+ from cryptography.hazmat.backends import default_backend as crypto_default_backend
15
+ from bytedance.jeddak_secure_channel.crypto import PrivateKey
16
+ from .secure_http_client import SecureHttpClient
17
+ from .common.tos_utils import TOSUtils
18
+ from .common.error import AICCException
19
+ from .common.log import logger
20
+ from .secure_http_client import AICCConfig
21
+ from .common.aes import decrypt_file
22
+
23
+
24
+ class AUCClient:
25
+ """
26
+ AUC机密推理客户端.
27
+ """
28
+
29
+ def __init__(self,
30
+ base_url: str = "",
31
+ api_key: str = "",
32
+ endpoint: str = "",
33
+ aicc_config: AICCConfig = None,
34
+ **kwargs):
35
+ self.base_url = base_url
36
+ self.api_key = api_key
37
+ self.endpoint = endpoint
38
+ self.aicc_config = aicc_config
39
+ self.tos_utils = TOSUtils()
40
+ self.kwargs = kwargs
41
+ self.is_secure = self.kwargs.get("is_secure", True)
42
+
43
+ if aicc_config:
44
+ self.secure_http_client = SecureHttpClient(aicc_config=aicc_config)
45
+ else:
46
+ if self.is_secure:
47
+ aicc_config = AICCConfig(ra_url=base_url, auth_token=api_key)
48
+ self.secure_http_client = SecureHttpClient(aicc_config=aicc_config)
49
+ else:
50
+ self.secure_http_client = SecureHttpClient()
51
+
52
+ # 是否开启AUC文件加密, 默认加密
53
+ self.is_enable_auc_encrypt = str(self.kwargs.get("is_enable_auc_encrypt", True)).lower() == "true"
54
+
55
+ # AUC文件的存储位置, 目前仅支持火山TOS
56
+ self.auc_file_storage_location = self.kwargs.get("auc_file_storage_location", "TOS").upper()
57
+
58
+ self.auc_file_encrypt_pub_key_content = None
59
+ self.auc_file_encrypt_priv_key_content = None
60
+
61
+ self.auc_file_encrypt_priv_key: PrivateKey = None
62
+
63
+ self.timeout = self.kwargs.get("timeout", 120.0)
64
+
65
+ # API Endpoints
66
+ self.CREATE_TASK_URL = "/auc/lark/submit"
67
+ self.QUERY_TASK_URL = "/auc/lark/query"
68
+
69
+ def _build_common_headers(self, request_id: str) -> dict[str, str]:
70
+ if not request_id:
71
+ request_id = str(uuid.uuid4())
72
+
73
+ return {
74
+ "Content-Type": "application/json",
75
+ "X-Api-App-Key": self.endpoint,
76
+ "Authorization": f"Bearer {self.api_key}",
77
+ "X-Request-Id": request_id,
78
+ }
79
+
80
+ def _get_base_url(self):
81
+ """
82
+ Get base URL with trailing slash removed
83
+ """
84
+ return self.base_url.rstrip("/")
85
+
86
+ def generate_key_pair(self, pub_key_path: str = "", priv_key_path: str = ""):
87
+ """
88
+ Generate RSA 4096 key pair and save to files
89
+
90
+ Args:
91
+ pub_key_path: Path to save public key
92
+ priv_key_path: Path to save private key
93
+
94
+ Returns:
95
+ Tuple of (public_key_path, private_key_path)
96
+ """
97
+ try:
98
+ # Generate RSA key pair
99
+ key = rsa.generate_private_key(
100
+ backend=crypto_default_backend(),
101
+ public_exponent=65537,
102
+ key_size=4096
103
+ )
104
+
105
+ # Save private key
106
+ private_key = key.private_bytes(
107
+ crypto_serialization.Encoding.PEM,
108
+ crypto_serialization.PrivateFormat.PKCS8,
109
+ crypto_serialization.NoEncryption()
110
+ )
111
+ with open(priv_key_path, 'wb') as f:
112
+ f.write(private_key)
113
+
114
+ # Save public key
115
+ public_key = key.public_key().public_bytes(
116
+ crypto_serialization.Encoding.PEM,
117
+ crypto_serialization.PublicFormat.SubjectPublicKeyInfo
118
+ )
119
+ with open(pub_key_path, 'wb') as f:
120
+ f.write(public_key)
121
+ except Exception as e:
122
+ logger.error(f"[Error] Failed to generate key pair: {e}")
123
+ raise AICCException("Failed to generate RSA key pair") from e
124
+
125
+ def set_file_encrypt_key(self, pub_key_path: str = "", priv_key_path: str = ""):
126
+ """
127
+ Set auc file encrypt key
128
+ Args:
129
+ pub_key_path: Public key for encrypt
130
+ priv_key_path: Private key for decrypt
131
+ """
132
+ if os.path.exists(pub_key_path):
133
+ with open(pub_key_path, 'r') as f:
134
+ content = f.read()
135
+ if '-----BEGIN' in content and '-----END' in content:
136
+ self.auc_file_encrypt_pub_key_content = content
137
+
138
+ if os.path.exists(priv_key_path):
139
+ with open(priv_key_path, 'r') as f:
140
+ content = f.read()
141
+ if '-----BEGIN' in content and '-----END' in content:
142
+ self.auc_file_encrypt_priv_key_content = content
143
+ self.auc_file_encrypt_priv_key = \
144
+ PrivateKey.from_private_pem(self.auc_file_encrypt_priv_key_content)
145
+
146
+ def create_task(self, data: dict, custom_header: dict = None, request_id: str = "") -> str:
147
+ """
148
+ :param data:
149
+ :param custom_header:
150
+ :return:
151
+ """
152
+ if not data:
153
+ raise AICCException("data must be provided")
154
+
155
+ if "model" not in data:
156
+ data["model"] = self.endpoint
157
+
158
+ base_url = self._get_base_url()
159
+ url = f"{base_url}{self.CREATE_TASK_URL}"
160
+
161
+ headers = self._build_common_headers(request_id)
162
+ if custom_header:
163
+ headers.update(custom_header)
164
+
165
+ # Add encryption headers if provided
166
+ if self.is_enable_auc_encrypt:
167
+ if not self.auc_file_encrypt_pub_key_content or not self.auc_file_encrypt_priv_key_content:
168
+ raise AICCException("pub_key and priv_key must be provided when Is-Enable-Encrypt is 'true'")
169
+
170
+ headers["Enable-TOS-Content-Result-Encryption"] = str(self.is_enable_auc_encrypt).lower()
171
+ headers["X-Encryption-Algorithm"] = "RSA_OAEP_4096_AES_256"
172
+ if self.auc_file_encrypt_pub_key_content:
173
+ # Base64 encode the PEM public key for safe HTTP header transmission
174
+ # This avoids issues with newlines and special characters in headers
175
+ pk_base64 = base64.b64encode(self.auc_file_encrypt_pub_key_content.encode()).decode()
176
+ headers["PK"] = pk_base64
177
+
178
+ # logger.info(f"[Request] url: {url}, headers: {headers}, data: {data}")
179
+
180
+ response = self.secure_http_client.post(url, headers=headers, json=data, timeout=self.timeout)
181
+ if response.status_code == 200:
182
+ result = response.json()
183
+ task_id = result.get("Data", {}).get("TaskID", "")
184
+ return task_id
185
+ else:
186
+ logger.error(f"[Error] url: {url}, response: {response.text}")
187
+ return ""
188
+
189
+ def query_task(self, task_id: str, request_id: str = "") -> dict:
190
+ """
191
+ Query auc task
192
+ API: POST /auc/lark/query
193
+ """
194
+
195
+ if not task_id:
196
+ raise AICCException("task_id must be provided")
197
+
198
+ base_url = self._get_base_url()
199
+ url = f"{base_url}{self.QUERY_TASK_URL}"
200
+
201
+ headers = self._build_common_headers(request_id)
202
+ payload = {"TaskID": task_id}
203
+
204
+ response = self.secure_http_client.post(url, headers=headers, json=payload, timeout=self.timeout)
205
+ if response.status_code == 200:
206
+ result = response.json()
207
+ return result
208
+ else:
209
+ try:
210
+ result = response.json()
211
+ return result
212
+ except Exception as e:
213
+ logger.error(f"[Error] url: {url}, Exception={e}")
214
+ raise AICCException("Failed to query auc task")
215
+
216
+ def download_file(self, task_id: str, auc_file_path: str) -> bool:
217
+ """
218
+ Download and decrypt encrypted auc
219
+ This function polls the task status until it succeeds, then downloads
220
+ and decrypts the auc using the provided private key.
221
+ Args:
222
+ task_id: AUC generation task ID
223
+ auc_file_path: Directory to save decrypted auc
224
+
225
+ Returns:
226
+ True if download succeeded, False otherwise
227
+ """
228
+ if not task_id:
229
+ raise AICCException("task_id must be provided")
230
+
231
+ if not auc_file_path:
232
+ raise AICCException("auc_file_path must be provided")
233
+
234
+ task_info = self.query_task(task_id)
235
+ status = task_info.get("Data", {}).get("Status", "")
236
+ if not status:
237
+ logger.error(f"task status is null, task_id={task_id}, query_task return {task_info}")
238
+ raise AICCException("task status is null")
239
+ if status != "success":
240
+ logger.info(f"task status is not success, task_id={task_id}, status={status}")
241
+ return False
242
+
243
+ result = task_info.get("Data", {}).get("Result") or {}
244
+ for label, key in (
245
+ ("AudioTranscriptionFile", "AudioTranscriptionFile"),
246
+ ("SummarizationFile", "SummarizationFile"),
247
+ ("ChapterFile", "ChapterFile"),
248
+ ("InformationExtractionFile", "InformationExtractionFile"),
249
+ ("TranslationFile", "TranslationFile"),
250
+ ):
251
+ file_url = result.get(key, "")
252
+ if file_url:
253
+ local_file_path = f"./{auc_file_path}_{label}.json"
254
+ self.download_file_by_url(file_url, local_file_path)
255
+
256
+ def download_file_by_url(self, file_url: str, local_file_path: str) -> bool:
257
+ """
258
+ Download auc file
259
+ Args:
260
+ file_url: File file_url
261
+ auc_file_path: Directory to save auc file
262
+
263
+ Returns:
264
+ Path to auc file, or None if failed
265
+ """
266
+ if not file_url:
267
+ raise AICCException("file_url must be provided")
268
+
269
+ if not local_file_path:
270
+ raise AICCException("local_file_path must be provided")
271
+
272
+ if os.path.exists(local_file_path) and os.path.getsize(local_file_path) > 0:
273
+ logger.error(f"[Error] File {local_file_path} already exists and is not empty")
274
+ return False
275
+
276
+ try:
277
+ response = requests.get(file_url, stream=True)
278
+ with open(local_file_path, "wb") as f:
279
+ for chunk in response.iter_content(chunk_size=8192):
280
+ f.write(chunk)
281
+
282
+ if self.is_enable_auc_encrypt:
283
+ if not self.auc_file_encrypt_priv_key:
284
+ logger.error("[Error] No private key set, cannot decrypt encrypted auc")
285
+ return False
286
+
287
+ headers = response.headers
288
+ encrypted_key = headers.get("x-tos-meta-enc-dek")
289
+ if encrypted_key:
290
+ try:
291
+ encrypted_key_bytes = base64.b64decode(unquote(encrypted_key))
292
+ encrypted_key = self.auc_file_encrypt_priv_key.decrypt(encrypted_key_bytes)
293
+ tmp_file_path = local_file_path + ".tmp"
294
+ decrypt_file(encrypted_key, local_file_path, tmp_file_path, 'b')
295
+ os.remove(local_file_path)
296
+ os.rename(tmp_file_path, local_file_path)
297
+ except Exception as e:
298
+ logger.error(f"[Error] Failed to decode encrypted_key: {e}")
299
+ traceback.print_exc()
300
+ return False
301
+ except Exception as e:
302
+ logger.error(f"[Error] Download failed: {e}")
303
+ traceback.print_exc()
304
+ return False
305
+ return True
File without changes
@@ -0,0 +1,136 @@
1
+ __all__ = [
2
+ "gcm_encrypt",
3
+ "gcm_decrypt",
4
+ "aes_encrypt",
5
+ "aes_decrypt",
6
+ "encrypt_file",
7
+ "decrypt_file"
8
+ ]
9
+
10
+ import base64
11
+ from typing_extensions import IO, Union
12
+
13
+ import secrets
14
+
15
+ from cryptography.hazmat.backends import default_backend
16
+ from cryptography.hazmat.primitives import hashes
17
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
18
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
19
+
20
+
21
+ SALT = "ai-handler-salt"
22
+
23
+
24
+ def gcm_decrypt(key: bytes, ciphertext: bytes) -> bytes:
25
+ iv, ciphertext = ciphertext[:12], ciphertext[12:]
26
+ ciphertext, tag = ciphertext[:-16], ciphertext[-16:]
27
+ decryptor = Cipher(
28
+ algorithms.AES(key),
29
+ modes.GCM(iv, tag),
30
+ ).decryptor()
31
+
32
+ return decryptor.update(ciphertext) + decryptor.finalize()
33
+
34
+
35
+ def gcm_encrypt(key: bytes, plaintext: bytes) -> bytes:
36
+ iv = secrets.token_bytes(12)
37
+ encryptor = Cipher(
38
+ algorithms.AES(key),
39
+ modes.GCM(iv),
40
+ ).encryptor()
41
+
42
+ ciphertext = encryptor.update(plaintext) + encryptor.finalize()
43
+ return iv + ciphertext + encryptor.tag
44
+
45
+
46
+ def generate_key_from_string(seed_string: str) -> bytes:
47
+ backend = default_backend()
48
+ # 将字符串种子转换为字节
49
+ password = seed_string.encode()
50
+ salt = SALT.encode()
51
+ kdf = PBKDF2HMAC(
52
+ algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=backend
53
+ )
54
+ key = kdf.derive(password)
55
+ return key
56
+
57
+
58
+ def aes_encrypt(aes_key: Union[bytes, str], data: Union[bytes, str]) -> bytes:
59
+ if isinstance(aes_key, str):
60
+ aes_key = aes_key.encode()
61
+ if isinstance(data, str):
62
+ data = data.encode()
63
+ return gcm_encrypt(aes_key, data)
64
+
65
+
66
+ def aes_decrypt(aes_key: Union[bytes, str], data: Union[bytes, str]) -> bytes:
67
+ if isinstance(aes_key, str):
68
+ aes_key = aes_key.encode()
69
+ if isinstance(data, str):
70
+ data = data.encode()
71
+ return gcm_decrypt(aes_key, data)
72
+
73
+
74
+ def encrypt_file(
75
+ aes_key: Union[bytes, str],
76
+ source_path: str,
77
+ dest_path: str,
78
+ mode: str = "b",
79
+ ) -> bool:
80
+ if mode not in ["b", "t"]:
81
+ raise Exception("mode argument error")
82
+
83
+ if isinstance(aes_key, str):
84
+ aes_key = aes_key.encode()
85
+
86
+ with open(source_path, f"r{mode}") as source, open(
87
+ dest_path, f"w{mode}"
88
+ ) as dest:
89
+ if mode == "b":
90
+ source_b: IO[bytes] = source
91
+ dest_b: IO[bytes] = dest
92
+ plaintext = source_b.read()
93
+ ciphertext = aes_encrypt(aes_key, plaintext)
94
+ dest_b.write(ciphertext)
95
+ else:
96
+ source_t: IO[str] = source
97
+ dest_t: IO[str] = dest
98
+ for plaintext in source_t:
99
+ plaintext = plaintext.rstrip("\n")
100
+ ciphertext = aes_encrypt(aes_key, plaintext)
101
+ dest_t.write(base64.b64encode(ciphertext).decode() + "\n")
102
+
103
+ return True
104
+
105
+
106
+ def decrypt_file(
107
+ aes_key: Union[bytes, str],
108
+ source_path: str,
109
+ dest_path: str,
110
+ mode: str = "b",
111
+ ) -> bool:
112
+ if mode not in ["b", "t"]:
113
+ raise Exception("mode argument error")
114
+
115
+ if isinstance(aes_key, str):
116
+ aes_key = aes_key.encode()
117
+
118
+ with open(source_path, f"r{mode}") as source, open(
119
+ dest_path, f"w{mode}"
120
+ ) as dest:
121
+ if mode == "b":
122
+ source_b: IO[bytes] = source
123
+ dest_b: IO[bytes] = dest
124
+ ciphertext = source_b.read()
125
+ plaintext = aes_decrypt(aes_key, ciphertext)
126
+ dest_b.write(plaintext)
127
+ else:
128
+ source_t: IO[str] = source
129
+ dest_t: IO[str] = dest
130
+ for ciphertext in source_t:
131
+ ciphertext = ciphertext.rstrip("\n")
132
+ ciphertext = base64.b64decode(ciphertext.encode())
133
+ plaintext = aes_decrypt(aes_key, ciphertext)
134
+ dest_t.write(plaintext.decode() + "\n")
135
+ # dest_t.write(base64.b64encode(plaintext).decode() + "\n")
136
+ return True
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class Constants:
4
+ LOG_CONFIG = "{\"dir\": \"aicc_log\"}"
@@ -0,0 +1,21 @@
1
+ """
2
+ 安全通信错误类型.
3
+ """
4
+
5
+ __all__ = [
6
+ "AICCException"
7
+ ]
8
+
9
+
10
+ class AICCException(Exception):
11
+ """AICC 异常"""
12
+
13
+ def __init__(self, msg):
14
+ Exception.__init__(self)
15
+ self._msg = msg
16
+
17
+ def get_msg(self):
18
+ return self._msg
19
+
20
+ def __str__(self):
21
+ return self.get_msg()
@@ -0,0 +1,24 @@
1
+ __all__ = [
2
+ "DEBUG",
3
+ "INFO",
4
+ "WARNING",
5
+ "ERROR",
6
+ "CRITICAL",
7
+ "FORMAT",
8
+ "LogConfig",
9
+ "debug",
10
+ "info",
11
+ "warning",
12
+ "error",
13
+ "critical",
14
+ "init_log_config"
15
+ ]
16
+
17
+ from .config import DEBUG, INFO, WARNING, ERROR, CRITICAL, FORMAT, LogConfig
18
+ from .logger import init_log_config, info, debug, warning, error, critical
19
+
20
+ _initialized = False
21
+ if not _initialized:
22
+ config = LogConfig()
23
+ init_log_config(config)
24
+ _initialized = True
@@ -0,0 +1,31 @@
1
+ __all__ = [
2
+ "DEBUG",
3
+ "INFO",
4
+ "WARNING",
5
+ "ERROR",
6
+ "CRITICAL",
7
+ "FORMAT",
8
+ "LogConfig"
9
+ ]
10
+
11
+ # Log level constants
12
+ DEBUG = "DEBUG"
13
+ INFO = "INFO"
14
+ WARNING = "WARNING"
15
+ ERROR = "ERROR"
16
+ CRITICAL = "CRITICAL"
17
+ # Log output format
18
+ FORMAT = "{time:YYYY-MM-DD HH:mm:ss} | {level} | {file.path}:{line} | {message}"
19
+
20
+
21
+ class LogConfig:
22
+ """Log configuration class."""
23
+
24
+ def __init__(self):
25
+ self.rotation = 100 # Size limit for log files in MB
26
+ self.retention = 7 # Days to retain log files
27
+ self.compression = "zip"
28
+ self.dir = "aicc_log"
29
+ self.filename = "aicc.log"
30
+ self.format = FORMAT
31
+ self.level = INFO
@@ -0,0 +1,88 @@
1
+ __all__ = [
2
+ "debug",
3
+ "info",
4
+ "warning",
5
+ "error",
6
+ "critical",
7
+ "init_log_config"
8
+ ]
9
+
10
+ import os.path
11
+ import sys
12
+ from typing import Any
13
+
14
+ from loguru import logger
15
+
16
+ from . import LogConfig, FORMAT
17
+
18
+
19
+ def init_log_config(*configs: LogConfig):
20
+ """Initialize logging configuration with given configs."""
21
+ logger.remove()
22
+
23
+ log_level = "INFO"
24
+ if os.environ.get("log_level"):
25
+ log_level = os.environ.get("log_level")
26
+ if os.environ.get("LOG_LEVEL"):
27
+ log_level = os.environ.get("LOG_LEVEL")
28
+
29
+ logger.add(sink=sys.stdout, format=FORMAT, level=log_level)
30
+
31
+ for config in configs:
32
+ if not config.dir:
33
+ config.dir = os.environ.get("LOG_DIR", "aicc_log")
34
+
35
+ if not config.filename:
36
+ config.filename = "aicc.log"
37
+
38
+ os.makedirs(config.dir, exist_ok=True)
39
+ fullpath = os.path.join(config.dir, config.filename)
40
+
41
+ if config.rotation <= 0:
42
+ config.rotation = 100
43
+ rotation = f"{config.rotation} MB"
44
+
45
+ if config.retention <= 0:
46
+ config.retention = 7
47
+ retention = f"{config.retention} days"
48
+
49
+ logger.add(
50
+ sink=fullpath,
51
+ rotation=rotation,
52
+ retention=retention,
53
+ compression=config.compression,
54
+ format=config.format,
55
+ level=config.level
56
+ )
57
+
58
+
59
+ def msg_proc(message: str) -> str:
60
+ if message:
61
+ return f"[aicc]:{message}"
62
+ else:
63
+ return message
64
+
65
+
66
+ def debug(message: str, *args: Any, **kwargs: Any) -> None:
67
+ """Log debug message."""
68
+ logger.opt(depth=1).debug(msg_proc(message), *args, **kwargs)
69
+
70
+
71
+ def info(message: str, *args: Any, **kwargs: Any) -> None:
72
+ """Log info message."""
73
+ logger.opt(depth=1).info(msg_proc(message), *args, **kwargs)
74
+
75
+
76
+ def warning(message: str, *args: Any, **kwargs: Any) -> None:
77
+ """Log warning message."""
78
+ logger.opt(depth=1).warning(msg_proc(message), *args, **kwargs)
79
+
80
+
81
+ def error(message: str, *args: Any, **kwargs: Any) -> None:
82
+ """Log error message."""
83
+ logger.opt(depth=1).error(msg_proc(message), *args, **kwargs)
84
+
85
+
86
+ def critical(message: str, *args: Any, **kwargs: Any) -> None:
87
+ """Log critical message."""
88
+ logger.opt(depth=1).critical(msg_proc(message), *args, **kwargs)