codosdk 1.0.55__tar.gz → 1.0.57__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.
Files changed (58) hide show
  1. {codosdk-1.0.55 → codosdk-1.0.57}/PKG-INFO +1 -1
  2. codosdk-1.0.57/README.md +231 -0
  3. {codosdk-1.0.55 → codosdk-1.0.57}/codosdk.egg-info/PKG-INFO +1 -1
  4. {codosdk-1.0.55 → codosdk-1.0.57}/codosdk.egg-info/SOURCES.txt +4 -0
  5. {codosdk-1.0.55 → codosdk-1.0.57}/codosdk.egg-info/top_level.txt +1 -0
  6. {codosdk-1.0.55 → codosdk-1.0.57}/setup.py +2 -2
  7. codosdk-1.0.57/tests/test_openapi_sign.py +41 -0
  8. codosdk-1.0.57/websdk2/cloud/__init__.py +0 -0
  9. codosdk-1.0.57/websdk2/openapi_client.py +127 -0
  10. codosdk-1.0.57/websdk2/openapi_sign.py +120 -0
  11. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/sqlalchemy_pagination.py +2 -1
  12. codosdk-1.0.55/README.md +0 -58
  13. {codosdk-1.0.55 → codosdk-1.0.57}/LICENSE +0 -0
  14. {codosdk-1.0.55 → codosdk-1.0.57}/codosdk.egg-info/dependency_links.txt +0 -0
  15. {codosdk-1.0.55 → codosdk-1.0.57}/codosdk.egg-info/requires.txt +0 -0
  16. {codosdk-1.0.55 → codosdk-1.0.57}/opssdk/__init__.py +0 -0
  17. {codosdk-1.0.55 → codosdk-1.0.57}/opssdk/utils/__init__.py +0 -0
  18. {codosdk-1.0.55 → codosdk-1.0.57}/pyproject.toml +0 -0
  19. {codosdk-1.0.55 → codosdk-1.0.57}/setup.cfg +0 -0
  20. {codosdk-1.0.55/websdk2 → codosdk-1.0.57/tests}/__init__.py +0 -0
  21. {codosdk-1.0.55/websdk2/cloud → codosdk-1.0.57/websdk2}/__init__.py +0 -0
  22. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/api_set.py +0 -0
  23. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/apis/__init__.py +0 -0
  24. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/apis/admin_apis.py +0 -0
  25. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/apis/agent_apis.py +0 -0
  26. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/apis/cmdb_apis.py +0 -0
  27. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/apis/kerrigan_apis.py +0 -0
  28. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/apis/mgv4_apis.py +0 -0
  29. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/apis/notice_apis.py +0 -0
  30. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/apis/task_apis.py +0 -0
  31. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/application.py +0 -0
  32. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/base_handler.py +0 -0
  33. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/cache.py +0 -0
  34. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/cache_context.py +0 -0
  35. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/client.py +0 -0
  36. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/cloud/qcloud_api.py +0 -0
  37. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/cloud/ucloud_api.py +0 -0
  38. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/cloud_utils.py +0 -0
  39. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/configs.py +0 -0
  40. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/consts.py +0 -0
  41. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/crud_utils.py +0 -0
  42. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/db_context.py +0 -0
  43. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/error.py +0 -0
  44. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/fetch_coroutine.py +0 -0
  45. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/jwt_token.py +0 -0
  46. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/ldap.py +0 -0
  47. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/logger.py +0 -0
  48. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/model_utils.py +0 -0
  49. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/mqhelper.py +0 -0
  50. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/program.py +0 -0
  51. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/salt_api.py +0 -0
  52. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/tools.py +0 -0
  53. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/utils/__init__.py +0 -0
  54. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/utils/cc_crypto.py +0 -0
  55. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/utils/date_format.py +0 -0
  56. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/utils/pydantic_utils.py +0 -0
  57. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/utils/pydantic_utils_v2.py +0 -0
  58. {codosdk-1.0.55 → codosdk-1.0.57}/websdk2/web_logs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codosdk
3
- Version: 1.0.55
3
+ Version: 1.0.57
4
4
  Summary: CODO项目的Python SDK
