cloudoll 2.4.0__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.
- cloudoll/__init__.py +1 -0
- cloudoll/__main__.py +4 -0
- cloudoll/cli.py +76 -0
- cloudoll/error/__init__.py +0 -0
- cloudoll/error/errors.py +48 -0
- cloudoll/logging/__init__.py +188 -0
- cloudoll/mail/__init__.py +0 -0
- cloudoll/mail/smtp.py +197 -0
- cloudoll/orm/__init__.py +252 -0
- cloudoll/orm/base.py +80 -0
- cloudoll/orm/err.py +2 -0
- cloudoll/orm/field.py +522 -0
- cloudoll/orm/model.py +806 -0
- cloudoll/orm/mysql.py +84 -0
- cloudoll/orm/parse.py +139 -0
- cloudoll/orm/postgres.py +70 -0
- cloudoll/robot/__init__.py +0 -0
- cloudoll/robot/dingtalk.py +209 -0
- cloudoll/robot/feishu.py +102 -0
- cloudoll/robot/slack.py +115 -0
- cloudoll/utils/__init__.py +0 -0
- cloudoll/utils/cli_main.py +72 -0
- cloudoll/utils/cli_mian.py +74 -0
- cloudoll/utils/common.py +57 -0
- cloudoll/utils/m2d.py +311 -0
- cloudoll/utils/patch_loop.py +222 -0
- cloudoll/utils/snowflake.py +86 -0
- cloudoll/utils/watch.py +194 -0
- cloudoll/web/__init__.py +53 -0
- cloudoll/web/core.py +557 -0
- cloudoll/web/exception.py +63 -0
- cloudoll/web/html.py +93 -0
- cloudoll/web/http.py +79 -0
- cloudoll/web/jwt.py +40 -0
- cloudoll/web/requests.py +161 -0
- cloudoll/web/settings.py +22 -0
- cloudoll-2.4.0.dist-info/METADATA +414 -0
- cloudoll-2.4.0.dist-info/RECORD +42 -0
- cloudoll-2.4.0.dist-info/WHEEL +6 -0
- cloudoll-2.4.0.dist-info/entry_points.txt +2 -0
- cloudoll-2.4.0.dist-info/licenses/LICENSE +21 -0
- cloudoll-2.4.0.dist-info/top_level.txt +1 -0
cloudoll/web/http.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
__author__ = "chuchur/chuchur.com"
|
|
5
|
+
# from aiohttp.web import middleware
|
|
6
|
+
from curl_cffi import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Client(object):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.session = requests.Session()
|
|
12
|
+
|
|
13
|
+
def requests(self, method, url, **kw) -> requests.Response:
|
|
14
|
+
impersonate = kw.get("impersonate", None)
|
|
15
|
+
if not impersonate:
|
|
16
|
+
kw["impersonate"] = "chrome133a"
|
|
17
|
+
fun = getattr(self.session, method, None)
|
|
18
|
+
result: requests.Response = fun(url, **kw)
|
|
19
|
+
return result
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
http = Client()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get(url, **kw):
|
|
26
|
+
"""
|
|
27
|
+
get 请求
|
|
28
|
+
:params params=dict() 传参
|
|
29
|
+
"""
|
|
30
|
+
return http.requests("get", url, **kw)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def post(url, **kw):
|
|
34
|
+
"""
|
|
35
|
+
post 请求
|
|
36
|
+
:params data=dict() or json=dict() 传参
|
|
37
|
+
"""
|
|
38
|
+
return http.requests("post", url, **kw)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def put(url, **kw):
|
|
42
|
+
"""
|
|
43
|
+
post 请求
|
|
44
|
+
data=dict()传参
|
|
45
|
+
"""
|
|
46
|
+
return http.requests("put", url, **kw)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def delete(url, **kw):
|
|
50
|
+
return http.requests("delete", url, **kw)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def head(url, **kw):
|
|
54
|
+
return http.requests("head", url, **kw)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def option(url, **kw):
|
|
58
|
+
return http.requests("option", url, **kw)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def download(url, savepath=None, **kw):
|
|
62
|
+
"""
|
|
63
|
+
下载文件
|
|
64
|
+
:params url 文件的地址
|
|
65
|
+
:params savepath 保存路径
|
|
66
|
+
"""
|
|
67
|
+
response = http.requests("get", url, **kw)
|
|
68
|
+
if response.status_code == 200:
|
|
69
|
+
# 如果传入了保存路径,保存文件
|
|
70
|
+
if savepath:
|
|
71
|
+
with open(savepath, "wb") as file:
|
|
72
|
+
file.write(response.content)
|
|
73
|
+
print(f"File saved to {savepath}")
|
|
74
|
+
else:
|
|
75
|
+
# 如果没有传入保存路径,返回文件内容
|
|
76
|
+
return response.content
|
|
77
|
+
else:
|
|
78
|
+
print(f"Failed to download the file. Status code: {response.status_code}")
|
|
79
|
+
return None
|
cloudoll/web/jwt.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
__author__ = "chuchur/chuchur.com"
|
|
5
|
+
|
|
6
|
+
import jwt, datetime
|
|
7
|
+
from ..logging import print_error
|
|
8
|
+
from typing import Union
|
|
9
|
+
|
|
10
|
+
def encode(payload, key, exp: Union[int, str] = 3600)->str:
|
|
11
|
+
"""
|
|
12
|
+
jwt 加密
|
|
13
|
+
:params payload
|
|
14
|
+
:params key
|
|
15
|
+
:params exp 过期时间单位秒,默认1个小时
|
|
16
|
+
"""
|
|
17
|
+
headers = dict(typ="jwt", alg="HS256")
|
|
18
|
+
exp = datetime.datetime.now() + datetime.timedelta(seconds=eval(exp) if isinstance(exp, str) else exp) # 过期时间
|
|
19
|
+
payload["exp"] = exp.timestamp()
|
|
20
|
+
result = jwt.encode(payload=payload, key=key, algorithm="HS256", headers=headers)
|
|
21
|
+
return result
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def decode(token, key):
|
|
25
|
+
"""
|
|
26
|
+
jwt
|
|
27
|
+
:params token
|
|
28
|
+
:params key
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
payload = jwt.decode(token, key, algorithms=['HS256'])
|
|
32
|
+
if not payload:
|
|
33
|
+
return None
|
|
34
|
+
now = datetime.datetime.now().timestamp() # 当前时间
|
|
35
|
+
if int(now) > int(payload["exp"]): # 登录时间过期
|
|
36
|
+
return None
|
|
37
|
+
return payload # 返回自定义内容
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print_error(e)
|
|
40
|
+
return None
|
cloudoll/web/requests.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import Optional, Dict, Any, Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AsyncRequest:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
timeout: int = 30,
|
|
10
|
+
max_retries: int = 3,
|
|
11
|
+
retry_delay: float = 1.0,
|
|
12
|
+
session: Optional[aiohttp.ClientSession] = None,
|
|
13
|
+
):
|
|
14
|
+
"""
|
|
15
|
+
异步HTTP请求客户端
|
|
16
|
+
|
|
17
|
+
:param timeout: 请求超时时间(秒)
|
|
18
|
+
:param max_retries: 最大重试次数
|
|
19
|
+
:param retry_delay: 重试延迟(秒)
|
|
20
|
+
:param session: 可复用的aiohttp会话
|
|
21
|
+
"""
|
|
22
|
+
self.timeout = aiohttp.ClientTimeout(total=timeout)
|
|
23
|
+
self.max_retries = max_retries
|
|
24
|
+
self.retry_delay = retry_delay
|
|
25
|
+
self._session = session
|
|
26
|
+
|
|
27
|
+
async def request(
|
|
28
|
+
self,
|
|
29
|
+
method: str,
|
|
30
|
+
url: str,
|
|
31
|
+
params: Optional[Dict[str, Any]] = None,
|
|
32
|
+
data: Optional[Union[Dict[str, Any], str, bytes]] = None,
|
|
33
|
+
json_data: Optional[Any] = None,
|
|
34
|
+
headers: Optional[Dict[str, str]] = None,
|
|
35
|
+
cookies: Optional[Dict[str, str]] = None,
|
|
36
|
+
allow_redirects: bool = True,
|
|
37
|
+
raise_for_status: bool = True,
|
|
38
|
+
) -> aiohttp.ClientResponse:
|
|
39
|
+
"""
|
|
40
|
+
执行异步HTTP请求
|
|
41
|
+
|
|
42
|
+
:param method: HTTP方法 (GET, POST, PUT, DELETE等)
|
|
43
|
+
:param url: 请求URL
|
|
44
|
+
:param params: URL查询参数
|
|
45
|
+
:param data: 请求体数据 (表单或原始数据)
|
|
46
|
+
:param json_data: JSON格式的请求体数据
|
|
47
|
+
:param headers: 请求头
|
|
48
|
+
:param cookies: cookies
|
|
49
|
+
:param allow_redirects: 是否允许重定向
|
|
50
|
+
:param raise_for_status: 是否在HTTP错误状态时抛出异常
|
|
51
|
+
:return: aiohttp.ClientResponse
|
|
52
|
+
"""
|
|
53
|
+
session = self._session or aiohttp.ClientSession(timeout=self.timeout)
|
|
54
|
+
last_exception = None
|
|
55
|
+
|
|
56
|
+
for attempt in range(self.max_retries):
|
|
57
|
+
try:
|
|
58
|
+
async with session.request(
|
|
59
|
+
method=method,
|
|
60
|
+
url=url,
|
|
61
|
+
params=params,
|
|
62
|
+
data=data,
|
|
63
|
+
json=json_data,
|
|
64
|
+
headers=headers,
|
|
65
|
+
cookies=cookies,
|
|
66
|
+
allow_redirects=allow_redirects,
|
|
67
|
+
) as response:
|
|
68
|
+
if raise_for_status:
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
return response
|
|
71
|
+
|
|
72
|
+
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
|
73
|
+
last_exception = e
|
|
74
|
+
if attempt < self.max_retries - 1:
|
|
75
|
+
await asyncio.sleep(self.retry_delay * (attempt + 1))
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
if self._session is None:
|
|
79
|
+
await session.close()
|
|
80
|
+
|
|
81
|
+
raise last_exception or RuntimeError("Request failed")
|
|
82
|
+
|
|
83
|
+
async def get(
|
|
84
|
+
self,
|
|
85
|
+
url: str,
|
|
86
|
+
params: Optional[Dict[str, Any]] = None,
|
|
87
|
+
headers: Optional[Dict[str, str]] = None,
|
|
88
|
+
**kwargs
|
|
89
|
+
) -> aiohttp.ClientResponse:
|
|
90
|
+
"""发送GET请求"""
|
|
91
|
+
return await self.request("GET", url, params=params, headers=headers, **kwargs)
|
|
92
|
+
|
|
93
|
+
async def post(
|
|
94
|
+
self,
|
|
95
|
+
url: str,
|
|
96
|
+
data: Optional[Union[Dict[str, Any], str, bytes]] = None,
|
|
97
|
+
json_data: Optional[Any] = None,
|
|
98
|
+
headers: Optional[Dict[str, str]] = None,
|
|
99
|
+
**kwargs
|
|
100
|
+
) -> aiohttp.ClientResponse:
|
|
101
|
+
"""发送POST请求"""
|
|
102
|
+
return await self.request(
|
|
103
|
+
"POST", url, data=data, json_data=json_data, headers=headers, **kwargs
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def put(
|
|
107
|
+
self,
|
|
108
|
+
url: str,
|
|
109
|
+
data: Optional[Union[Dict[str, Any], str, bytes]] = None,
|
|
110
|
+
json_data: Optional[Any] = None,
|
|
111
|
+
headers: Optional[Dict[str, str]] = None,
|
|
112
|
+
**kwargs
|
|
113
|
+
) -> aiohttp.ClientResponse:
|
|
114
|
+
"""发送PUT请求"""
|
|
115
|
+
return await self.request(
|
|
116
|
+
"PUT", url, data=data, json_data=json_data, headers=headers, **kwargs
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async def delete(
|
|
120
|
+
self, url: str, headers: Optional[Dict[str, str]] = None, **kwargs
|
|
121
|
+
) -> aiohttp.ClientResponse:
|
|
122
|
+
"""发送DELETE请求"""
|
|
123
|
+
return await self.request("DELETE", url, headers=headers, **kwargs)
|
|
124
|
+
|
|
125
|
+
async def close(self) -> None:
|
|
126
|
+
"""关闭客户端会话"""
|
|
127
|
+
if self._session is not None:
|
|
128
|
+
await self._session.close()
|
|
129
|
+
self._session = None
|
|
130
|
+
|
|
131
|
+
async def __aenter__(self):
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
135
|
+
await self.close()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# 使用示例
|
|
139
|
+
"""
|
|
140
|
+
async def test():
|
|
141
|
+
async with AsyncRequest() as client:
|
|
142
|
+
try:
|
|
143
|
+
# GET请求示例
|
|
144
|
+
response = await client.get(
|
|
145
|
+
"https://httpbin.org/get",
|
|
146
|
+
params={"key": "value"},
|
|
147
|
+
headers={"User-Agent": "AsyncRequest/1.0"},
|
|
148
|
+
)
|
|
149
|
+
print(await response.json())
|
|
150
|
+
|
|
151
|
+
# POST请求示例
|
|
152
|
+
response = await client.post(
|
|
153
|
+
"https://httpbin.org/post",
|
|
154
|
+
json_data={"key": "value"},
|
|
155
|
+
headers={"Content-Type": "application/json"},
|
|
156
|
+
)
|
|
157
|
+
print(await response.json())
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
print(f"Request failed: {e}")
|
|
161
|
+
"""
|
cloudoll/web/settings.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
import os
|
|
3
|
+
from ..logging import error, info
|
|
4
|
+
from envyaml import EnvYAML
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_config(env):
|
|
8
|
+
if env is None:
|
|
9
|
+
return {}
|
|
10
|
+
conf_path = os.path.join(os.path.abspath("."), "config", f"conf.{env}.yaml")
|
|
11
|
+
info(f"loading config {conf_path}")
|
|
12
|
+
if not os.path.exists(conf_path):
|
|
13
|
+
error(f"Configuration file does not exist: {conf_path}")
|
|
14
|
+
return {}
|
|
15
|
+
with open(conf_path, "r", encoding="utf-8") as f:
|
|
16
|
+
config = yaml.safe_load(f)
|
|
17
|
+
try:
|
|
18
|
+
config_ori = EnvYAML(conf_path, strict=False)
|
|
19
|
+
config = dict(config_ori)
|
|
20
|
+
except BaseException as e:
|
|
21
|
+
error(e)
|
|
22
|
+
return config or {}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cloudoll
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: 辅助快速创建可分布的微服务。
|
|
5
|
+
Home-page: https://gitee.com/chuchur/cloudoll-py
|
|
6
|
+
Author: chuchur
|
|
7
|
+
Author-email: chuchur@qq.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
+
Requires-Python: >=3.6.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: watchfiles
|
|
19
|
+
Requires-Dist: colorlog
|
|
20
|
+
Requires-Dist: aiomysql
|
|
21
|
+
Requires-Dist: aiopg
|
|
22
|
+
Requires-Dist: aiohttp
|
|
23
|
+
Requires-Dist: redis[hiredis]
|
|
24
|
+
Requires-Dist: aiomcache
|
|
25
|
+
Requires-Dist: jinja2
|
|
26
|
+
Requires-Dist: pyjwt
|
|
27
|
+
Requires-Dist: aiohttp_session[secure]
|
|
28
|
+
Requires-Dist: PyYAML
|
|
29
|
+
Requires-Dist: click
|
|
30
|
+
Requires-Dist: envyaml
|
|
31
|
+
Dynamic: author
|
|
32
|
+
Dynamic: author-email
|
|
33
|
+
Dynamic: classifier
|
|
34
|
+
Dynamic: description
|
|
35
|
+
Dynamic: description-content-type
|
|
36
|
+
Dynamic: home-page
|
|
37
|
+
Dynamic: license
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
Dynamic: requires-dist
|
|
40
|
+
Dynamic: requires-python
|
|
41
|
+
Dynamic: summary
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# cloudoll 云端玩具
|
|
45
|
+
|
|
46
|
+
## 更新日志
|
|
47
|
+
|
|
48
|
+
`2.2.28` 2023-07-04
|
|
49
|
+
- 多数据库支持
|
|
50
|
+
- 优化orm
|
|
51
|
+
-
|
|
52
|
+
`2.0.14` 2023-07-04
|
|
53
|
+
- 加入cli,devtoll
|
|
54
|
+
- 优化orm
|
|
55
|
+
-
|
|
56
|
+
`2.0.13` 2023-07-04
|
|
57
|
+
- 修复已知 问题
|
|
58
|
+
-
|
|
59
|
+
`2.0.12` 2023-07-04
|
|
60
|
+
- 修复已知 问题
|
|
61
|
+
-
|
|
62
|
+
`2.0.11` 2023-07-04
|
|
63
|
+
- 修复已知 问题
|
|
64
|
+
-
|
|
65
|
+
`2.0.10` 2023-07-04
|
|
66
|
+
- 修复已知 问题
|
|
67
|
+
-
|
|
68
|
+
`2.0.9` 2023-07-04
|
|
69
|
+
- 修复已知 问题
|
|
70
|
+
-
|
|
71
|
+
`2.0.8` 2023-07-04
|
|
72
|
+
- 修复已知 问题
|
|
73
|
+
-
|
|
74
|
+
`2.0.7` 2023-07-04
|
|
75
|
+
- 修复logging 问题
|
|
76
|
+
|
|
77
|
+
`2.0.5` 2023-07-04
|
|
78
|
+
- 修复redis在 3.11+环境下的问题
|
|
79
|
+
|
|
80
|
+
`2.0.4` 2023-07-04
|
|
81
|
+
- 切换热更为adev
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
`2.0.2` 2023-07-03
|
|
85
|
+
- 优化一系列问题
|
|
86
|
+
- 可以热加载
|
|
87
|
+
|
|
88
|
+
`2.0.0` 2023-06-09
|
|
89
|
+
- 优化一系列问题
|
|
90
|
+
- orm 执行更优雅
|
|
91
|
+
|
|
92
|
+
`0.1.6` 2022-11-03
|
|
93
|
+
- orm 允许更新为空数据
|
|
94
|
+
- server 文件上传加入大小限制
|
|
95
|
+
- smtp 出错异常处理
|
|
96
|
+
|
|
97
|
+
`0.1.5` 2022-09-19
|
|
98
|
+
- 修复logging level 错误的问题
|
|
99
|
+
- 修正默认依赖
|
|
100
|
+
|
|
101
|
+
`0.1.4` 2022-09-12
|
|
102
|
+
- 优化orm 超时的问题
|
|
103
|
+
- 增加websocket 支持
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## Documentation
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
[Docs](https://cloudoll.chuchur.com)
|
|
110
|
+
|
|
111
|
+
[Docs](https://cloudoll.chuchur.com)
|
|
112
|
+
|
|
113
|
+
[Structure](https://cloudoll.chuchur.com/structure)
|
|
114
|
+
|
|
115
|
+
[Config](https://cloudoll.chuchur.com/config)
|
|
116
|
+
|
|
117
|
+
[Middleware](https://cloudoll.chuchur.com/middleware)
|
|
118
|
+
|
|
119
|
+
[Router](https://cloudoll.chuchur.com/router)
|
|
120
|
+
|
|
121
|
+
[View](https://cloudoll.chuchur.com/view)
|
|
122
|
+
|
|
123
|
+
[Cookie and Session](https://cloudoll.chuchur.com/session-cookie)
|
|
124
|
+
|
|
125
|
+
[Upload file](https://cloudoll.chuchur.com/file-uploads)
|
|
126
|
+
|
|
127
|
+
[WebSocket](https://cloudoll.chuchur.com/websockets)
|
|
128
|
+
|
|
129
|
+
[Mysql](https://cloudoll.chuchur.com/mysql)
|
|
130
|
+
|
|
131
|
+
[Deployment](https://cloudoll.chuchur.com/deployment)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
## 环境准备
|
|
135
|
+
|
|
136
|
+
- 操作系统:支持 macOS,Linux,Windows
|
|
137
|
+
- 运行环境:最低要求 3.6.0。
|
|
138
|
+
|
|
139
|
+
## 快速开始
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
$ mkdir cloudoll-demo && cd cloudoll-demo
|
|
143
|
+
$ pip3 install cloudoll
|
|
144
|
+
$ vi app.py
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`app.py` 内容如下:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
## /app.py
|
|
151
|
+
from cloudoll.web import app
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
app.create().run()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 编写 Controller
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
$ mkdir -p controllers/home
|
|
162
|
+
$ touch controllers/home/__init__.py
|
|
163
|
+
$ vi controllers/home/index.py
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`controllers/home/index.py` 内容如下:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
# /controllers/home/index.py
|
|
170
|
+
from cloudoll.web import get
|
|
171
|
+
|
|
172
|
+
@get('/')
|
|
173
|
+
async def home():
|
|
174
|
+
return {"name": "chuchur" ,"msg": "ok"}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
运行:
|
|
178
|
+
```sh
|
|
179
|
+
$ python3 app.py
|
|
180
|
+
$ open http://localhost:9001
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
在浏览器打开 [http://127.0.0.1:9001/](http://127.0.0.1:9001/)
|
|
184
|
+
|
|
185
|
+
就能看到:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"name": "chuchur" ,
|
|
190
|
+
"msg": "ok" ,
|
|
191
|
+
"timestamp": 1681993906410
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
恭喜, 你已经成功的写好了一个 `Restful API`接口.
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
### 模板渲染
|
|
199
|
+
|
|
200
|
+
绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户。故我们需要引入对应的模板引擎。
|
|
201
|
+
|
|
202
|
+
在本例中,我们使用 [Nunjucks](https://mozilla.github.io/nunjucks/) 来渲染
|
|
203
|
+
```sh
|
|
204
|
+
$ mkdir templates
|
|
205
|
+
$ vi templates/index.html
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`index.html` 内容如下:
|
|
209
|
+
|
|
210
|
+
```html
|
|
211
|
+
<!-- /templates/index.html -->
|
|
212
|
+
|
|
213
|
+
<!DOCTYPE html>
|
|
214
|
+
<html lang="zh">
|
|
215
|
+
<head>
|
|
216
|
+
<meta charset="UTF-8">
|
|
217
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
218
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
219
|
+
<title>Home</title>
|
|
220
|
+
</head>
|
|
221
|
+
<body>
|
|
222
|
+
<p>My name is {{name} }</p>
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
修改 `/controllers/home/index.py` 内容如下:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
# /controllers/home/index.py
|
|
232
|
+
from cloudoll.web import get, render_view
|
|
233
|
+
|
|
234
|
+
@get('/')
|
|
235
|
+
async def home():
|
|
236
|
+
data = {"name": "chuchur" ,"msg": "ok"}
|
|
237
|
+
return render_view("index.html",data)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
这时 页面正常渲染 ,可以看到 `“My name is chuchur”`
|
|
241
|
+
|
|
242
|
+
恭喜, 你已经成功的写好了一个视图页面.
|
|
243
|
+
|
|
244
|
+
### 静态资源
|
|
245
|
+
|
|
246
|
+
我们想在模版里面嵌入静态资源,如图片,js ,css , 这个时候就得用到静态资源. 我们把这些`js` ,`css` ,`image` 都放到 `static` 目录
|
|
247
|
+
|
|
248
|
+
线上环境建议部署到 CDN,或者使用 `nginx` 等相关服务器
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
$ mkdir -p static/img
|
|
252
|
+
$ mkdir -p static/js
|
|
253
|
+
$ mkdir -p static/css
|
|
254
|
+
```
|
|
255
|
+
在 `img`目录 放入在张图 名`logo.png`
|
|
256
|
+
|
|
257
|
+
在 `js` 目录新建 `index.js` ,内容如下:
|
|
258
|
+
|
|
259
|
+
点击页面 弹出 "hello world"
|
|
260
|
+
```js
|
|
261
|
+
// /static/js/index.js
|
|
262
|
+
document.addEventListener('DOMContentLoaded',function(){
|
|
263
|
+
document.body.addEventListener('click',function(){
|
|
264
|
+
alert('hello world.')
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
```
|
|
268
|
+
在 `css` 目录新建 `index.css` ,内容如下:
|
|
269
|
+
```css
|
|
270
|
+
/* /static/css/index.css */
|
|
271
|
+
html,body {
|
|
272
|
+
width: 100%;
|
|
273
|
+
height: 100%;
|
|
274
|
+
color: red;
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
修改视图 `/templates/index.html` ,在 `head` 引入 静态资源, 内容如下:
|
|
279
|
+
|
|
280
|
+
```html
|
|
281
|
+
<!-- /templates/index.html -->
|
|
282
|
+
<!DOCTYPE html>
|
|
283
|
+
<html lang="zh">
|
|
284
|
+
<head>
|
|
285
|
+
<meta charset="UTF-8">
|
|
286
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
287
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
288
|
+
<link rel="icon" type="image/png" href="/static/img/logo.png"/>
|
|
289
|
+
<link rel="stylesheet" href="/static/css/index.css">
|
|
290
|
+
<script src="/static/js/index.js"></script>
|
|
291
|
+
<title>Home</title>
|
|
292
|
+
</head>
|
|
293
|
+
<body>
|
|
294
|
+
<p>My name is {{name} }</p>
|
|
295
|
+
</body>
|
|
296
|
+
</html>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
我们新建一个配置文件, 在配置文件里面配置静态 资源.
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
$ mkdir config
|
|
303
|
+
$ vi config/conf.local.yaml
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
`/config/conf.local.yaml` 内容如下:
|
|
307
|
+
|
|
308
|
+
```yaml
|
|
309
|
+
server:
|
|
310
|
+
static:
|
|
311
|
+
prefix: /static
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
刷新页面之后, 我们所改动即可呈现.
|
|
316
|
+
|
|
317
|
+
### 编写 Middleware
|
|
318
|
+
|
|
319
|
+
假设有个需求:我们的新闻站点,禁止百度爬虫访问。
|
|
320
|
+
|
|
321
|
+
所以可以通过 `Middleware` 判断 User-Agent,如下:
|
|
322
|
+
|
|
323
|
+
```sh
|
|
324
|
+
$ mkdir middlewares
|
|
325
|
+
$ vi middlewares/robot.py
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
修改 `middlewares/robot.py`, 内容如下:
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
# /middlewares/robot.py
|
|
332
|
+
from cloudoll.web import middleware, render_json
|
|
333
|
+
import re
|
|
334
|
+
|
|
335
|
+
@middleware()
|
|
336
|
+
def mid_robot():
|
|
337
|
+
async def robot(request, handler):
|
|
338
|
+
ua = request.headers.get('user-agent')
|
|
339
|
+
if re.match('Baiduspider', ua):
|
|
340
|
+
return render_json(status=403, text="Go away , robot.")
|
|
341
|
+
return await handler(request)
|
|
342
|
+
|
|
343
|
+
return robot
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
重新启动之后, 现在可以使用 `curl http://localhost:7001/news -A "Baiduspider"` 看看效果。
|
|
347
|
+
|
|
348
|
+
更多参见中间件文档。
|
|
349
|
+
|
|
350
|
+
### 配置文件
|
|
351
|
+
写业务的时候,不可避免的需要有配置文件,使用代码管理配置,在代码中添加多个环境的配置,在启动时传入当前环境的参数即可.
|
|
352
|
+
|
|
353
|
+
cloudoll 支持根据环境来加载配置,定义多个环境的配置文件
|
|
354
|
+
|
|
355
|
+
```ini
|
|
356
|
+
config
|
|
357
|
+
|- conf.local.yaml
|
|
358
|
+
|- conf.prod.yaml
|
|
359
|
+
`- conf.test.yaml
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
我们创建配置文件:
|
|
363
|
+
|
|
364
|
+
```sh
|
|
365
|
+
$ mkdir -p config/conf.local.yaml
|
|
366
|
+
$ vi config/conf.local.yaml
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
如下是 mysql 和 server 的配置:
|
|
370
|
+
|
|
371
|
+
```yaml
|
|
372
|
+
server:
|
|
373
|
+
host: 192.168.0.1
|
|
374
|
+
port: 9001
|
|
375
|
+
static: false
|
|
376
|
+
client_max_size: 1024000
|
|
377
|
+
static:
|
|
378
|
+
prefix: /static
|
|
379
|
+
show_index: true
|
|
380
|
+
append_version: true
|
|
381
|
+
follow_symlinks: true
|
|
382
|
+
database:
|
|
383
|
+
mysql:
|
|
384
|
+
host: 127.0.0.1
|
|
385
|
+
port: 3306
|
|
386
|
+
user: root
|
|
387
|
+
password: abcd
|
|
388
|
+
db: blog
|
|
389
|
+
charset: utf8mb4
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
默认开发会使用默认的`local`作为配置。 启动时 通过 `env` 加载对应的配置。 如 `python3 app.py --env=prod` 会加载 `conf.prod.yaml`
|
|
393
|
+
|
|
394
|
+
# cli
|
|
395
|
+
|
|
396
|
+
## 生成模型
|
|
397
|
+
|
|
398
|
+
从数据库导出 `users` 表模型
|
|
399
|
+
```sh
|
|
400
|
+
cloudoll gen -t users
|
|
401
|
+
```
|
|
402
|
+
更多参数 :
|
|
403
|
+
- -p (--path) 导出的模型路径
|
|
404
|
+
- -c (--create) 值 model(默认) 生成模型, 值table 建表
|
|
405
|
+
- -t (--table) 要生的模型或要建的表名,以`,`分开, `ALL` 所有表
|
|
406
|
+
- -env (--environment) 读取配置名 ,值 local(默认) / test / prod
|
|
407
|
+
- -db (--database) 数据库实例名,取决于配置文件,如果有多个数据库
|
|
408
|
+
- -h (--help) 帮助
|
|
409
|
+
|
|
410
|
+
## 开发调试
|
|
411
|
+
|
|
412
|
+
```sh
|
|
413
|
+
cloudoll start
|
|
414
|
+
```
|