pytbox 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pytbox might be problematic. Click here for more details.

@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from enum import Enum
4
+ from typing import Optional
5
+
6
+ import httpx
7
+
8
+
9
+ class RequestTimeoutError(Exception):
10
+ code = "notionhq_client_request_timeout"
11
+
12
+ def __init__(self, message: str="Request to Notion API has time out") -> None:
13
+ super().__init__(message)
14
+
15
+
16
+ class HTTPResponseError(Exception):
17
+ def __init__(self, response: httpx.Response, message: Optional[str]=None) -> None:
18
+ if message is None:
19
+ message = (
20
+ f'Request to Notion API failed with status: {response.status_code}'
21
+ )
22
+ super().__init__(message)
23
+
24
+ self.status = response.status_code
25
+ self.headers = response.headers
26
+ self.body = response.text
27
+
28
+
29
+ class APIErrorCode(str, Enum):
30
+ Unauthorized = "unauthorized"
31
+
32
+
33
+ class APIResponseError(HTTPResponseError):
34
+
35
+ code: APIErrorCode
36
+
37
+ def __init__(self, response: httpx.Response, message: str, code: APIErrorCode) -> None:
38
+ super().__init__(response, message)
39
+ self.code = code
40
+
41
+
42
+ def is_api_error_code(code: str) -> bool:
43
+ if isinstance(code, str):
44
+ return code in (error_code.value for error_code in APIErrorCode)
45
+ return False
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import Dict, Any
4
+
5
+
6
+ def pick(base: Dict[Any, Any], *keys: str) -> Dict[Any, Any]:
7
+ return {key: base[key] for key in keys if key in base and base[key] is not None}
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from typing import Awaitable, TypeVar, Union
5
+
6
+ T = TypeVar("T")
7
+ SyncAsync = Union[T, Awaitable[T]]
pytbox/logger.py ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ from loguru import logger
5
+ from .victorialog import Victorialog
6
+
7
+
8
+ logger.remove()
9
+ logger.add(sys.stdout, colorize=True, format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <level>{message}</level>")
10
+
11
+
12
+ class AppLogger:
13
+ """
14
+ 应用日志记录器类
15
+
16
+ 提供统一的日志记录接口,支持多种日志级别和外部服务集成。
17
+ 自动记录调用者信息(文件名、行号、函数名)到日志中。
18
+ """
19
+ def __init__(self,
20
+ app_name: str='inbox',
21
+ stream: str='automation',
22
+ enable_victorialog: bool=False,
23
+ victorialog_url: str=None,
24
+ enable_sls: bool=False,
25
+ sls_url: str=None,
26
+ sls_access_key_id: str=None,
27
+ sls_access_key_secret: str=None,
28
+ sls_project: str=None,
29
+ sls_logstore: str=None,
30
+ sls_topic: str=None,
31
+ ):
32
+ """
33
+ 初始化应用日志记录器
34
+
35
+ Args:
36
+ app_name: 应用名称,用于标识日志来源
37
+ stream: 日志流名称,用于VictoriaLogs分类
38
+ """
39
+ self.app_name = app_name
40
+ self.stream = stream
41
+ self.victorialog = Victorialog(url=victorialog_url)
42
+ self.enable_victorialog = enable_victorialog
43
+
44
+ def _get_caller_info(self) -> tuple[str, int, str]:
45
+ """
46
+ 获取调用者信息
47
+
48
+ Returns:
49
+ tuple: (文件名, 行号, 函数名)
50
+ """
51
+ import inspect
52
+ stack = inspect.stack()
53
+ caller = stack[2] # 索引0是当前函数,索引1是_get_caller_info,索引2是实际调用者
54
+
55
+ # 获取调用者的文件名、行号、函数名
56
+ call_full_filename = caller.filename
57
+ caller_filename = caller.filename.split('/')[-1]
58
+ caller_lineno = caller.lineno
59
+ caller_function = caller.function
60
+
61
+ return caller_filename, caller_lineno, caller_function, call_full_filename
62
+
63
+ def debug(self, message: str):
64
+ """记录调试级别日志"""
65
+ caller_filename, caller_lineno, caller_function, call_full_filename = self._get_caller_info()
66
+ logger.debug(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
67
+ if self.enable_victorialog:
68
+ self.victorialog.send_program_log(stream=self.stream, level="DEBUG", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
69
+
70
+ def info(self, message: str='', feishu_notify: bool=False):
71
+ """记录信息级别日志"""
72
+ caller_filename, caller_lineno, caller_function, call_full_filename = self._get_caller_info()
73
+ logger.info(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
74
+ if self.enable_victorialog:
75
+ r = self.victorialog.send_program_log(stream=self.stream, level="INFO", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
76
+ print(r)
77
+ if feishu_notify:
78
+ self.feishu(message)
79
+
80
+ def warning(self, message: str):
81
+ """记录警告级别日志"""
82
+ caller_filename, caller_lineno, caller_function, call_full_filename = self._get_caller_info()
83
+ logger.warning(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
84
+ if self.enable_victorialog:
85
+ self.victorialog.send_program_log(stream=self.stream, level="WARN", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
86
+
87
+ def error(self, message: str):
88
+ """记录错误级别日志"""
89
+ caller_filename, caller_lineno, caller_function, call_full_filename = self._get_caller_info()
90
+ logger.error(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
91
+ if self.enable_victorialog:
92
+ self.victorialog.send_program_log(stream=self.stream, level="ERROR", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
93
+ from src.library.monitor.insert_program import insert_error_message
94
+ # 传递清理后的消息给监控系统
95
+ message.replace('#', '')
96
+ insert_error_message(message, self.app_name, caller_filename, caller_lineno, caller_function)
97
+
98
+ def critical(self, message: str):
99
+ """记录严重错误级别日志"""
100
+ caller_filename, caller_lineno, caller_function, call_full_filename = self._get_caller_info()
101
+ logger.critical(f"[{caller_filename}:{caller_lineno}:{caller_function}] {message}")
102
+ if self.enable_victorialog:
103
+ self.victorialog.send_program_log(stream=self.stream, level="CRITICAL", message=message, app_name=self.app_name, file_name=call_full_filename, line_number=caller_lineno, function_name=caller_function)
104
+
105
+
106
+ def get_logger(app_name: str, enable_) -> AppLogger:
107
+ """
108
+ 获取应用日志记录器实例
109
+
110
+ Args:
111
+ app_name: 应用名称
112
+ log_level: 日志级别
113
+ enable_influx: 是否启用InfluxDB记录
114
+
115
+ Returns:
116
+ AppLogger: 日志记录器实例
117
+ """
118
+ return AppLogger(app_name)
119
+
120
+
121
+ # 使用示例
122
+ if __name__ == "__main__":
123
+ log = get_logger(app_name='test')
124
+ log.info("That's it, beautiful and simple logging!")
125
+ log.warning("That's it, beautiful and simple logging!")
126
+ log.error("That's it, beautiful and simple logging!11")
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import List
4
+ from onepasswordconnectsdk.client import new_client_from_environment
5
+ from onepasswordconnectsdk.models import (Item, Field)
6
+
7
+
8
+ class OnePasswordConnect:
9
+ '''tbd'''
10
+ def __init__(self, vault_id):
11
+ self.client = new_client_from_environment()
12
+ self.vault_id = vault_id
13
+
14
+ def create_item(self, name, username: str=None, password: str=None, notes: str="create by automation", tags: List=None):
15
+ # Create an item
16
+ new_item = Item(
17
+ title=name,
18
+ category="LOGIN",
19
+ tags=tags,
20
+ fields=[
21
+ Field(value=username, purpose="USERNAME"),
22
+ Field(value=password, purpose="PASSWORD"),
23
+ Field(value=notes, purpose="NOTES")
24
+ ],
25
+ )
26
+ created_item = self.client.create_item(self.vault_id, new_item)
27
+ return created_item
28
+
29
+ def delete_item(self, item_id):
30
+ return self.client.delete_item(self.vault_id, item_id)
31
+
32
+ def get_item(self, item_id):
33
+ item = self.client.get_item(item_id, self.vault_id)
34
+ return item
35
+
36
+ def get_item_by_title(self, title: str='', totp: bool=False):
37
+ '''通过title获取具体的值, 会返回一个字典'''
38
+ value = {}
39
+
40
+ item = self.client.get_item_by_title(title, self.vault_id)
41
+
42
+ if totp:
43
+ for field in item.fields:
44
+ if field.totp != None:
45
+ return field.totp
46
+ else:
47
+ for field in item.fields:
48
+ value[field.label] = field.value
49
+ return value
50
+
51
+ def update_item(self, item_id: str, name: str=None, username: str=None, password: str=None, tags: list=None, notes: str=None):
52
+ '''
53
+ 更新单个item
54
+ Parms:
55
+ title(str): 一条数据的名称
56
+ name(str): 需要更新的参数值, 例如 username, password
57
+ value(str): 新的值
58
+ Returns:
59
+ Item object(class): 更新后的item
60
+ '''
61
+ # self.get_item_by_title(title="")
62
+ update_item = self.get_item(item_id=item_id)
63
+
64
+ if name:
65
+ update_item.title = name
66
+
67
+ for field in update_item.fields:
68
+ if field.purpose == "USERNAME":
69
+ field.value = username
70
+ if field.purpose == "PASSWORD":
71
+ field.value = password
72
+ if field.purpose == "NOTES":
73
+ field.value = notes
74
+
75
+ if tags:
76
+ update_item.tags = tags
77
+ return self.client.update_item(item_id, self.vault_id, update_item)
78
+
79
+ def search_item(self, title: str=None, tag: str=None) -> list:
80
+ if title:
81
+ filter_query = f'title eq "{title}"'
82
+ if tag:
83
+ filter_query = f'tag eq "{tag}"'
84
+ else:
85
+ filter_query = None
86
+ return self.client.get_items(self.vault_id, filter_query=filter_query)
87
+
88
+
89
+
90
+ if __name__ == "__main__":
91
+ pass
92
+ # my1p.update_item(title='fengmao-server', name='password', value='test')
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 1Password 客户端封装类 - 异步版本(简化)
4
+ """
5
+
6
+ import os
7
+ from typing import Optional, Dict, Any, List, Literal
8
+ try:
9
+ from onepassword.client import Client
10
+ from onepassword import (
11
+ ItemCreateParams, ItemCategory, ItemField, ItemFieldType,
12
+ ItemSection, Website, AutofillBehavior
13
+ )
14
+ except ImportError:
15
+ print("⚠️ 请安装 1password SDK: pip install onepassword")
16
+ Client = None
17
+
18
+
19
+ class OnePasswordClient:
20
+ """1Password 客户端封装类"""
21
+
22
+ def __init__(self, service_account_token: str = None, integration_name: str = "pytbox", integration_version: str = "v1.0.0", vault_id: str = None):
23
+ """
24
+ 初始化 1Password 客户端
25
+
26
+ Args:
27
+ service_account_token: 服务账户令牌,如果不提供则从环境变量 OP_SERVICE_ACCOUNT_TOKEN 获取
28
+ integration_name: 集成名称
29
+ integration_version: 集成版本
30
+ vault_id: 默认保险库ID
31
+ """
32
+ self.token = service_account_token or os.getenv("OP_SERVICE_ACCOUNT_TOKEN")
33
+ self.integration_name = integration_name
34
+ self.integration_version = integration_version
35
+ self.vault_id = vault_id
36
+ self.client: Optional[Client] = None
37
+
38
+ if not self.token:
39
+ raise ValueError("未找到 1Password 服务账户令牌,请设置 OP_SERVICE_ACCOUNT_TOKEN 环境变量或传入 service_account_token 参数")
40
+
41
+ if Client is None:
42
+ raise ImportError("未安装 onepassword SDK,请运行: pip install onepassword")
43
+
44
+ # print(f"🔧 初始化1Password客户端:")
45
+ # print(f" - 集成名称: {self.integration_name}")
46
+ # print(f" - 默认保险库ID: {self.vault_id}")
47
+ # print(f" - 令牌: {'已设置' if self.token else '未设置'}")
48
+
49
+ async def authenticate(self) -> None:
50
+ """认证并初始化客户端"""
51
+ if not self.client:
52
+ self.client = await Client.authenticate(
53
+ auth=self.token,
54
+ integration_name=self.integration_name,
55
+ integration_version=self.integration_version
56
+ )
57
+ # print(f"✅ 1Password 客户端认证成功")
58
+
59
+ async def ensure_authenticated(self) -> None:
60
+ """确保客户端已认证"""
61
+ if not self.client:
62
+ await self.authenticate()
63
+
64
+ async def create_item(self, url, name, username, password, notes, tags: list=[]):
65
+ # Create an Item and add it to your vault.
66
+ await self.ensure_authenticated()
67
+ to_create = ItemCreateParams(
68
+ title=name,
69
+ category=ItemCategory.LOGIN,
70
+ vault_id=self.vault_id,
71
+ fields=[
72
+ ItemField(
73
+ id="username",
74
+ title="username",
75
+ field_type=ItemFieldType.TEXT,
76
+ value=username,
77
+ ),
78
+ ItemField(
79
+ id="password",
80
+ title="password",
81
+ field_type=ItemFieldType.CONCEALED,
82
+ value=password,
83
+ ),
84
+ ],
85
+ sections=[
86
+ ItemSection(id="", title=""),
87
+ ItemSection(id="totpsection", title=""),
88
+ ],
89
+ tags=tags,
90
+ notes=notes,
91
+ websites=[
92
+ Website(
93
+ label="网站",
94
+ url=url,
95
+ autofillBehavior=AutofillBehavior.ANYWHEREONWEBSITE
96
+ # autofill_behavior=AutofillBehavior.NEVER,
97
+ )
98
+ ],
99
+ )
100
+ created_item = await self.client.items.create(to_create)
101
+ return created_item
102
+
103
+ async def get_item(self, item_id):
104
+ # Retrieve an item from your vault.
105
+ item = await self.client.items.get(self.vault_id, item_id)
106
+ return item
107
+
108
+ async def get_vault_items(self) -> List[Dict[str, Any]]:
109
+ """
110
+ 获取保险库中的所有项目
111
+
112
+ Args:
113
+ vault_id: 保险库ID,如果不提供则使用默认的
114
+
115
+ Returns:
116
+ List[Dict]: 项目列表
117
+ """
118
+ await self.ensure_authenticated()
119
+ return await self.client.items.list(value_id = self.vault_id)
120
+
121
+ async def list_vaults(self) -> List[Dict[str, Any]]:
122
+ """
123
+ 列出所有可用的保险库
124
+
125
+ Returns:
126
+ List[Dict]: 保险库列表
127
+ """
128
+ await self.ensure_authenticated()
129
+ try:
130
+ vaults = await self.client.vaults.list_all()
131
+ print(f"📁 找到 {len(vaults)} 个保险库:")
132
+ for i, vault in enumerate(vaults):
133
+ print(f" {i+1}. {vault.get('name', 'Unknown')} (ID: {vault.get('id', 'Unknown')})")
134
+ return vaults
135
+ except Exception as e:
136
+ print(f"❌ 获取保险库列表失败: {e}")
137
+ return []
138
+
139
+ async def get_item_by_item_id(self, item_id: str, title: Literal['username', 'password', 'totp']) -> Dict[str, Any]:
140
+ """
141
+ 根据标题获取项目
142
+
143
+ Args:
144
+ title: 项目标题
145
+ vault_id: 保险库ID,如果不提供则使用默认的
146
+
147
+ Returns:
148
+ Dict: 项目信息,如果未找到返回None
149
+ """
150
+ r = await self.get_item(item_id=item_id)
151
+ for field in r.fields:
152
+ if title == 'totp':
153
+ if 'TOTP' in field.id:
154
+ return field.details.content.code
155
+ else:
156
+ if field.title == title:
157
+ return field.value
158
+
159
+ async def search_items(self, query: str, vault_id: str = None) -> List[Dict[str, Any]]:
160
+ """
161
+ 搜索项目
162
+
163
+ Args:
164
+ query: 搜索查询
165
+ vault_id: 保险库ID,如果不提供则使用默认的
166
+
167
+ Returns:
168
+ List[Dict]: 搜索结果
169
+ """
170
+ await self.ensure_authenticated()
171
+ vault = vault_id or self.vault_id
172
+ if not vault:
173
+ raise ValueError("必须提供 vault_id 或在初始化时设置默认 vault_id")
174
+
175
+ items = await self.get_vault_items(vault)
176
+ # 简单的标题匹配搜索
177
+ return [item for item in items if query.lower() in item.get('title', '').lower()]
178
+
179
+ async def close(self) -> None:
180
+ """关闭客户端连接"""
181
+ if self.client:
182
+ # 如果SDK提供关闭方法,在这里调用
183
+ self.client = None
184
+ print("🔒 1Password 客户端连接已关闭")
185
+
186
+ async def __aenter__(self):
187
+ """异步上下文管理器入口"""
188
+ await self.authenticate()
189
+ return self
190
+
191
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
192
+ """异步上下文管理器出口"""
193
+ await self.close()
194
+
195
+
196
+ if __name__ == "__main__":
197
+ pass
pytbox/utils/env.py ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ from typing import Literal
5
+
6
+
7
+ def get_env_by_file_exist(file_path: str) -> Literal['prod', 'dev']:
8
+ '''
9
+ 检查当前的运行环境
10
+
11
+ Returns:
12
+ Literal['prod', 'dev']: 环境
13
+ '''
14
+ if os.path.exists(file_path):
15
+ return 'prod'
16
+ else:
17
+ return 'dev'
18
+
19
+
20
+ def get_env_by_os_environment() -> Literal['dev', 'prod']:
21
+ '''
22
+ 根据环境变量获取环境
23
+
24
+ Returns:
25
+ Literal['dev', 'prod']: 环境,dev 表示开发环境,prod 表示生产环境
26
+ '''
27
+ if os.getenv('ENV') == 'dev':
28
+ return 'dev'
29
+ else:
30
+ return 'prod'
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Optional
5
+
6
+
7
+ @dataclass
8
+ class ReturnResponse:
9
+ """统一响应格式类
10
+
11
+ Attributes:
12
+ code: 响应状态码
13
+ 0 - 成功 (SUCCESS)
14
+ 1 - 一般错误 (ERROR)
15
+ 2 - 警告 (WARNING)
16
+ 3 - 未授权 (UNAUTHORIZED)
17
+ 4 - 资源未找到 (NOT_FOUND)
18
+ 5 - 请求超时 (TIMEOUT)
19
+ 6 - 参数错误 (INVALID_PARAMS)
20
+ 7 - 权限不足 (PERMISSION_DENIED)
21
+ 8 - 服务不可用 (SERVICE_UNAVAILABLE)
22
+ 9 - 数据库错误 (DATABASE_ERROR)
23
+ 10 - 网络错误 (NETWORK_ERROR)
24
+ message: 响应消息描述
25
+ data: 响应数据,可以是任意类型
26
+ """
27
+ code: int = 0
28
+ msg: str = ''
29
+ data: Optional[Any] = None
30
+
31
+ def is_success(self) -> bool:
32
+ """判断是否为成功响应
33
+
34
+ Returns:
35
+ bool: code为0时返回True,否则返回False
36
+ """
37
+ return self.code == 0
38
+
39
+ def is_error(self) -> bool:
40
+ """判断是否为错误响应
41
+
42
+ Returns:
43
+ bool: code不为0时返回True,否则返回False
44
+ """
45
+ return self.code != 0
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ import time
5
+ import pytz
6
+ import datetime
7
+
8
+
9
+ class TimeUtils:
10
+
11
+ @staticmethod
12
+ def get_timestamp(now: bool=True) -> int:
13
+ '''
14
+ 获取时间戳
15
+
16
+ Args:
17
+ now (bool, optional): _description_. Defaults to True.
18
+
19
+ Returns:
20
+ _type_: _description_
21
+ '''
22
+ if now:
23
+ return int(time.time())
24
+ else:
25
+ return int(time.time() * 1000)
26
+
27
+ @staticmethod
28
+ def get_time_object(now: bool=True):
29
+ '''
30
+ 获取当前时间, 加入了时区信息, 简单是存储在 Mongo 中时格式为 ISODate
31
+
32
+ Returns:
33
+ current_time(class): 时间, 格式: 2024-04-23 16:48:11.591589+08:00
34
+ '''
35
+ if now:
36
+ return datetime.datetime.now(pytz.timezone('Asia/Shanghai'))
37
+
38
+ @staticmethod
39
+ def get_utc_time():
40
+ return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"