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.

pytbox/alicloud/sls.py ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import Literal
4
+ # 引入sls包。
5
+ from aliyun.log import GetLogsRequest, LogItem, PutLogsRequest
6
+ from aliyun.log import LogClient as SlsLogClient
7
+ from aliyun.log.auth import AUTH_VERSION_4
8
+ from ..utils.env import check_env
9
+
10
+
11
+
12
+ class AliCloudSls:
13
+
14
+ def __init__(self, access_key_id: str=None, access_key_secret: str=None, project: str, logstore: str):
15
+ # 日志服务的服务接入点
16
+ self.endpoint = "cn-shanghai.log.aliyuncs.com"
17
+ # 创建 LogClient 实例,使用 V4 签名,根据实际情况填写 region,这里以杭州为例
18
+ self.client = SlsLogClient(self.endpoint, access_key_id, access_key_secret, auth_version=AUTH_VERSION_4, region='cn-shanghai')
19
+ self.project = project
20
+ self.logstore = logstore
21
+
22
+ def get_logs(self, project_name, logstore_name, query, from_time, to_time):
23
+ # Project名称。
24
+ # project_name = "sh-prod-network-devices-log"
25
+ # Logstore名称
26
+ # logstore_name = "sh-prod-network-devices-log"
27
+ # 查询语句。
28
+ # query = "*| select dev,id from " + logstore_name
29
+ # query = "*"
30
+ # 索引。
31
+ logstore_index = {'line': {
32
+ 'token': [',', ' ', "'", '"', ';', '=', '(', ')', '[', ']', '{', '}', '?', '@', '&', '<', '>', '/', ':', '\n', '\t',
33
+ '\r'], 'caseSensitive': False, 'chn': False}, 'keys': {'dev': {'type': 'text',
34
+ 'token': [',', ' ', "'", '"', ';', '=',
35
+ '(', ')', '[', ']', '{', '}',
36
+ '?', '@', '&', '<', '>', '/',
37
+ ':', '\n', '\t', '\r'],
38
+ 'caseSensitive': False, 'alias': '',
39
+ 'doc_value': True, 'chn': False},
40
+ 'id': {'type': 'long', 'alias': '',
41
+ 'doc_value': True}}, 'log_reduce': False,
42
+ 'max_text_len': 2048}
43
+
44
+ # from_time和to_time表示查询日志的时间范围,Unix时间戳格式。
45
+ # from_time = int(time.time()) - 60
46
+ # to_time = time.time() + 60
47
+ # # 通过SQL查询日志。
48
+ # def get_logs():
49
+ # print("ready to query logs from logstore %s" % logstore_name)
50
+ request = GetLogsRequest(project_name, logstore_name, from_time, to_time, query=query)
51
+ response = self.client.get_logs(request)
52
+ for log in response.get_logs():
53
+ yield log.contents
54
+
55
+ def put_logs(self,
56
+ topic: Literal['meraki_alert', 'program']='program',
57
+ level: Literal['INFO', 'WARN']='INFO',
58
+ msg: str=None,
59
+ app: str=None,
60
+ caller_filename: str=None,
61
+ caller_lineno: int=None,
62
+ caller_function: str=None,
63
+ call_full_filename: str=None
64
+ ):
65
+ log_group = []
66
+ log_item = LogItem()
67
+ contents = [
68
+ ('env', check_env()),
69
+ ('level', level),
70
+ ('app', app),
71
+ ('msg', msg),
72
+ ('caller_filename', caller_filename),
73
+ ('caller_lineno', str(caller_lineno)),
74
+ ('caller_function', caller_function),
75
+ ('call_full_filename', call_full_filename)
76
+ ]
77
+ log_item.set_contents(contents)
78
+ log_group.append(log_item)
79
+ request = PutLogsRequest(self.project, self.logstore, topic, "", log_group, compress=False)
80
+ self.client.put_logs(request)
81
+
82
+ def put_logs_for_meraki(self, alert):
83
+ log_group = []
84
+ log_item = LogItem()
85
+ contents = alert
86
+ log_item.set_contents(contents)
87
+ log_group.append(log_item)
88
+ request = PutLogsRequest(self.project, self.logstore, "", "", log_group, compress=False)
89
+ self.client.put_logs(request)
90
+
91
+
92
+ if __name__ == "__main__":
93
+ pass
@@ -0,0 +1,7 @@
1
+ """
2
+ Common utilities and base classes
3
+ """
4
+
5
+ from .base import BaseAPI, BaseResponse
6
+
7
+ __all__ = ["BaseAPI", "BaseResponse"]
pytbox/common/base.py ADDED
File without changes
pytbox/dida365.py ADDED
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """滴答清单API客户端。
4
+
5
+ 此模块提供了与滴答清单API交互的功能,包括认证和基本操作。
6
+ """
7
+ import re
8
+ import json
9
+ from typing import Dict, Any, Literal, Union
10
+ from dataclasses import dataclass
11
+ import requests
12
+ from datetime import datetime
13
+ from .utils.response import ReturnResponse
14
+
15
+
16
+ @dataclass
17
+ class Task:
18
+ task_id: str
19
+ project_id: str
20
+ title: str
21
+ content: str
22
+ desc: str
23
+ start_date: str
24
+ due_date: str
25
+ priority: int
26
+ status: int
27
+ tags: list
28
+ completed_time: str
29
+ assignee: int
30
+
31
+ @dataclass
32
+ class DidaResponse:
33
+ code: int
34
+ message: str
35
+ data: dict
36
+
37
+
38
+ class ProcessDidaResponse:
39
+ @staticmethod
40
+ def status(status):
41
+ if status == 0:
42
+ return '进行中'
43
+ elif status == 2:
44
+ return '已完成'
45
+ else:
46
+ return '未识别'
47
+
48
+ @staticmethod
49
+ def priority(priority):
50
+ if priority == 1:
51
+ return '低优先级'
52
+ elif priority == 3:
53
+ return '中优先级'
54
+ elif priority == 5:
55
+ return '高优先级'
56
+ else:
57
+ return '未识别'
58
+
59
+
60
+ class Dida365:
61
+ """滴答清单API客户端类。
62
+
63
+ 处理滴答清单API的认证和操作。
64
+
65
+ Attributes:
66
+ config: 滴答清单配置实例
67
+ access_token: 访问令牌
68
+ refresh_token: 刷新令牌
69
+ """
70
+
71
+ def __init__(self, access_token: str, cookie: str) -> None:
72
+ """初始化客户端。
73
+
74
+ Args:
75
+ access_token: 滴答清单配置实例
76
+ """
77
+ self.access_token = access_token
78
+ self.base_url = 'https://api.dida365.com'
79
+ self.cookie = cookie
80
+ self.headers = {
81
+ "Authorization": f"Bearer {self.access_token}",
82
+ "Content-Type": "application/json",
83
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
84
+ }
85
+ self.cookie_headers = {
86
+ "Content-Type": "application/json",
87
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
88
+ "Cookie": self.cookie
89
+ }
90
+ self.timeout = 10
91
+
92
+ def request(self, api_url: str=None, method: str='GET', payload: dict={}):
93
+ """发送请求。
94
+
95
+ Args:
96
+ url: 请求URL
97
+ method: 请求方法
98
+ """
99
+
100
+ url = f"{self.base_url}{api_url}"
101
+ response = requests.request(
102
+ method=method,
103
+ url=url,
104
+ headers=self.headers,
105
+ data=json.dumps(payload),
106
+ timeout=3
107
+ )
108
+
109
+ if response.status_code == 200:
110
+ if 'complete' in api_url:
111
+ return DidaResponse(code=0, msg='success', data=None)
112
+ else:
113
+ try:
114
+ return DidaResponse(code=0, msg='success', data=response.json())
115
+ except Exception as e:
116
+ return DidaResponse(code=1, msg='warning', data=response)
117
+ else:
118
+ return DidaResponse(code=1, msg='error', data=response.json())
119
+
120
+ def task_list(self, project_id: str, enhancement: bool=True):
121
+ """获取任务列表。
122
+
123
+ Returns:
124
+ List[Dict[str, Any]]: 任务列表
125
+
126
+ Raises:
127
+ requests.exceptions.RequestException: 请求失败时抛出
128
+ ValueError: 没有访问令牌时抛出
129
+ """
130
+ if enhancement:
131
+ tasks = requests.request(
132
+ method='GET',
133
+ url=f'https://api.dida365.com/api/v2/project/{project_id}/tasks',
134
+ headers=self.cookie_headers,
135
+ timeout=3
136
+ ).json()
137
+ for task in tasks:
138
+ yield Task(task_id=task.get('id'),
139
+ project_id=task.get('projectId'),
140
+ title=task.get('title'),
141
+ content=task.get('content'),
142
+ desc=task.get('desc'),
143
+ start_date=task.get('startDate'),
144
+ due_date=task.get('dueDate'),
145
+ priority=ProcessDidaResponse.priority(task.get('priority')),
146
+ status=ProcessDidaResponse.status(task.get('status')),
147
+ tags=task.get('tags'),
148
+ completed_time=task.get('completedTime'),
149
+ assignee=task.get('assignee'))
150
+ else:
151
+ tasks = self.request(api_url=f"/open/v1/project/{project_id}/data", method="GET")['tasks']
152
+ for task in tasks:
153
+ yield Task(task_id=task.get('id'),
154
+ project_id=task.get('projectId'),
155
+ title=task.get('title'),
156
+ content=task.get('content'),
157
+ desc=task.get('desc'),
158
+ start_date=task.get('startDate'),
159
+ due_date=task.get('dueDate'),
160
+ priority=ProcessDidaResponse.priority(task.get('priority')),
161
+ status=ProcessDidaResponse.status(task.get('status')),
162
+ tags=task.get('tags'),
163
+ completed_time=task.get('completedTime'),
164
+ assignee=task.get('assignee'))
165
+
166
+ def task_create(self,
167
+ project_id,
168
+ title :str,
169
+ content :str=None,
170
+ tags :list=None,
171
+ priority :Literal[1, 3, 5]=1,
172
+ start_date: datetime=datetime.utcnow(),
173
+ start_time_offset: bool=True,
174
+ due_date: str=None,
175
+ kind: Union[None, Literal["NOTE"], str] = 'TEXT',
176
+ assignee: int=None,
177
+ reminder: bool=True):
178
+ '''
179
+ _summary_
180
+
181
+ Args:
182
+ project_id (_type_): _description_
183
+ title (str): _description_
184
+ content (str, optional): _description_. Defaults to None.
185
+ tags (list, optional): _description_. Defaults to None.
186
+ priority (Literal[1, 3, 5], optional): _description_. Defaults to 1.
187
+ start_date (datetime, optional): 传入 utc 时区的时间对象. Defaults to datetime.utcnow().
188
+ due_date (str, optional): _description_. Defaults to None.
189
+ kind (Union[None, Literal[&quot;NOTE&quot;], str], optional): _description_. Defaults to 'TEXT'.
190
+ assignee (int, optional): _description_. Defaults to None.
191
+ reminder (bool, optional): _description_. Defaults to True.
192
+ '''
193
+ # 如果存在start_date,将其增加3分钟
194
+ if isinstance(start_date, datetime):
195
+ if start_time_offset:
196
+ if start_date.minute + 3 >= 60:
197
+ minute = 59
198
+ else:
199
+ minute = start_date.minute + 3
200
+ start_date_offset = start_date.replace(minute=minute)
201
+ start_date_format = start_date_offset.strftime('%Y-%m-%dT%H:%M:%S.000+0000')
202
+ else:
203
+ start_date_format = start_date.strftime('%Y-%m-%dT%H:%M:%S.000+0000')
204
+ else:
205
+ start_date_format = start_date.strftime('%Y-%m-%dT%H:%M:%S.000+0000')
206
+
207
+ payload = {
208
+ "projectId": project_id,
209
+ "priority": priority,
210
+ "assignee": str(assignee),
211
+ # "startDate": start_date_format,
212
+ "title": title,
213
+ "timeZone": "Asia/Shanghai",
214
+ "kind": kind,
215
+ "content": content,
216
+ }
217
+
218
+ payload['startDate'] = start_date_format
219
+
220
+ if isinstance(due_date, datetime):
221
+ due_date_format = due_date.strftime('%Y-%m-%dT%H:%M:%S.000+0000')
222
+ payload['dueDate'] = due_date_format
223
+
224
+ if reminder:
225
+ payload["reminders"] = [
226
+ "TRIGGER:PT0S"
227
+ ]
228
+ if tags:
229
+ payload['tags'] = tags
230
+
231
+ return self.request(api_url="/open/v1/task", method="POST", payload=payload)
232
+
233
+ def task_complete(self, project_id: str, task_id: str):
234
+ """完成任务。
235
+
236
+ Args:
237
+ project_id: 项目ID
238
+ task_id: 任务ID
239
+ """
240
+ return self.request(api_url=f"/open/v1/project/{project_id}/task/{task_id}/complete", method="POST")
241
+
242
+ def task_get(self, project_id, task_id):
243
+ return self.request(api_url = f'/open/v1/project/{project_id}/task/{task_id}')
244
+
245
+ def task_comments(self, project_id: str, task_id: str):
246
+ return requests.request(
247
+ method='GET',
248
+ url=f'https://api.dida365.com/api/v2/project/{project_id}/task/{task_id}/comments',
249
+ headers=self.cookie_headers,
250
+ timeout=3
251
+ ).json()
252
+
253
+ def task_update(self, project_id: str=None, task_id: str=None, title: str=None, content: str=None, priority: int=None, start_date: str=None, content_front: bool=False):
254
+ """更新任务。
255
+
256
+ Args:
257
+ project_id: 项目ID
258
+ task_id: 任务ID
259
+ """
260
+ task_get_resp = self.task_get(project_id, task_id)
261
+ if task_get_resp.code == 0:
262
+ exists_content = task_get_resp.data['content']
263
+
264
+ if content_front:
265
+ content = f'{content}\n{exists_content}'
266
+ else:
267
+ content = f'{exists_content}\n{content}'
268
+
269
+ elif task_get_resp.code == 1:
270
+ return task_get_resp
271
+
272
+ payload = {
273
+ "projectId": project_id,
274
+ "taskId": task_id,
275
+ "title": title,
276
+ "content": content,
277
+ "priority": priority,
278
+ }
279
+ if start_date:
280
+ payload["startDate"] = start_date
281
+
282
+ return self.request(api_url=f"/open/v1/task/{task_id}", method="POST", payload=payload)
283
+
284
+ def get_projects(self) -> ReturnResponse:
285
+ response = requests.request(
286
+ method='GET',
287
+ url=f'{self.base_url}/open/v1/project',
288
+ headers=self.headers,
289
+ timeout=self.timeout
290
+ )
291
+ if response.status_code == 200:
292
+ return ReturnResponse(code=0, msg=f"获取到 {len(response.json())} 条 project", data=response.json())
293
+ else:
294
+ return ReturnResponse(code=1, msg=f"获取 project 失败: {response.status_code}", data=response.json())
295
+
296
+ if __name__ == "__main__":
297
+ pass
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import Optional, Union, Any, Dict, Type, List
4
+ from dataclasses import dataclass
5
+ from .endpoints import (
6
+ AuthEndpoint,
7
+ MessageEndpoint,
8
+ ExtensionsEndpoint,
9
+ BitableEndpoint,
10
+ DocsEndpoint,
11
+ CalendarEndpoint
12
+ )
13
+
14
+ from .errors import (
15
+ RequestTimeoutError,
16
+ is_api_error_code,
17
+ APIResponseError,
18
+ HTTPResponseError
19
+ )
20
+ from types import TracebackType
21
+ from abc import abstractclassmethod
22
+ import httpx
23
+ from httpx import Request, Response
24
+ from .typing import SyncAsync
25
+
26
+ @dataclass
27
+ class ClientOptions:
28
+ auth: Optional[str] = None
29
+ timeout_ms: int = 60_000
30
+ base_url: str = "https://open.feishu.cn/open-apis"
31
+
32
+
33
+ @dataclass
34
+ class FeishuResponse:
35
+ code: int
36
+ data: dict
37
+ chat_id: str
38
+ message_id: str
39
+ msg_type: str
40
+ sender: dict
41
+ msg: dict
42
+ expire: int
43
+ tenant_access_token: str
44
+
45
+
46
+ class BaseClient:
47
+
48
+ def __init__(self,
49
+ app_id: str,
50
+ app_secret: str,
51
+ client: Union[httpx.Client, httpx.AsyncClient],
52
+ ) -> None:
53
+
54
+ self.app_id = app_id
55
+ self.app_secret = app_secret
56
+
57
+ self.options = ClientOptions()
58
+
59
+ self._clients: List[Union[httpx.Client, httpx.AsyncClient]] = []
60
+ self.client = client
61
+
62
+ self.auth = AuthEndpoint(self)
63
+ self.message = MessageEndpoint(self)
64
+ self.bitable = BitableEndpoint(self)
65
+ self.docs = DocsEndpoint(self)
66
+ self.calendar = CalendarEndpoint(self)
67
+ self.extensions = ExtensionsEndpoint(self)
68
+
69
+ @property
70
+ def client(self) -> Union[httpx.Client, httpx.AsyncClient]:
71
+ return self._clients[-1]
72
+
73
+ @client.setter
74
+ def client(self, client: Union[httpx.Client, httpx.AsyncClient]) -> None:
75
+ client.base_url = httpx.URL(f'{self.options.base_url}/')
76
+ client.timeout = httpx.Timeout(timeout=self.options.timeout_ms / 1_000)
77
+ client.headers = httpx.Headers(
78
+ {
79
+ "User-Agent": "cc_feishu",
80
+ }
81
+ )
82
+ self._clients.append(client)
83
+
84
+
85
+ def _build_request(self,
86
+ method: str,
87
+ path: str,
88
+ query: Optional[Dict[str, Any]] = None,
89
+ body: Optional[Dict[str, Any]] = None,
90
+ data: Optional[Any] = None,
91
+ files: Optional[Dict[str, Any]] = None,
92
+ token: Optional[str] = None) -> Request:
93
+
94
+ headers = httpx.Headers()
95
+ headers['Authorization'] = f'Bearer {token}'
96
+ if 'image' in path:
97
+ headers['Content-Type'] = 'multipart/form-data'
98
+
99
+ return self.client.build_request(
100
+ method=method, url=path, params=query, json=body, headers=headers, files=files, data=data
101
+ )
102
+
103
+ def _parse_response(self, response) -> Any:
104
+ response = response.json()
105
+ return FeishuResponse(code=response.get('code'),
106
+ data=response.get('data'),
107
+ chat_id=response.get('chat_id'),
108
+ message_id=response.get('message_id'),
109
+ msg_type=response.get('msg_type'),
110
+ sender=response.get('sender'),
111
+ msg=response.get('msg'),
112
+ expire=response.get('expire'),
113
+ tenant_access_token=response.get('tenant_access_token'))
114
+
115
+ @abstractclassmethod
116
+ def request(self,
117
+ path: str,
118
+ method: str,
119
+ query: Optional[Dict[Any, Any]] = None,
120
+ body: Optional[Dict[Any, Any]] = None,
121
+ auth: Optional[str] = None,
122
+ data: Optional[Any] = None,
123
+ ) -> SyncAsync[Any]:
124
+ pass
125
+
126
+
127
+ class Client(BaseClient):
128
+
129
+ client: httpx.Client
130
+
131
+ def __init__(self,
132
+ app_id: str,
133
+ app_secret: str,
134
+ client: Optional[httpx.Client]=None) -> None:
135
+
136
+ if client is None:
137
+ client = httpx.Client()
138
+ super().__init__(app_id, app_secret, client)
139
+
140
+ def __enter__(self) -> "Client":
141
+ self.client = httpx.Client()
142
+ self.client.__enter__()
143
+ return self
144
+
145
+ def __exit__(self,
146
+ exc_type: Type[BaseException],
147
+ exc_value: BaseException,
148
+ traceback: TracebackType) -> None:
149
+ self.client.__exit__(exc_type, exc_value, traceback)
150
+ del self._clients[-1]
151
+
152
+ def close(self) -> None:
153
+ self.client.close()
154
+
155
+ def _get_token(self):
156
+ if self.auth.fetch_token_from_file():
157
+ return self.auth.fetch_token_from_file()
158
+ else:
159
+ self.auth.save_token_to_file()
160
+ return self.auth.fetch_token_from_file()
161
+
162
+ def request(self,
163
+ path: str,
164
+ method: str,
165
+ query: Optional[Dict[Any, Any]] = None,
166
+ body: Optional[Dict[Any, Any]] = None,
167
+ files: Optional[Dict[Any, Any]] = None,
168
+ token: Optional[str] = None,
169
+ data: Optional[Any] = None,
170
+ ) -> Any:
171
+
172
+ request = self._build_request(method, path, query, body, files=files, data=data, token=self._get_token())
173
+ try:
174
+ response = self._parse_response(self.client.send(request))
175
+
176
+ if 'Invalid access token for authorization' in response.msg:
177
+ self.auth.save_token_to_file()
178
+ request = self._build_request(method, path, query, body, files=files, data=data, token=self._get_token())
179
+ return self._parse_response(self.client.send(request))
180
+ else:
181
+ return response
182
+ except httpx.TimeoutException:
183
+ raise RequestTimeoutError()