5
5
  Home-page: https://github.com/ss1917/codo_sdk/
6
6
  Author: shenshuo
@@ -0,0 +1,231 @@
1
+ # CODO SDK
2
+
3
+ [![Python](https://img.shields.io/badge/Python-%3E%3D3.9-blue)](https://www.python.org/)
4
+ [![License](https://img.shields.io/badge/License-GPLv3-green)](https://www.gnu.org/licenses/gpl-3.0.html)
5
+ [![Version](https://img.shields.io/badge/version-1.0.55-brightgreen)](https://pypi.org/project/codosdk/)
6
+
7
+ CODO项目的官方Python SDK,提供企业级Web应用开发框架和运维工具集成。基于Tornado,集成数据库、缓存、消息队列、认证等核心组件。
8
+
9
+ ## 核心特性
10
+
11
+ - **Tornado Web框架** - 异步请求处理,内置认证和权限管理
12
+ - **数据库支持** - SQLAlchemy ORM,支持主从库配置
13
+ - **Redis缓存** - 多连接池管理
14
+ - **消息队列** - RabbitMQ集成
15
+ - **多种认证** - JWT、LDAP、Session
16
+ - **API集成** - 预定义CODO各平台API接口
17
+ - **数据验证** - Pydantic模型验证
18
+ - **工具支持** - 各类常用工具封装
19
+
20
+ ## 安装
21
+
22
+ ```bash
23
+ # 从PyPI安装
24
+ pip install codosdk
25
+
26
+ # 从GitHub安装最新版
27
+ pip install -U git+https://github.com/ss1917/ops_sdk.git
28
+ ```
29
+
30
+ ## 快速开始
31
+
32
+ ### 创建Web应用
33
+
34
+ ```python
35
+ from websdk2.application import Application
36
+ from websdk2.base_handler import BaseHandler
37
+
38
+ class HelloHandler(BaseHandler):
39
+ def get(self):
40
+ self.write({'code': 0, 'msg': 'Hello CODO!'})
41
+
42
+ handlers = [
43
+ (r'/api/hello/', HelloHandler, {'handle_name': '问候接口', 'method': ['GET']}),
44
+ ]
45
+
46
+ app = Application(handlers)
47
+ app.start_server()
48
+ ```
49
+
50
+ ### 使用数据库
51
+
52
+ ```python
53
+ from websdk2.db_context import DBContextV2 as DBContext
54
+ from sqlalchemy import Column, Integer, String
55
+ from sqlalchemy.ext.declarative import declarative_base
56
+
57
+ Base = declarative_base()
58
+
59
+ class User(Base):
60
+ __tablename__ = 'users'
61
+ id = Column(Integer, primary_key=True)
62
+ username = Column(String(50), unique=True)
63
+
64
+ # 查询
65
+ with DBContext('r') as session:
66
+ user = session.query(User).filter(User.username == 'admin').first()
67
+
68
+ # 创建/更新
69
+ with DBContext('w') as session:
70
+ user = User(username='new_user')
71
+ session.add(user)
72
+ ```
73
+
74
+ ### 使用缓存
75
+
76
+ ```python
77
+ from websdk2.cache_context import cache_conn
78
+
79
+ redis_conn = cache_conn()
80
+ redis_conn.set('key', 'value', ex=3600)
81
+ value = redis_conn.get('key')
82
+ ```
83
+
84
+ ### JWT认证
85
+
86
+ ```python
87
+ from websdk2.jwt_token import AuthToken
88
+
89
+ auth = AuthToken()
90
+
91
+ # 生成Token
92
+ token = auth.encode_auth_token(
93
+ user_id='123',
94
+ username='admin',
95
+ exp_time=7 # 7天过期
96
+ )
97
+
98
+ # 验证Token
99
+ user_info = auth.decode_auth_token(token)
100
+ ```
101
+
102
+ ### 开放 API(AK/SK)
103
+
104
+ 适用于 CI、集成方等编程访问。网关校验 `CODO1-HMAC-SHA256` 签名后签发短时内部 JWT。
105
+
106
+ ```python
107
+ from websdk2.openapi_client import OpenAPIClient
108
+
109
+ client = OpenAPIClient(
110
+ endpoint="https://gw.example.com",
111
+ access_key="your-ak",
112
+ secret_key="your-sk",
113
+ )
114
+ resp = client.request("GET", "/api/p/v4/biz/list/")
115
+ print(resp.status_code, resp.text)
116
+ ```
117
+
118
+ ### 消息队列
119
+
120
+ ```python
121
+ from websdk2.mqhelper import MessageQueueBase
122
+ import json
123
+
124
+ # 发送消息(with语句自动管理连接)
125
+ msg = json.dumps({'task_id': '123', 'status': 'running'})
126
+ with MessageQueueBase('bpm_task_log', 'direct', 'the_log') as mq:
127
+ mq.publish_message(msg)
128
+
129
+ # 消费消息(继承MessageQueueBase)
130
+ class TaskConsumer(MessageQueueBase):
131
+ def __init__(self):
132
+ super().__init__(
133
+ exchange='bpm_task_log',
134
+ exchange_type='direct',
135
+ routing_key='the_log',
136
+ queue_name='task_queue'
137
+ )
138
+
139
+ def on_message(self, body):
140
+ """处理接收到的消息"""
141
+ print(f"收到消息: {body}")
142
+
143
+ # 启动消费
144
+ consumer = TaskConsumer()
145
+ consumer.start_consuming()
146
+ ```
147
+
148
+ ## 配置示例
149
+
150
+ ### 数据库配置
151
+
152
+ ```python
153
+ from websdk2.consts import const
154
+
155
+ db_config = {
156
+ const.DB_CONFIG_ITEM: {
157
+ const.DEFAULT_DB_KEY: {
158
+ const.DBHOST_KEY: 'localhost',
159
+ const.DBPORT_KEY: 3306,
160
+ const.DBUSER_KEY: 'root',
161
+ const.DBPWD_KEY: 'password',
162
+ const.DBNAME_KEY: 'codo_db',
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
168
+ ### Redis配置
169
+
170
+ ```python
171
+ from websdk2.consts import const
172
+ redis_config = {
173
+ const.REDIS_CONFIG_ITEM: {
174
+ const.DEFAULT_RD_KEY: {
175
+ const.RD_HOST_KEY: 'localhost',
176
+ const.RD_PORT_KEY: 6379,
177
+ const.RD_DB_KEY: 0,
178
+ const.RD_PASSWORD_KEY: 'password',
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ ## 项目结构
185
+
186
+ ```
187
+ ops_sdk/
188
+ ├── websdk2/ # Web开发SDK(主模块)
189
+ │ ├── apis/ # CODO平台API集合
190
+ │ │ ├── admin_apis.py # 用户管理API
191
+ │ │ ├── mgv4_apis.py # 后台管理API
192
+ │ │ ├── cmdb_apis.py # CMDB配置API
193
+ │ │ ├── agent_apis.py # Agent代理API
194
+ │ │ ├── task_apis.py # 任务调度API
195
+ │ │ ├── kerrigan_apis.py # 配置文件管理API
196
+ │ │ └── notice_apis.py # 通知告警API
197
+ │ │
198
+ │ ├── cloud/ # 云厂商SDK
199
+ │ │
200
+ │ ├── utils/ # 工具集
201
+ │ │ ├── pydantic_utils.py # Pydantic数据验证
202
+ │ │ ├── date_format.py # 日期格式化
203
+ │ │ └── cc_crypto.py # 加密解密
204
+ │ │
205
+ │ ├── application.py # Tornado应用定制
206
+ │ ├── base_handler.py # 请求处理基类(认证、授权)
207
+ │ ├── db_context.py # 数据库连接管理
208
+ │ ├── cache.py / cache_context.py # Redis缓存管理
209
+ │ ├── crud_utils.py # CRUD工具(分页、验证)
210
+ │ ├── sqlalchemy_pagination.py # ORM分页组件
211
+ │ ├── model_utils.py # 模型转换工具
212
+ │ ├── jwt_token.py # JWT认证
213
+ │ ├── mqhelper.py # RabbitMQ消息队列
214
+ │ ├── client.py # API调用客户端
215
+ │ ├── ldap.py # LDAP认证
216
+ │ ├── configs.py # 配置管理
217
+ │ ├── consts.py # 常量定义
218
+ │ ├── error.py # 自定义异常
219
+ │ ├── logger.py # 日志配置
220
+ │ └── ... # 其他工具模块
221
+
222
+ ├── opssdk/ # 运维SDK(原始模块)
223
+ │ └── utils/ # 运维工具集
224
+
225
+ ├── setup.py # 包配置
226
+ └── pyproject.toml # 项目配置
227
+ ```
228
+
229
+ ## License
230
+
231
+ Everything is [GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.html).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codosdk
3
- Version: 1.0.55
3
+ Version: 1.0.57
4
4
  Summary: CODO项目的Python SDK
5
5
  Home-page: https://github.com/ss1917/codo_sdk/
6
6
  Author: shenshuo
@@ -10,6 +10,8 @@ codosdk.egg-info/requires.txt
10
10
  codosdk.egg-info/top_level.txt
11
11
  opssdk/__init__.py
12
12
  opssdk/utils/__init__.py
13
+ tests/__init__.py
14
+ tests/test_openapi_sign.py
13
15
  websdk2/__init__.py
14
16
  websdk2/api_set.py
15
17
  websdk2/application.py
@@ -29,6 +31,8 @@ websdk2/ldap.py
29
31
  websdk2/logger.py
30
32
  websdk2/model_utils.py
31
33
  websdk2/mqhelper.py
34
+ websdk2/openapi_client.py
35
+ websdk2/openapi_sign.py
32
36
  websdk2/program.py
33
37
  websdk2/salt_api.py
34
38
  websdk2/sqlalchemy_pagination.py
@@ -1,2 +1,3 @@
1
1
  opssdk
2
+ tests
2
3
  websdk2
@@ -2,14 +2,14 @@
2
2
  # -*-coding:utf-8-*-
3
3
  """"
4
4
  author : shenshuo
5
- date : 20241212
5
+ date : 20260729
6
6
  desc : CODO SDK
7
7
  """
8
8
 
9
9
  import sys
10
10
  from setuptools import setup, find_packages
11
11
 
12
- VERSION = '1.0.55'
12
+ VERSION = '1.0.57'
13
13
 
14
14
  if sys.version_info < (2, 7) or (3, 0) <= sys.version_info < (3, 6):
15
15
  print('This program requires at least Python 2.7 or 3.6 to run.')
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ from websdk2.openapi_sign import build_auth_headers, sign
4
+
5
+
6
+ def test_get_vector():
7
+ ak = "codoAKTEST0001"
8
+ sk = "testSecretKeyForOpenAPISign0001"
9
+ ts = "1700000000"
10
+ nonce = "nonce-fixed-001"
11
+ headers = {
12
+ "x-codo-access-key": ak,
13
+ "x-codo-timestamp": ts,
14
+ "x-codo-nonce": nonce,
15
+ }
16
+ sig = sign(sk, "GET", "/api/p/v4/biz/list/", {}, headers, b"", ts)
17
+ assert sig == "ce94e413a8f752ed75a7c129c184f14892d8b24f30222607994276174339e07f"
18
+
19
+
20
+ def test_post_vector():
21
+ ak = "codoAKTEST0001"
22
+ sk = "testSecretKeyForOpenAPISign0001"
23
+ ts = "1700000000"
24
+ nonce = "nonce-fixed-001"
25
+ headers = {
26
+ "content-type": "application/json",
27
+ "x-codo-access-key": ak,
28
+ "x-codo-timestamp": ts,
29
+ "x-codo-nonce": nonce,
30
+ }
31
+ body = b'{"page":1}'
32
+ sig = sign(sk, "POST", "/api/p/v4/user/", {}, headers, body, ts)
33
+ assert sig == "4a2adef69ff58b92bc99c04474811e78082a5e3f5675a3c1d26e19ed3e7dc163"
34
+
35
+
36
+ def test_build_auth_headers_keys():
37
+ h = build_auth_headers(
38
+ "ak", "sk", "GET", "/api/x/", query=None, body=None, timestamp=1700000000, nonce="n1"
39
+ )
40
+ assert "X-Codo-Access-Key" in h
41
+ assert "X-Codo-Signature" in h
File without changes
@@ -0,0 +1,127 @@
1
+ # -*- coding: utf-8 -*-
2
+ """开放 API 客户端:AK/SK 签名请求 CODO 网关。"""
3
+
4
+ import json
5
+ import logging
6
+ from typing import Any, Dict, Optional
7
+ from urllib.parse import urlencode, urlparse
8
+ import requests
9
+ from .configs import configs
10
+ from .consts import const
11
+ from .openapi_sign import build_auth_headers
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class OpenAPIClient:
17
+ def __init__(
18
+ self,
19
+ access_key: str,
20
+ secret_key: str,
21
+ endpoint: str = None,
22
+ request_timeout: int = 10,
23
+ ):
24
+ self.access_key = access_key
25
+ self.secret_key = secret_key
26
+ self.request_timeout = request_timeout
27
+ if endpoint:
28
+ self.endpoint = endpoint.rstrip("/")
29
+ else:
30
+ self.endpoint = (configs.get(const.WEBSITE_API_GW_URL) or "http://gw.opendevops.cn").rstrip(
31
+ "/"
32
+ )
33
+
34
+ def _path_and_query(self, url: str, params: Optional[dict]):
35
+ if url.startswith("http://") or url.startswith("https://"):
36
+ parsed = urlparse(url)
37
+ path = parsed.path or "/"
38
+ # merge query from url
39
+ q = {}
40
+ if parsed.query:
41
+ from urllib.parse import parse_qs
42
+
43
+ for k, vs in parse_qs(parsed.query, keep_blank_values=True).items():
44
+ q[k] = vs[0] if len(vs) == 1 else vs
45
+ if params:
46
+ q.update(params)
47
+ return path, q, f"{parsed.scheme}://{parsed.netloc}"
48
+ path = url if url.startswith("/") else "/" + url
49
+ return path, params or {}, self.endpoint
50
+
51
+ def request(
52
+ self,
53
+ method: str,
54
+ url: str,
55
+ params: dict = None,
56
+ data: Any = None,
57
+ json_body: Any = None,
58
+ headers: dict = None,
59
+ ) -> requests.Response:
60
+ path, query, base = self._path_and_query(url, params)
61
+ method_u = method.upper()
62
+ body_bytes = None
63
+ content_type = None
64
+ if json_body is not None:
65
+ body_bytes = json.dumps(json_body, ensure_ascii=False, separators=(",", ":")).encode(
66
+ "utf-8"
67
+ )
68
+ content_type = "application/json"
69
+ elif data is not None:
70
+ if isinstance(data, (dict, list)):
71
+ body_bytes = json.dumps(data, ensure_ascii=False, separators=(",", ":")).encode(
72
+ "utf-8"
73
+ )
74
+ content_type = "application/json"
75
+ elif isinstance(data, str):
76
+ body_bytes = data.encode("utf-8")
77
+ else:
78
+ body_bytes = data
79
+
80
+ auth_headers = build_auth_headers(
81
+ self.access_key,
82
+ self.secret_key,
83
+ method_u,
84
+ path,
85
+ query=query,
86
+ body=body_bytes,
87
+ content_type=content_type,
88
+ )
89
+ final_headers = dict(headers or {})
90
+ final_headers.update(auth_headers)
91
+
92
+ full_url = f"{base}{path}"
93
+ if query:
94
+ full_url = f"{full_url}?{urlencode(query, doseq=True)}"
95
+
96
+ return requests.request(
97
+ method_u,
98
+ full_url,
99
+ headers=final_headers,
100
+ data=body_bytes,
101
+ timeout=self.request_timeout,
102
+ )
103
+
104
+ def with_params_data_url(self, **kwargs):
105
+ url = kwargs.get("url")
106
+ method = kwargs.get("method") or "GET"
107
+ params = kwargs.get("params")
108
+ body = kwargs.get("body")
109
+ req_json = kwargs.get("json")
110
+ if method.upper() in ("GET", "DELETE", "HEAD"):
111
+ return dict(method=method, url=url, params=params)
112
+ return dict(method=method, url=url, params=params, body=body, json=req_json)
113
+
114
+ def do_action(self, **kwargs) -> str:
115
+ return self.do_action_v2(**kwargs).text
116
+
117
+ def do_action_v2(self, **kwargs) -> requests.Response:
118
+ return self.do_action_v3(**kwargs)
119
+
120
+ def do_action_v3(self, **kwargs) -> requests.Response:
121
+ method = kwargs.get("method") or "GET"
122
+ url = kwargs.get("url")
123
+ params = kwargs.get("params")
124
+ if kwargs.get("json") is not None:
125
+ return self.request(method, url, params=params, json_body=kwargs.get("json"))
126
+ body = kwargs.get("body")
127
+ return self.request(method, url, params=params, data=body)
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """CODO1-HMAC-SHA256 签名(与网关 / Go SDK 对齐)。"""
4
+
5
+ import hashlib
6
+ import hmac
7
+ import secrets
8
+ import time
9
+ from typing import Dict, Mapping, Optional, Union
10
+ from urllib.parse import quote
11
+
12
+
13
+ def sha256_hex(data: Union[bytes, str, None]) -> str:
14
+ if data is None:
15
+ data = b""
16
+ if isinstance(data, str):
17
+ data = data.encode("utf-8")
18
+ return hashlib.sha256(data).hexdigest()
19
+
20
+
21
+ def hmac_sha256_hex(key: str, msg: str) -> str:
22
+ return hmac.new(key.encode("utf-8"), msg.encode("utf-8"), hashlib.sha256).hexdigest()
23
+
24
+
25
+ def canonical_query(params: Optional[Mapping] = None) -> str:
26
+ if not params:
27
+ return ""
28
+ items = []
29
+ for k in sorted(params.keys()):
30
+ v = params[k]
31
+ if isinstance(v, (list, tuple)):
32
+ for vv in sorted(str(x) for x in v):
33
+ items.append(f"{quote(str(k), safe='-_.~')}={quote(str(vv), safe='-_.~')}")
34
+ elif v is None:
35
+ continue
36
+ else:
37
+ items.append(f"{quote(str(k), safe='-_.~')}={quote(str(v), safe='-_.~')}")
38
+ return "&".join(items)
39
+
40
+
41
+ def build_canonical_request(
42
+ method: str,
43
+ uri: str,
44
+ query: Optional[Mapping],
45
+ headers: Mapping[str, str],
46
+ body: Union[bytes, str, None],
47
+ ) -> str:
48
+ if body is None:
49
+ body_b = b""
50
+ elif isinstance(body, str):
51
+ body_b = body.encode("utf-8")
52
+ else:
53
+ body_b = body
54
+ normalized = {str(k).lower(): str(v).strip() for k, v in headers.items()}
55
+ names = sorted(normalized.keys())
56
+ canonical_headers = "".join(f"{n}:{normalized[n]}\n" for n in names)
57
+ signed_headers = ";".join(names)
58
+ return "\n".join(
59
+ [
60
+ method.upper(),
61
+ uri,
62
+ canonical_query(query),
63
+ canonical_headers,
64
+ signed_headers,
65
+ sha256_hex(body_b),
66
+ ]
67
+ )
68
+
69
+
70
+ def build_string_to_sign(timestamp: Union[str, int], canonical_request: str) -> str:
71
+ return "\n".join(
72
+ ["CODO1-HMAC-SHA256", str(timestamp), sha256_hex(canonical_request.encode("utf-8"))]
73
+ )
74
+
75
+
76
+ def sign(
77
+ secret_key: str,
78
+ method: str,
79
+ uri: str,
80
+ query: Optional[Mapping],
81
+ headers: Mapping[str, str],
82
+ body: Union[bytes, str, None],
83
+ timestamp: Union[str, int],
84
+ ) -> str:
85
+ cr = build_canonical_request(method, uri, query, headers, body)
86
+ sts = build_string_to_sign(timestamp, cr)
87
+ return hmac_sha256_hex(secret_key, sts)
88
+
89
+
90
+ def build_auth_headers(
91
+ access_key: str,
92
+ secret_key: str,
93
+ method: str,
94
+ uri: str,
95
+ query: Optional[Mapping] = None,
96
+ body: Union[bytes, str, None] = None,
97
+ content_type: Optional[str] = None,
98
+ timestamp: Optional[Union[str, int]] = None,
99
+ nonce: Optional[str] = None,
100
+ ) -> Dict[str, str]:
101
+ ts = str(timestamp if timestamp is not None else int(time.time()))
102
+ n = nonce or secrets.token_hex(8)
103
+ sign_headers = {
104
+ "x-codo-access-key": access_key,
105
+ "x-codo-timestamp": ts,
106
+ "x-codo-nonce": n,
107
+ }
108
+ body_b = b"" if body is None else (body.encode("utf-8") if isinstance(body, str) else body)
109
+ if content_type and body_b:
110
+ sign_headers["content-type"] = content_type
111
+ signature = sign(secret_key, method, uri, query, sign_headers, body_b, ts)
112
+ out = {
113
+ "X-Codo-Access-Key": access_key,
114
+ "X-Codo-Timestamp": ts,
115
+ "X-Codo-Nonce": n,
116
+ "X-Codo-Signature": signature,
117
+ }
118
+ if content_type:
119
+ out["Content-Type"] = content_type
120
+ return out
@@ -48,6 +48,7 @@ def paginate(query, order_by: str = None, **query_params):
48
48
  else:
49
49
  items = query.all() if page_size >= 200 else query.limit(page_size).offset((page - 1) * page_size).all()
50
50
 
51
- total = query.count()
51
+ # 计算总数:去掉排序避免 filesort
52
+ total = query.order_by(None).count()
52
53
  if not items_not_to_list: items = queryset_to_list(items)
53
54
  return Page(items, page, page_size, total)
codosdk-1.0.55/README.md DELETED
@@ -1,58 +0,0 @@
1
- ## 安装
2
-
3
- ### python3安装
4
-
5
- [python链接](https://www.python.org/)
6
-
7
- ##### python3.9以上
8
-
9
- ##### SDK 安装
10
-
11
- ```bash
12
- $ pip3 install -U git+https://github.com/ss1917/ops_sdk.git
13
- ```
14
-
15
- ## 结构
16
-
17
- ```
18
- ├── README.md 项目readme
19
- └── opssdk
20
- ├── logs 日志模块
21
- ├── install 安装模块
22
- ├── get_info 配置获取
23
- └── operate 运维操作
24
- ├── check 系统参数检查和获取
25
- ├── mysql mysql 操作
26
- ├── mail 发送邮件
27
- └── centralization 集中化管理工具 salt
28
- ├── websdk2 web开发使用
29
- ├── application.py tornado application
30
- ├── base_handler.py tornado 基类
31
- ├── cache.py 处理redis缓存
32
- ├── configs.py 配置文件管理
33
- ├── consts.py 常量
34
- ├── db_context.py MySQL 处理类
35
- ├── error.py 异常
36
- ├── crud_utils.py API CRUD类
37
- ├── model_utils.py 数据库模型处理类 sqlalchemy_pagination
38
- ├── sqlalchemy_pagination.py 分页
39
- ├── fetch_coroutine.py
40
- ├── jwt_token.py JWT处理
41
- ├── mqhelper.py MQ 处理类
42
- ├── program.py
43
- ├── salt_api.py salt 处理类 可以移到工具类
44
- ├── ldap.py LDAP 处理
45
- ├── sms.py 发送短信 可以移到工具类
46
- ├── tools.py 工具类
47
- ├── clent.py API调用客户端封装
48
- └── apis API集合
49
- ├── mgv4_apis 后台API集合
50
- ├── cmdb_apis 配置平台API
51
- ├── agent_apis Agent
52
- ├── kerrigan_apis 配置文件管理
53
- └── notice_apis 待补充
54
- ```
55
-
56
- ## License
57
-
58
- Everything is [GPL v3.0](https://www.gnu.org/licenses/gpl-3.0.html).
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes