cloudoll 2.2.38__tar.gz → 2.3.0__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.
- {cloudoll-2.2.38 → cloudoll-2.3.0}/PKG-INFO +13 -2
- cloudoll-2.3.0/cloudoll/__init__.py +1 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/cli.py +1 -1
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/logging/__init__.py +2 -2
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/model.py +3 -3
- cloudoll-2.2.38/cloudoll/utils/cli_mian.py → cloudoll-2.3.0/cloudoll/utils/cli_main.py +2 -4
- cloudoll-2.3.0/cloudoll/utils/common.py +51 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/watch.py +14 -23
- cloudoll-2.3.0/cloudoll/web/__init__.py +51 -0
- cloudoll-2.2.38/cloudoll/web/__init__.py → cloudoll-2.3.0/cloudoll/web/core.py +134 -199
- cloudoll-2.3.0/cloudoll/web/http.py +79 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/web/settings.py +4 -4
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/PKG-INFO +13 -2
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/SOURCES.txt +2 -1
- {cloudoll-2.2.38 → cloudoll-2.3.0}/setup.py +0 -3
- cloudoll-2.2.38/cloudoll/__init__.py +0 -1
- cloudoll-2.2.38/cloudoll/utils/common.py +0 -29
- cloudoll-2.2.38/cloudoll/web/http.py +0 -139
- {cloudoll-2.2.38 → cloudoll-2.3.0}/LICENSE +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/README.md +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/__main__.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/error/__init__.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/error/errors.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/mail/__init__.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/mail/smtp.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/__init__.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/base.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/err.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/field.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/mysql.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/parse.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/postgres.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/robot/__init__.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/robot/dingtalk.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/robot/feishu.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/robot/slack.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/__init__.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/m2d.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/patch_loop.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/snowflake.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/web/exception.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/web/html.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/web/jwt.py +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/dependency_links.txt +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/entry_points.txt +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/requires.txt +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/top_level.txt +0 -0
- {cloudoll-2.2.38 → cloudoll-2.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudoll
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: 辅助快速创建可分布的微服务。
|
|
5
5
|
Home-page: https://gitee.com/chuchur/cloudoll-py
|
|
6
6
|
Author: chuchur
|
|
@@ -29,6 +29,17 @@ Requires-Dist: aiohttp_session[secure]
|
|
|
29
29
|
Requires-Dist: PyYAML
|
|
30
30
|
Requires-Dist: click
|
|
31
31
|
Requires-Dist: envyaml
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: author-email
|
|
34
|
+
Dynamic: classifier
|
|
35
|
+
Dynamic: description
|
|
36
|
+
Dynamic: description-content-type
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: license
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
Dynamic: requires-dist
|
|
41
|
+
Dynamic: requires-python
|
|
42
|
+
Dynamic: summary
|
|
32
43
|
|
|
33
44
|
|
|
34
45
|
# cloudoll 云端玩具
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.3.0"
|
|
@@ -42,10 +42,10 @@ class ColorAnsi:
|
|
|
42
42
|
_FORMATS = {
|
|
43
43
|
# 终端输出格式
|
|
44
44
|
# "console_format": "%(log_color)s[%(levelname)s]-%(asctime)s-%(pathname)s-%(module)s-%(funcName)s-[line:%(lineno)d]: %(message)s",
|
|
45
|
-
"console_format": "%(log_color)s%(asctime)s %(levelname)s %(
|
|
45
|
+
"console_format": "%(log_color)s%(asctime)s %(levelname)s %(message)s",
|
|
46
46
|
# 日志输出格式
|
|
47
47
|
# "log_format": "[%(levelname)s]-%(asctime)s-%(pathname)s-%(module)s-%(funcName)s-[line:%(lineno)d]: %(message)s",
|
|
48
|
-
"log_format": "%(asctime)s %(levelname)s %(
|
|
48
|
+
"log_format": "%(asctime)s %(levelname)s %(message)s",
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
_LOG_SIZE = 1024 * 1024 * 1 # 1MB 日志最大1MB
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .field import Field, Function, Expression
|
|
2
|
-
from ..logging import
|
|
2
|
+
from ..logging import warning
|
|
3
3
|
from functools import reduce
|
|
4
4
|
import operator
|
|
5
5
|
import copy
|
|
@@ -35,13 +35,13 @@ class ModelMetaclass(type):
|
|
|
35
35
|
# mappings[k] = v
|
|
36
36
|
if v.primary_key:
|
|
37
37
|
if primary_key:
|
|
38
|
-
|
|
38
|
+
warning(f"Duplicate primary key for {table_name}")
|
|
39
39
|
primary_key = k
|
|
40
40
|
# else:
|
|
41
41
|
fields.append(k)
|
|
42
42
|
|
|
43
43
|
if not primary_key:
|
|
44
|
-
|
|
44
|
+
warning(f"{table_name} Missing primary key")
|
|
45
45
|
|
|
46
46
|
# for k in mappings.keys():
|
|
47
47
|
# attrs.pop(k)
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
from typing import Any
|
|
4
2
|
from ..web.settings import get_config
|
|
5
3
|
from ..logging import print_info
|
|
6
4
|
from .common import Object, chainMap
|
|
7
|
-
from
|
|
5
|
+
from .watch import AppTask
|
|
8
6
|
from aiohttp import web
|
|
9
7
|
from ..web import app
|
|
10
8
|
from pathlib import Path
|
|
11
|
-
from
|
|
9
|
+
from .m2d import create_models, create_tables
|
|
12
10
|
from ..orm import create_engine
|
|
13
11
|
import os
|
|
14
12
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
class Object(dict):
|
|
3
|
+
# __setattr__ = dict.__setitem__
|
|
4
|
+
# __getattr__ = dict.__getitem__
|
|
5
|
+
def __setattr__(self, key, value):
|
|
6
|
+
self[key] = value
|
|
7
|
+
|
|
8
|
+
def __getattr__(self, key):
|
|
9
|
+
return self.get(key, None)
|
|
10
|
+
|
|
11
|
+
def __delattr__(self, key):
|
|
12
|
+
try:
|
|
13
|
+
del self[key]
|
|
14
|
+
except KeyError:
|
|
15
|
+
raise AttributeError(f"'DotDict' object has no attribute '{key}'")
|
|
16
|
+
|
|
17
|
+
def __getstate__(self):
|
|
18
|
+
'''to fix pickle.dumps '''
|
|
19
|
+
return dict(self)
|
|
20
|
+
|
|
21
|
+
def __setstate__(self, state):
|
|
22
|
+
self.update(state)
|
|
23
|
+
|
|
24
|
+
def chainMap(*dicts):
|
|
25
|
+
merged_dict = Object()
|
|
26
|
+
for d in dicts:
|
|
27
|
+
for key, value in d.items():
|
|
28
|
+
if key not in merged_dict or value is not None:
|
|
29
|
+
merged_dict[key] = value
|
|
30
|
+
return merged_dict
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def check_port_open(port: int, delay: float = 1) -> None:
|
|
34
|
+
loop = asyncio.get_running_loop()
|
|
35
|
+
# the "s = socket.socket; s.bind" approach sometimes says a port is in use when it's not
|
|
36
|
+
# this approach replicates aiohttp so should always give the same answer
|
|
37
|
+
for i in range(5, 0, -1):
|
|
38
|
+
try:
|
|
39
|
+
server = await loop.create_server(
|
|
40
|
+
asyncio.Protocol, host="0.0.0.0", port=port
|
|
41
|
+
)
|
|
42
|
+
except OSError as e:
|
|
43
|
+
if e.errno != EADDRINUSE:
|
|
44
|
+
raise
|
|
45
|
+
warning("port %d is already in use, waiting %d...", port, i)
|
|
46
|
+
await asyncio.sleep(delay)
|
|
47
|
+
else:
|
|
48
|
+
server.close()
|
|
49
|
+
await server.wait_closed()
|
|
50
|
+
return
|
|
51
|
+
raise Exception("The port {} is already is use".format(port))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import AsyncIterator, Optional, Union
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from ..web import Application, app
|
|
3
|
+
from ..web import Application, app
|
|
4
4
|
import asyncio
|
|
5
5
|
import os
|
|
6
6
|
import signal
|
|
@@ -8,13 +8,13 @@ import sys
|
|
|
8
8
|
from contextlib import suppress
|
|
9
9
|
from multiprocessing import Process
|
|
10
10
|
from watchfiles import awatch, DefaultFilter
|
|
11
|
-
from ..logging import
|
|
11
|
+
from ..logging import info, warning, error
|
|
12
12
|
import contextlib
|
|
13
13
|
from typing import Iterator, Optional
|
|
14
14
|
from typing import Optional, Union
|
|
15
15
|
from aiohttp import web
|
|
16
16
|
import traceback
|
|
17
|
-
|
|
17
|
+
from .common import check_port_open
|
|
18
18
|
|
|
19
19
|
class CloudollFilter(DefaultFilter):
|
|
20
20
|
def __init__(self, ignore_dirs: tuple = None) -> None:
|
|
@@ -90,14 +90,10 @@ def mian_app(tty_path, config, entry, env):
|
|
|
90
90
|
loop = asyncio.new_event_loop()
|
|
91
91
|
runner = loop.run_until_complete(create_main_app(config, entry, env))
|
|
92
92
|
try:
|
|
93
|
-
loop.run_until_complete(
|
|
94
|
-
start_main_app(runner, config["server"]["port"])
|
|
95
|
-
)
|
|
93
|
+
loop.run_until_complete(start_main_app(runner, config["server"]["port"]))
|
|
96
94
|
loop.run_forever()
|
|
97
95
|
except KeyboardInterrupt:
|
|
98
96
|
pass
|
|
99
|
-
except Exception as e:
|
|
100
|
-
traceback.print_exc()
|
|
101
97
|
finally:
|
|
102
98
|
with suppress(asyncio.TimeoutError, KeyboardInterrupt):
|
|
103
99
|
loop.run_until_complete(runner.cleanup())
|
|
@@ -112,6 +108,8 @@ async def create_main_app(config, entry, env):
|
|
|
112
108
|
async def start_main_app(runner, port):
|
|
113
109
|
await runner.setup()
|
|
114
110
|
site = web.TCPSite(runner, host="0.0.0.0", port=port)
|
|
111
|
+
info(f"Server running on http://0.0.0.0:{port}")
|
|
112
|
+
info("(Press CTRL+C to quit)")
|
|
115
113
|
await site.start()
|
|
116
114
|
|
|
117
115
|
|
|
@@ -131,7 +129,7 @@ class AppTask(WatchTask):
|
|
|
131
129
|
async for changes in self._awatch:
|
|
132
130
|
self._reloads += 1
|
|
133
131
|
if any(f.endswith(".py") for _, f in changes):
|
|
134
|
-
|
|
132
|
+
info(f"{len(changes)} changes, restarting server")
|
|
135
133
|
await self._stop_dev_server()
|
|
136
134
|
self._start_dev_server()
|
|
137
135
|
await asyncio.sleep(1)
|
|
@@ -142,8 +140,7 @@ class AppTask(WatchTask):
|
|
|
142
140
|
|
|
143
141
|
def _start_dev_server(self) -> None:
|
|
144
142
|
act = "Start" if self._reloads == 0 else "Restart"
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
info(f"{act}ing dev server")
|
|
147
144
|
|
|
148
145
|
try:
|
|
149
146
|
tty_path = os.ttyname(sys.stdin.fileno())
|
|
@@ -171,29 +168,23 @@ class AppTask(WatchTask):
|
|
|
171
168
|
)
|
|
172
169
|
self._process.start()
|
|
173
170
|
except Exception as e:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
print_info(
|
|
177
|
-
f"Server Running on http://{self._config['server']['host']}:{self._config['server']['port']}"
|
|
178
|
-
)
|
|
179
|
-
print_info("(Press CTRL+C to quit)")
|
|
180
|
-
|
|
171
|
+
error(f"Error starting server: {e}")
|
|
181
172
|
|
|
182
173
|
async def _stop_dev_server(self) -> None:
|
|
183
174
|
if self._process.is_alive():
|
|
184
|
-
|
|
175
|
+
info("stopping server process...")
|
|
185
176
|
if self._process.pid:
|
|
186
|
-
|
|
177
|
+
info("sending SIGINT")
|
|
187
178
|
os.kill(self._process.pid, signal.SIGINT)
|
|
188
179
|
self._process.join(5)
|
|
189
180
|
if self._process.exitcode is None:
|
|
190
|
-
|
|
181
|
+
warning("process has not terminated, sending SIGKILL")
|
|
191
182
|
self._process.kill()
|
|
192
183
|
self._process.join(1)
|
|
193
184
|
else:
|
|
194
|
-
|
|
185
|
+
info("process stopped")
|
|
195
186
|
else:
|
|
196
|
-
|
|
187
|
+
warning(
|
|
197
188
|
"server process already dead, exit code: %s", self._process.exitcode
|
|
198
189
|
)
|
|
199
190
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
__author__ = "chuchur/chuchur.com"
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
|
|
7
|
+
from .settings import get_config
|
|
8
|
+
from . import jwt
|
|
9
|
+
from .core import (
|
|
10
|
+
Application,
|
|
11
|
+
app,
|
|
12
|
+
Response,
|
|
13
|
+
WebSocketResponse,
|
|
14
|
+
WebSocket,
|
|
15
|
+
WebStream,
|
|
16
|
+
WSMsgType,
|
|
17
|
+
View,
|
|
18
|
+
routes,
|
|
19
|
+
get,
|
|
20
|
+
put,
|
|
21
|
+
delete,
|
|
22
|
+
post,
|
|
23
|
+
render_json,
|
|
24
|
+
render_error,
|
|
25
|
+
render_view,
|
|
26
|
+
middleware,
|
|
27
|
+
redirect,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__: Tuple[str, ...] = (
|
|
31
|
+
"Application",
|
|
32
|
+
"app",
|
|
33
|
+
"WebSocket",
|
|
34
|
+
"Response",
|
|
35
|
+
"WebSocketResponse",
|
|
36
|
+
"WebStream",
|
|
37
|
+
"WSMsgType",
|
|
38
|
+
"View",
|
|
39
|
+
"routes",
|
|
40
|
+
"get",
|
|
41
|
+
"put",
|
|
42
|
+
"delete",
|
|
43
|
+
"post",
|
|
44
|
+
"render_json",
|
|
45
|
+
"render_error",
|
|
46
|
+
"render_view",
|
|
47
|
+
"middleware",
|
|
48
|
+
"redirect",
|
|
49
|
+
"jwt",
|
|
50
|
+
"get_config",
|
|
51
|
+
)
|
|
@@ -27,7 +27,7 @@ from aiohttp_session import (
|
|
|
27
27
|
from .settings import get_config
|
|
28
28
|
import aiomcache
|
|
29
29
|
from redis import asyncio as aioredis
|
|
30
|
-
from ..logging import
|
|
30
|
+
from ..logging import warning, info
|
|
31
31
|
from ..orm.model import Model
|
|
32
32
|
from . import jwt
|
|
33
33
|
from decimal import Decimal
|
|
@@ -35,80 +35,19 @@ from datetime import datetime, date
|
|
|
35
35
|
from ..utils.common import chainMap, Object
|
|
36
36
|
from ..orm import create_engine, parse_coon
|
|
37
37
|
import uuid
|
|
38
|
-
|
|
39
|
-
# from ..orm.mysql import Mysql
|
|
40
38
|
from typing import Optional, Iterable, Callable, Awaitable
|
|
41
39
|
|
|
42
|
-
__ALL__ = (
|
|
43
|
-
"app",
|
|
44
|
-
"WebSocket",
|
|
45
|
-
"Response",
|
|
46
|
-
"WebStream",
|
|
47
|
-
"WSMsgType",
|
|
48
|
-
"View",
|
|
49
|
-
"routes" "get",
|
|
50
|
-
"put",
|
|
51
|
-
"delete",
|
|
52
|
-
"post",
|
|
53
|
-
"render_json",
|
|
54
|
-
"render_error",
|
|
55
|
-
"render_view",
|
|
56
|
-
"middleware",
|
|
57
|
-
"redirect",
|
|
58
|
-
"jwt",
|
|
59
|
-
)
|
|
60
|
-
|
|
61
40
|
|
|
62
41
|
class RequestHandler(object):
|
|
63
42
|
def __init__(self, fn):
|
|
64
43
|
self.fn = fn
|
|
65
44
|
|
|
66
45
|
async def __call__(self, request):
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
props = inspect.getfullargspec(self.fn)
|
|
70
|
-
if len(props.args) == 2 and content_type == "multipart/form-data":
|
|
71
|
-
await set_session_route(request)
|
|
72
|
-
multipart = await request.multipart()
|
|
73
|
-
field = await multipart.next()
|
|
74
|
-
result = await self.fn(request, field)
|
|
75
|
-
elif len(props.args) == 1:
|
|
76
|
-
await set_session_route(request)
|
|
77
|
-
if content_type == "multipart/form-data":
|
|
78
|
-
data = await request.post()
|
|
79
|
-
elif content_type == "application/json":
|
|
80
|
-
data = await request.json()
|
|
81
|
-
else:
|
|
82
|
-
data = await request.post()
|
|
83
|
-
query_string = request.query_string
|
|
84
|
-
body = {}
|
|
85
|
-
for k in data:
|
|
86
|
-
body[k] = data[k]
|
|
87
|
-
qs = {}
|
|
88
|
-
if query_string:
|
|
89
|
-
for k, v in parse.parse_qs(query_string, True).items():
|
|
90
|
-
qs[k] = v[0]
|
|
91
|
-
request.qs = Object(qs)
|
|
92
|
-
request.body = Object(body)
|
|
93
|
-
result = await self.fn(request)
|
|
94
|
-
else:
|
|
95
|
-
result = await self.fn()
|
|
96
|
-
try:
|
|
97
|
-
if isinstance(result, Response):
|
|
98
|
-
return result
|
|
99
|
-
if isinstance(result, StreamResponse):
|
|
100
|
-
return result
|
|
101
|
-
if isinstance(result, WebSocketResponse): # maybe catch error
|
|
102
|
-
return result
|
|
103
|
-
if "content_type" in result and "text/html" in result["content_type"]:
|
|
104
|
-
return result
|
|
105
|
-
except:
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
|
-
return render_json(result)
|
|
46
|
+
info(f"{request.method} {request.path}")
|
|
47
|
+
return await _render_result(request, self.fn)
|
|
109
48
|
|
|
110
49
|
|
|
111
|
-
async def
|
|
50
|
+
async def _set_session_route(request):
|
|
112
51
|
params = dict()
|
|
113
52
|
# match
|
|
114
53
|
rt = request.match_info
|
|
@@ -120,39 +59,17 @@ async def set_session_route(request):
|
|
|
120
59
|
request.session = session
|
|
121
60
|
|
|
122
61
|
|
|
123
|
-
|
|
124
|
-
loop = asyncio.get_running_loop()
|
|
125
|
-
# the "s = socket.socket; s.bind" approach sometimes says a port is in use when it's not
|
|
126
|
-
# this approach replicates aiohttp so should always give the same answer
|
|
127
|
-
for i in range(5, 0, -1):
|
|
128
|
-
try:
|
|
129
|
-
server = await loop.create_server(
|
|
130
|
-
asyncio.Protocol, host="0.0.0.0", port=port
|
|
131
|
-
)
|
|
132
|
-
except OSError as e:
|
|
133
|
-
if e.errno != EADDRINUSE:
|
|
134
|
-
raise
|
|
135
|
-
print_warn("port %d is already in use, waiting %d...", port, i)
|
|
136
|
-
await asyncio.sleep(delay)
|
|
137
|
-
else:
|
|
138
|
-
server.close()
|
|
139
|
-
await server.wait_closed()
|
|
140
|
-
return
|
|
141
|
-
raise Exception("The port {} is already is use".format(port))
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def auto_reg_module(module_dir: str):
|
|
145
|
-
|
|
62
|
+
def _auto_reg_module(module_dir: str):
|
|
146
63
|
module_path = os.path.join(os.path.abspath("."), module_dir)
|
|
147
64
|
if not os.path.exists(module_path):
|
|
148
|
-
|
|
65
|
+
info(f"{module_dir} not detected")
|
|
149
66
|
return
|
|
150
67
|
for filename in os.listdir(module_dir):
|
|
151
68
|
if filename.startswith("__") or filename.startswith("."):
|
|
152
69
|
continue
|
|
153
70
|
full_path = os.path.join(os.path.abspath("."), module_dir, filename)
|
|
154
71
|
if os.path.isdir(full_path):
|
|
155
|
-
|
|
72
|
+
_auto_reg_module(module_dir + os.sep + filename)
|
|
156
73
|
elif filename.endswith(".py"):
|
|
157
74
|
module_name = (
|
|
158
75
|
module_dir.replace(os.sep, ".")
|
|
@@ -162,10 +79,75 @@ def auto_reg_module(module_dir: str):
|
|
|
162
79
|
importlib.import_module(module_name)
|
|
163
80
|
|
|
164
81
|
|
|
165
|
-
def
|
|
82
|
+
async def _render_result(request, func):
|
|
83
|
+
content_type = request.content_type
|
|
84
|
+
# 获取函数参数的名称和默认值
|
|
85
|
+
props = inspect.getfullargspec(func)
|
|
86
|
+
args = list(props.args)
|
|
87
|
+
if "self" in args:
|
|
88
|
+
args.remove("self")
|
|
89
|
+
|
|
90
|
+
if len(args) == 2 and content_type == "multipart/form-data":
|
|
91
|
+
await _set_session_route(request)
|
|
92
|
+
multipart = await request.multipart()
|
|
93
|
+
field = await multipart.next()
|
|
94
|
+
result = await func(request, field)
|
|
95
|
+
elif len(args) == 1:
|
|
96
|
+
await _set_session_route(request)
|
|
97
|
+
if content_type == "multipart/form-data":
|
|
98
|
+
data = await request.post()
|
|
99
|
+
elif content_type == "application/json":
|
|
100
|
+
data = await request.json()
|
|
101
|
+
else:
|
|
102
|
+
data = await request.post()
|
|
103
|
+
query_string = request.query_string
|
|
104
|
+
body = {}
|
|
105
|
+
for k in data:
|
|
106
|
+
body[k] = data[k]
|
|
107
|
+
qs = {}
|
|
108
|
+
if query_string:
|
|
109
|
+
for k, v in parse.parse_qs(query_string, True).items():
|
|
110
|
+
qs[k] = v[0]
|
|
111
|
+
request.qs = Object(qs)
|
|
112
|
+
request.body = Object(body)
|
|
113
|
+
result = await func(request)
|
|
114
|
+
else:
|
|
115
|
+
result = await func()
|
|
116
|
+
try:
|
|
117
|
+
if isinstance(result, Response):
|
|
118
|
+
return result
|
|
119
|
+
if isinstance(result, StreamResponse):
|
|
120
|
+
return result
|
|
121
|
+
if isinstance(result, WebSocketResponse): # maybe catch error
|
|
122
|
+
return result
|
|
123
|
+
if "content_type" in result and "text/html" in result["content_type"]:
|
|
124
|
+
return result
|
|
125
|
+
except:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
return render_json(result)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _sa_ignore_hash(method, path):
|
|
132
|
+
md5 = hashlib.md5()
|
|
133
|
+
hashstr = f"{method}{path}"
|
|
134
|
+
md5.update(hashstr.encode("utf-8"))
|
|
135
|
+
return md5.hexdigest()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _parse_int(num):
|
|
166
139
|
return eval(num) if isinstance(num, str) else num
|
|
167
140
|
|
|
168
141
|
|
|
142
|
+
def _sa_ignore_middleware():
|
|
143
|
+
async def set_ignore(ctx, handler):
|
|
144
|
+
hashstr = _sa_ignore_hash(ctx.method, ctx.path)
|
|
145
|
+
ctx.is_sa_ignore = hashstr in ctx.app.ignore_paths
|
|
146
|
+
return await handler(ctx)
|
|
147
|
+
|
|
148
|
+
return set_ignore
|
|
149
|
+
|
|
150
|
+
|
|
169
151
|
class Application(object):
|
|
170
152
|
def __init__(self):
|
|
171
153
|
self._loop = None
|
|
@@ -175,7 +157,6 @@ class Application(object):
|
|
|
175
157
|
self._middleware = []
|
|
176
158
|
self.config = {}
|
|
177
159
|
self.clean_up = False
|
|
178
|
-
# self.args = Object()
|
|
179
160
|
|
|
180
161
|
def _load_life_cycle(self, entry_model=None):
|
|
181
162
|
try:
|
|
@@ -189,9 +170,9 @@ class Application(object):
|
|
|
189
170
|
cy = getattr(self, cycle)
|
|
190
171
|
cy.append(getattr(entry, cycle))
|
|
191
172
|
except ImportError:
|
|
192
|
-
|
|
173
|
+
warning(f"Entry model:{entry_model} can not find.")
|
|
193
174
|
|
|
194
|
-
def
|
|
175
|
+
def _init_parse(self):
|
|
195
176
|
try:
|
|
196
177
|
parser = argparse.ArgumentParser(description="Cloudapp parse")
|
|
197
178
|
|
|
@@ -209,17 +190,17 @@ class Application(object):
|
|
|
209
190
|
if loop is None:
|
|
210
191
|
loop = asyncio.new_event_loop()
|
|
211
192
|
self._loop = loop
|
|
212
|
-
# if env is None:
|
|
213
|
-
# env = self.args.env
|
|
214
193
|
if not config:
|
|
215
194
|
config = get_config(env or "local")
|
|
216
|
-
# print(config)
|
|
217
|
-
# self._args = args
|
|
218
195
|
self.config = config
|
|
219
196
|
|
|
197
|
+
sa_ignore_mid = _sa_ignore_middleware()
|
|
198
|
+
sa_ignore_mid.__middleware_version__ = 1
|
|
199
|
+
self._middleware.append(sa_ignore_mid)
|
|
200
|
+
|
|
220
201
|
# middlewares
|
|
221
|
-
|
|
222
|
-
|
|
202
|
+
_auto_reg_module("middlewares")
|
|
203
|
+
info("auto register middlewares")
|
|
223
204
|
|
|
224
205
|
conf_server = config.get("server")
|
|
225
206
|
client_max_size = 1024**2 * 2
|
|
@@ -229,9 +210,11 @@ class Application(object):
|
|
|
229
210
|
logger=None,
|
|
230
211
|
loop=loop,
|
|
231
212
|
middlewares=self._middleware,
|
|
232
|
-
client_max_size=
|
|
213
|
+
client_max_size=_parse_int(client_max_size),
|
|
233
214
|
)
|
|
215
|
+
|
|
234
216
|
# database
|
|
217
|
+
self.app.ignore_paths = set()
|
|
235
218
|
self.app.db = Object()
|
|
236
219
|
self.app.on_startup.append(self._init_database)
|
|
237
220
|
self.app.on_cleanup.append(self._close_database)
|
|
@@ -241,11 +224,10 @@ class Application(object):
|
|
|
241
224
|
self.app.jwt_decode = self.jwt_decode
|
|
242
225
|
# session
|
|
243
226
|
self.app.on_startup.append(self._init_session)
|
|
244
|
-
#
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
227
|
+
# router:
|
|
228
|
+
_auto_reg_module("controllers")
|
|
229
|
+
info("auto register controllers")
|
|
230
|
+
|
|
249
231
|
self.app.add_routes(self._route_table)
|
|
250
232
|
|
|
251
233
|
# load life
|
|
@@ -258,7 +240,7 @@ class Application(object):
|
|
|
258
240
|
if conf_st:
|
|
259
241
|
temp = os.path.join(os.path.abspath("."), "static")
|
|
260
242
|
self.app.router.add_static(**conf_st, path=temp)
|
|
261
|
-
|
|
243
|
+
info("Suggest using nginx or others instead.")
|
|
262
244
|
|
|
263
245
|
temp = os.path.join(os.path.abspath("."), "templates")
|
|
264
246
|
if os.path.exists(temp):
|
|
@@ -280,15 +262,15 @@ class Application(object):
|
|
|
280
262
|
self.clean_up = True
|
|
281
263
|
|
|
282
264
|
for db in apps.db:
|
|
283
|
-
|
|
265
|
+
info(f"release database {db}.")
|
|
284
266
|
await apps.db[db].close()
|
|
285
267
|
|
|
286
268
|
# close for session
|
|
287
269
|
if "redis" in apps:
|
|
288
|
-
|
|
270
|
+
info(f"release redis session.")
|
|
289
271
|
await apps.redis.close()
|
|
290
272
|
if "memcached" in apps:
|
|
291
|
-
|
|
273
|
+
info(f"release memcached session")
|
|
292
274
|
apps.memcached.close()
|
|
293
275
|
|
|
294
276
|
async def _init_database(self, apps):
|
|
@@ -322,12 +304,12 @@ class Application(object):
|
|
|
322
304
|
storage = redis_storage.RedisStorage(
|
|
323
305
|
redis,
|
|
324
306
|
cookie_name=cookie_name,
|
|
325
|
-
max_age=
|
|
307
|
+
max_age=_parse_int(max_age),
|
|
326
308
|
httponly=httponly,
|
|
327
309
|
secure=secure,
|
|
328
310
|
)
|
|
329
311
|
setup(apps, storage)
|
|
330
|
-
|
|
312
|
+
info("start redis session.")
|
|
331
313
|
elif mcache_conf:
|
|
332
314
|
host = mcache_conf.get("host")
|
|
333
315
|
port = mcache_conf.get("port", 11211)
|
|
@@ -337,12 +319,12 @@ class Application(object):
|
|
|
337
319
|
storage = memcached_storage.MemcachedStorage(
|
|
338
320
|
mc,
|
|
339
321
|
cookie_name=cookie_name,
|
|
340
|
-
max_age=
|
|
322
|
+
max_age=_parse_int(max_age),
|
|
341
323
|
httponly=httponly,
|
|
342
324
|
secure=secure,
|
|
343
325
|
)
|
|
344
326
|
setup(apps, storage)
|
|
345
|
-
|
|
327
|
+
info("start memcached session.")
|
|
346
328
|
else:
|
|
347
329
|
dig = hashlib.sha256(cookie_name.encode()).digest()
|
|
348
330
|
fernet_key = base64.urlsafe_b64encode(dig)
|
|
@@ -354,11 +336,11 @@ class Application(object):
|
|
|
354
336
|
storage = cookie_storage.EncryptedCookieStorage(
|
|
355
337
|
secret_key,
|
|
356
338
|
cookie_name=cookie_name,
|
|
357
|
-
max_age=
|
|
339
|
+
max_age=_parse_int(max_age),
|
|
358
340
|
httponly=httponly,
|
|
359
341
|
)
|
|
360
342
|
setup(apps, storage)
|
|
361
|
-
|
|
343
|
+
info("start local cookie.")
|
|
362
344
|
|
|
363
345
|
def run(self, **kw):
|
|
364
346
|
"""
|
|
@@ -368,13 +350,9 @@ class Application(object):
|
|
|
368
350
|
"""
|
|
369
351
|
defaults = {"host": "0.0.0.0", "port": 9001, "path": None}
|
|
370
352
|
conf = self.config.get("server", {})
|
|
371
|
-
# args_conf = {"host": self.args.host, "port": self.args.port}
|
|
372
|
-
# print(conf)
|
|
373
353
|
conf = chainMap(defaults, conf, kw)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
print_info(f"Server running on http://{conf['host']}:{conf['port']}")
|
|
377
|
-
print_info("(Press CTRL+C to quit)")
|
|
354
|
+
info(f"Server running on http://{conf['host']}:{conf['port']}")
|
|
355
|
+
info("(Press CTRL+C to quit.)")
|
|
378
356
|
web.run_app(
|
|
379
357
|
self.app,
|
|
380
358
|
loop=self._loop,
|
|
@@ -384,30 +362,22 @@ class Application(object):
|
|
|
384
362
|
access_log=None,
|
|
385
363
|
print=None,
|
|
386
364
|
)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
# return web.run_app(self.app, host=host, port=port, **kw)
|
|
390
|
-
# else:
|
|
391
|
-
# print_info(f"Server run at http://{host}:{port}")
|
|
392
|
-
# return self.loop.create_server(
|
|
393
|
-
# self.app.make_handler(), host=host, port=port, **kw
|
|
394
|
-
# )
|
|
395
|
-
|
|
396
|
-
def add_router(self, path, method, name):
|
|
365
|
+
|
|
366
|
+
def add_router(self, path, method, name, sa_ignore):
|
|
397
367
|
def inner(handler):
|
|
398
368
|
handler = RequestHandler(handler)
|
|
399
|
-
self.
|
|
369
|
+
self.router.add_route(method, path, handler, name=name)
|
|
400
370
|
return handler
|
|
401
371
|
|
|
372
|
+
if sa_ignore:
|
|
373
|
+
hashstr = _sa_ignore_hash(method, path)
|
|
374
|
+
self.app.ignore_paths.add(hashstr)
|
|
402
375
|
return inner
|
|
403
376
|
|
|
404
|
-
def add_middleware(self):
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
self._middleware.append(mid)
|
|
409
|
-
|
|
410
|
-
return wrapper
|
|
377
|
+
def add_middleware(self, func):
|
|
378
|
+
func.__middleware_version__ = 1
|
|
379
|
+
self._middleware.append(func)
|
|
380
|
+
return func
|
|
411
381
|
|
|
412
382
|
def jwt_encode(self, payload):
|
|
413
383
|
jwt_conf = self.config.get("jwt", {})
|
|
@@ -426,6 +396,14 @@ class Application(object):
|
|
|
426
396
|
def route_table(self):
|
|
427
397
|
return self._route_table
|
|
428
398
|
|
|
399
|
+
@property
|
|
400
|
+
def router(self):
|
|
401
|
+
return self.app.router
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def middlewares(self):
|
|
405
|
+
return self.app.middlewares
|
|
406
|
+
|
|
429
407
|
@property
|
|
430
408
|
def on_startup(self):
|
|
431
409
|
return self.app.on_startup
|
|
@@ -448,55 +426,11 @@ class View(web.View):
|
|
|
448
426
|
request = self.request
|
|
449
427
|
if request.method not in hdrs.METH_ALL:
|
|
450
428
|
self._raise_allowed_methods()
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if
|
|
429
|
+
func: Optional[Callable[[], Awaitable[StreamResponse]]]
|
|
430
|
+
func = getattr(self, request.method.lower(), None)
|
|
431
|
+
if func is None:
|
|
454
432
|
self._raise_allowed_methods()
|
|
455
|
-
|
|
456
|
-
# assert isinstance(ret, StreamResponse)
|
|
457
|
-
# return ret
|
|
458
|
-
content_type = request.content_type
|
|
459
|
-
# 获取函数参数的名称和默认值
|
|
460
|
-
props = inspect.getfullargspec(method)
|
|
461
|
-
if len(props.args) == 3 and content_type == "multipart/form-data":
|
|
462
|
-
await set_session_route(request)
|
|
463
|
-
multipart = await request.multipart()
|
|
464
|
-
field = await multipart.next()
|
|
465
|
-
result = await method(request, field)
|
|
466
|
-
elif len(props.args) == 2:
|
|
467
|
-
await set_session_route(request)
|
|
468
|
-
if content_type == "multipart/form-data":
|
|
469
|
-
data = await request.post()
|
|
470
|
-
elif content_type == "application/json":
|
|
471
|
-
data = await request.json()
|
|
472
|
-
else:
|
|
473
|
-
data = await request.post()
|
|
474
|
-
query_string = request.query_string
|
|
475
|
-
body = {}
|
|
476
|
-
for k in data:
|
|
477
|
-
body[k] = data[k]
|
|
478
|
-
qs = {}
|
|
479
|
-
if query_string:
|
|
480
|
-
for k, v in parse.parse_qs(query_string, True).items():
|
|
481
|
-
qs[k] = v[0]
|
|
482
|
-
request.qs = Object(qs)
|
|
483
|
-
request.body = Object(body)
|
|
484
|
-
result = await method(request)
|
|
485
|
-
else:
|
|
486
|
-
result = await method()
|
|
487
|
-
try:
|
|
488
|
-
if isinstance(result, Response):
|
|
489
|
-
return result
|
|
490
|
-
if isinstance(result, StreamResponse):
|
|
491
|
-
return result
|
|
492
|
-
if isinstance(result, WebSocketResponse): # maybe catch error
|
|
493
|
-
return result
|
|
494
|
-
if "content_type" in result and "text/html" in result["content_type"]:
|
|
495
|
-
return result
|
|
496
|
-
except:
|
|
497
|
-
pass
|
|
498
|
-
|
|
499
|
-
return render_json(result)
|
|
433
|
+
return await _render_result(request, func)
|
|
500
434
|
|
|
501
435
|
|
|
502
436
|
app = Application()
|
|
@@ -556,23 +490,24 @@ async def WebStream(
|
|
|
556
490
|
return stream
|
|
557
491
|
|
|
558
492
|
|
|
559
|
-
def get(path: str, name=None):
|
|
560
|
-
return app.add_router(path, "GET", name)
|
|
493
|
+
def get(path: str, name=None, sa_ignore=False):
|
|
494
|
+
return app.add_router(path, "GET", name, sa_ignore)
|
|
561
495
|
|
|
562
496
|
|
|
563
|
-
def post(path: str, name=None):
|
|
564
|
-
return app.add_router(path, "POST", name)
|
|
497
|
+
def post(path: str, name=None, sa_ignore=False):
|
|
498
|
+
return app.add_router(path, "POST", name, sa_ignore)
|
|
565
499
|
|
|
566
500
|
|
|
567
|
-
def put(path: str, name=None):
|
|
568
|
-
return app.add_router(path, "PUT", name)
|
|
501
|
+
def put(path: str, name=None, sa_ignore=False):
|
|
502
|
+
return app.add_router(path, "PUT", name, sa_ignore)
|
|
569
503
|
|
|
570
504
|
|
|
571
|
-
def delete(path: str, name=None):
|
|
572
|
-
return app.add_router(path, "DELETE", name)
|
|
505
|
+
def delete(path: str, name=None, sa_ignore=False):
|
|
506
|
+
return app.add_router(path, "DELETE", name, sa_ignore)
|
|
573
507
|
|
|
574
508
|
|
|
575
|
-
def routes(path: str):
|
|
509
|
+
def routes(path: str, sa_ignore=False):
|
|
510
|
+
# todo: sa_ignore
|
|
576
511
|
return app.route_table.view(path)
|
|
577
512
|
|
|
578
513
|
|
|
@@ -601,8 +536,8 @@ def render_json(data, **kw) -> Response:
|
|
|
601
536
|
return web.json_response(text=text, **kw)
|
|
602
537
|
|
|
603
538
|
|
|
604
|
-
def middleware():
|
|
605
|
-
return app.add_middleware()
|
|
539
|
+
def middleware(func):
|
|
540
|
+
return app.add_middleware(func)
|
|
606
541
|
|
|
607
542
|
|
|
608
543
|
def render(**kw) -> Response:
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import yaml
|
|
2
2
|
import os
|
|
3
|
-
from ..logging import
|
|
3
|
+
from ..logging import error, info
|
|
4
4
|
from envyaml import EnvYAML
|
|
5
5
|
|
|
6
6
|
|
|
@@ -8,9 +8,9 @@ def get_config(env):
|
|
|
8
8
|
if env is None:
|
|
9
9
|
return {}
|
|
10
10
|
conf_path = os.path.join(os.path.abspath("."), "config", f"conf.{env}.yaml")
|
|
11
|
-
|
|
11
|
+
info(f"loading config {conf_path}")
|
|
12
12
|
if not os.path.exists(conf_path):
|
|
13
|
-
|
|
13
|
+
error(f"Configuration file does not exist: {conf_path}")
|
|
14
14
|
return {}
|
|
15
15
|
with open(conf_path, "r", encoding="utf-8") as f:
|
|
16
16
|
config = yaml.safe_load(f)
|
|
@@ -18,5 +18,5 @@ def get_config(env):
|
|
|
18
18
|
config_ori = EnvYAML(conf_path, strict=False)
|
|
19
19
|
config = dict(config_ori)
|
|
20
20
|
except BaseException as e:
|
|
21
|
-
|
|
21
|
+
error(e)
|
|
22
22
|
return config or {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudoll
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: 辅助快速创建可分布的微服务。
|
|
5
5
|
Home-page: https://gitee.com/chuchur/cloudoll-py
|
|
6
6
|
Author: chuchur
|
|
@@ -29,6 +29,17 @@ Requires-Dist: aiohttp_session[secure]
|
|
|
29
29
|
Requires-Dist: PyYAML
|
|
30
30
|
Requires-Dist: click
|
|
31
31
|
Requires-Dist: envyaml
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: author-email
|
|
34
|
+
Dynamic: classifier
|
|
35
|
+
Dynamic: description
|
|
36
|
+
Dynamic: description-content-type
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: license
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
Dynamic: requires-dist
|
|
41
|
+
Dynamic: requires-python
|
|
42
|
+
Dynamic: summary
|
|
32
43
|
|
|
33
44
|
|
|
34
45
|
# cloudoll 云端玩具
|
|
@@ -28,13 +28,14 @@ cloudoll/robot/dingtalk.py
|
|
|
28
28
|
cloudoll/robot/feishu.py
|
|
29
29
|
cloudoll/robot/slack.py
|
|
30
30
|
cloudoll/utils/__init__.py
|
|
31
|
-
cloudoll/utils/
|
|
31
|
+
cloudoll/utils/cli_main.py
|
|
32
32
|
cloudoll/utils/common.py
|
|
33
33
|
cloudoll/utils/m2d.py
|
|
34
34
|
cloudoll/utils/patch_loop.py
|
|
35
35
|
cloudoll/utils/snowflake.py
|
|
36
36
|
cloudoll/utils/watch.py
|
|
37
37
|
cloudoll/web/__init__.py
|
|
38
|
+
cloudoll/web/core.py
|
|
38
39
|
cloudoll/web/exception.py
|
|
39
40
|
cloudoll/web/html.py
|
|
40
41
|
cloudoll/web/http.py
|
|
@@ -123,9 +123,6 @@ setup(
|
|
|
123
123
|
exclude=["tests", "*.tests", "*.tests.*", "tests.*"]),
|
|
124
124
|
# If your package is a single module, use this instead of 'packages':
|
|
125
125
|
# py_modules=['mypackage'],
|
|
126
|
-
# entry_points={
|
|
127
|
-
# 'console_scripts': ['mycli=mymodule:cli'],
|
|
128
|
-
# },
|
|
129
126
|
install_requires=REQUIRED,
|
|
130
127
|
entry_points={
|
|
131
128
|
'console_scripts': [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.2.38"
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
class Object(dict):
|
|
2
|
-
# __setattr__ = dict.__setitem__
|
|
3
|
-
# __getattr__ = dict.__getitem__
|
|
4
|
-
def __setattr__(self, key, value):
|
|
5
|
-
self[key] = value
|
|
6
|
-
|
|
7
|
-
def __getattr__(self, key):
|
|
8
|
-
return self.get(key, None)
|
|
9
|
-
|
|
10
|
-
def __delattr__(self, key):
|
|
11
|
-
try:
|
|
12
|
-
del self[key]
|
|
13
|
-
except KeyError:
|
|
14
|
-
raise AttributeError(f"'DotDict' object has no attribute '{key}'")
|
|
15
|
-
|
|
16
|
-
def __getstate__(self):
|
|
17
|
-
'''to fix pickle.dumps '''
|
|
18
|
-
return dict(self)
|
|
19
|
-
|
|
20
|
-
def __setstate__(self, state):
|
|
21
|
-
self.update(state)
|
|
22
|
-
|
|
23
|
-
def chainMap(*dicts):
|
|
24
|
-
merged_dict = Object()
|
|
25
|
-
for d in dicts:
|
|
26
|
-
for key, value in d.items():
|
|
27
|
-
if key not in merged_dict or value is not None:
|
|
28
|
-
merged_dict[key] = value
|
|
29
|
-
return merged_dict
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
|
-
__author__ = "chuchur/chuchur.com"
|
|
5
|
-
|
|
6
|
-
from curl_cffi import requests
|
|
7
|
-
import time
|
|
8
|
-
|
|
9
|
-
# from requests import Response
|
|
10
|
-
from ..logging import print_error, print_info
|
|
11
|
-
|
|
12
|
-
PROXIES = {
|
|
13
|
-
"http": "http://127.0.0.1:7890",
|
|
14
|
-
"https": "http://127.0.0.1:7890",
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
HEADERS = {
|
|
18
|
-
"sec-ch-ua": '"Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102"',
|
|
19
|
-
"sec-ch-ua-mobile": "?0",
|
|
20
|
-
"sec-ch-ua-platform": "Windows",
|
|
21
|
-
"sec-fetch-dest": "empty",
|
|
22
|
-
"sec-fetch-mode": "cors",
|
|
23
|
-
"Expires": "0",
|
|
24
|
-
"Pragma": "no-cache",
|
|
25
|
-
"Cache": "no-cache",
|
|
26
|
-
"Cache-control": "no-cache",
|
|
27
|
-
"Connection": "keep-alive",
|
|
28
|
-
# "Content-Type": "application/json; charset=UTF-8",
|
|
29
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class Client(object):
|
|
34
|
-
def __init__(self):
|
|
35
|
-
self.session = requests.Session()
|
|
36
|
-
|
|
37
|
-
def requests(self, method, url, **kw):
|
|
38
|
-
headers = kw.get("headers", None)
|
|
39
|
-
if headers is None:
|
|
40
|
-
headers = HEADERS
|
|
41
|
-
else:
|
|
42
|
-
headers = {**headers, **HEADERS}
|
|
43
|
-
kw["headers"] = headers
|
|
44
|
-
proxies = kw.get("proxies", False)
|
|
45
|
-
if proxies:
|
|
46
|
-
if proxies is True:
|
|
47
|
-
kw["proxies"] = PROXIES
|
|
48
|
-
else:
|
|
49
|
-
kw["proxies"] = proxies
|
|
50
|
-
try_times = kw.get("try_times", 2)
|
|
51
|
-
if kw.get("try_times"):
|
|
52
|
-
kw.pop("try_times")
|
|
53
|
-
result = None
|
|
54
|
-
while try_times > 0:
|
|
55
|
-
try:
|
|
56
|
-
fun = getattr(self.session, method, None)
|
|
57
|
-
result: requests.Response = fun(url, **kw)
|
|
58
|
-
head = result.headers
|
|
59
|
-
ctype = head["Content-Type"]
|
|
60
|
-
if result.status_code == 200:
|
|
61
|
-
try_times = 0
|
|
62
|
-
if "application/json" in ctype:
|
|
63
|
-
return result.json()
|
|
64
|
-
elif "text/html" in ctype:
|
|
65
|
-
return result.text
|
|
66
|
-
return result
|
|
67
|
-
else:
|
|
68
|
-
print_info(result.json())
|
|
69
|
-
print_error(f"Error : code ->{result.status_code},try gain....")
|
|
70
|
-
try_times -= 1
|
|
71
|
-
time.sleep(2)
|
|
72
|
-
except BaseException as e:
|
|
73
|
-
print_error(e)
|
|
74
|
-
try_times -= 1
|
|
75
|
-
time.sleep(2)
|
|
76
|
-
# print_info(e)
|
|
77
|
-
return result
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
http = Client()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def get(url, **kw):
|
|
84
|
-
"""
|
|
85
|
-
get 请求
|
|
86
|
-
:params params=dict() 传参
|
|
87
|
-
"""
|
|
88
|
-
return http.requests("get", url, **kw)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def post(url, **kw):
|
|
92
|
-
"""
|
|
93
|
-
post 请求
|
|
94
|
-
:params data=dict() or json=dict() 传参
|
|
95
|
-
"""
|
|
96
|
-
return http.requests("post", url, **kw)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def put(url, **kw):
|
|
100
|
-
"""
|
|
101
|
-
post 请求
|
|
102
|
-
data=dict()传参
|
|
103
|
-
"""
|
|
104
|
-
return http.requests("put", url, **kw)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def delete(url, **kw):
|
|
108
|
-
return http.requests("delete", url, **kw)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def head(url, **kw):
|
|
112
|
-
return http.requests("head", url, **kw)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def option(url, **kw):
|
|
116
|
-
return http.requests("option", url, **kw)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def download(url, savepath=None, **kw):
|
|
120
|
-
"""
|
|
121
|
-
下载文件
|
|
122
|
-
:params url 文件的地址
|
|
123
|
-
:params savepath 保存路径
|
|
124
|
-
"""
|
|
125
|
-
rb = http.requests("get", url, **kw)
|
|
126
|
-
if not savepath:
|
|
127
|
-
return rb.content
|
|
128
|
-
if not rb:
|
|
129
|
-
print_error("下载出错")
|
|
130
|
-
return False
|
|
131
|
-
else:
|
|
132
|
-
try:
|
|
133
|
-
with open(savepath, "wb") as f:
|
|
134
|
-
f.write(rb.content)
|
|
135
|
-
print_info("下载完成!")
|
|
136
|
-
except BaseException as e:
|
|
137
|
-
print_error(e)
|
|
138
|
-
return False
|
|
139
|
-
return True
|
|
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
|
|
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
|