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/core.py
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
__author__ = "chuchur/chuchur.com"
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import asyncio
|
|
8
|
+
import hashlib
|
|
9
|
+
import os
|
|
10
|
+
import base64
|
|
11
|
+
import importlib
|
|
12
|
+
import inspect
|
|
13
|
+
import json
|
|
14
|
+
from errno import EADDRINUSE
|
|
15
|
+
from urllib import parse
|
|
16
|
+
from aiohttp import web, hdrs
|
|
17
|
+
from aiohttp.web import Response
|
|
18
|
+
from aiohttp.web_ws import WebSocketResponse, StreamResponse, WSMsgType
|
|
19
|
+
from aiohttp.web_response import LooseHeaders
|
|
20
|
+
from aiohttp_session import (
|
|
21
|
+
get_session,
|
|
22
|
+
setup,
|
|
23
|
+
redis_storage,
|
|
24
|
+
memcached_storage,
|
|
25
|
+
cookie_storage,
|
|
26
|
+
)
|
|
27
|
+
from .settings import get_config
|
|
28
|
+
import aiomcache
|
|
29
|
+
from redis import asyncio as aioredis
|
|
30
|
+
from ..logging import warning, info
|
|
31
|
+
from ..orm.model import Model
|
|
32
|
+
from . import jwt
|
|
33
|
+
from decimal import Decimal
|
|
34
|
+
from datetime import datetime, date
|
|
35
|
+
from ..utils.common import chainMap, Object
|
|
36
|
+
from ..orm import create_engine, parse_coon
|
|
37
|
+
import uuid
|
|
38
|
+
from typing import Optional, Iterable, Callable, Awaitable
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RequestHandler(object):
|
|
42
|
+
def __init__(self, fn):
|
|
43
|
+
self.fn = fn
|
|
44
|
+
|
|
45
|
+
async def __call__(self, request):
|
|
46
|
+
info(f"{request.method} {request.path}")
|
|
47
|
+
return await _render_result(request, self.fn)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def _set_session_route(request):
|
|
51
|
+
params = dict()
|
|
52
|
+
# match
|
|
53
|
+
rt = request.match_info
|
|
54
|
+
for k, v in rt.items():
|
|
55
|
+
params[k] = v
|
|
56
|
+
request.params = Object(params)
|
|
57
|
+
session = await get_session(request)
|
|
58
|
+
# session = await new_session(request)
|
|
59
|
+
request.session = session
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _auto_reg_module(module_dir: str):
|
|
63
|
+
module_path = os.path.join(os.path.abspath("."), module_dir)
|
|
64
|
+
if not os.path.exists(module_path):
|
|
65
|
+
info(f"{module_dir} not detected")
|
|
66
|
+
return
|
|
67
|
+
for filename in os.listdir(module_dir):
|
|
68
|
+
if filename.startswith("__") or filename.startswith("."):
|
|
69
|
+
continue
|
|
70
|
+
full_path = os.path.join(os.path.abspath("."), module_dir, filename)
|
|
71
|
+
if os.path.isdir(full_path):
|
|
72
|
+
_auto_reg_module(module_dir + os.sep + filename)
|
|
73
|
+
elif filename.endswith(".py"):
|
|
74
|
+
module_name = (
|
|
75
|
+
module_dir.replace(os.sep, ".")
|
|
76
|
+
+ "."
|
|
77
|
+
+ filename[:-3].replace(os.sep, ".")
|
|
78
|
+
)
|
|
79
|
+
importlib.import_module(module_name)
|
|
80
|
+
|
|
81
|
+
|
|
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):
|
|
139
|
+
return eval(num) if isinstance(num, str) else num
|
|
140
|
+
|
|
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
|
+
|
|
151
|
+
class Application(object):
|
|
152
|
+
def __init__(self):
|
|
153
|
+
self._loop = None
|
|
154
|
+
self.env = None
|
|
155
|
+
self.app = None
|
|
156
|
+
self._route_table = web.RouteTableDef()
|
|
157
|
+
self._middleware = []
|
|
158
|
+
self.config = {}
|
|
159
|
+
self.clean_up = False
|
|
160
|
+
|
|
161
|
+
def _load_life_cycle(self, entry_model=None):
|
|
162
|
+
try:
|
|
163
|
+
if not entry_model:
|
|
164
|
+
return
|
|
165
|
+
entry = importlib.import_module(entry_model, ".")
|
|
166
|
+
|
|
167
|
+
life_cycle = ["on_startup", "on_shutdown", "on_cleanup", "on_task"]
|
|
168
|
+
for cycle in life_cycle:
|
|
169
|
+
if hasattr(entry, cycle):
|
|
170
|
+
cy = getattr(self, cycle)
|
|
171
|
+
cy.append(getattr(entry, cycle))
|
|
172
|
+
except ImportError:
|
|
173
|
+
warning(f"Entry model:{entry_model} can not find.")
|
|
174
|
+
|
|
175
|
+
def _init_parse(self):
|
|
176
|
+
try:
|
|
177
|
+
parser = argparse.ArgumentParser(description="Cloudapp parse")
|
|
178
|
+
|
|
179
|
+
parser.add_argument("-host", type=str, help="Server Host", required=False)
|
|
180
|
+
parser.add_argument("-port", type=int, help="Server Port", required=False)
|
|
181
|
+
parser.add_argument("-env", type=str, help="Environment", required=False)
|
|
182
|
+
self.args = parser.parse_args()
|
|
183
|
+
except:
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
def create(self, env: str = None, entry_model: str = None, config=None):
|
|
187
|
+
# self.init_parse()
|
|
188
|
+
self.env = env
|
|
189
|
+
loop = asyncio.get_event_loop()
|
|
190
|
+
if loop is None:
|
|
191
|
+
loop = asyncio.new_event_loop()
|
|
192
|
+
self._loop = loop
|
|
193
|
+
if not config:
|
|
194
|
+
config = get_config(env or "local")
|
|
195
|
+
self.config = config
|
|
196
|
+
|
|
197
|
+
sa_ignore_mid = _sa_ignore_middleware()
|
|
198
|
+
sa_ignore_mid.__middleware_version__ = 1
|
|
199
|
+
self._middleware.append(sa_ignore_mid)
|
|
200
|
+
|
|
201
|
+
# middlewares
|
|
202
|
+
_auto_reg_module("middlewares")
|
|
203
|
+
info("auto register middlewares")
|
|
204
|
+
|
|
205
|
+
conf_server = config.get("server")
|
|
206
|
+
client_max_size = 1024**2 * 2
|
|
207
|
+
if conf_server is not None:
|
|
208
|
+
client_max_size = conf_server.get("client_max_size", client_max_size)
|
|
209
|
+
self.app = web.Application(
|
|
210
|
+
logger=None,
|
|
211
|
+
loop=loop,
|
|
212
|
+
middlewares=self._middleware,
|
|
213
|
+
client_max_size=_parse_int(client_max_size),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# database
|
|
217
|
+
self.app.ignore_paths = set()
|
|
218
|
+
self.app.db = Object()
|
|
219
|
+
self.app.on_startup.append(self._init_database)
|
|
220
|
+
self.app.on_cleanup.append(self._close_database)
|
|
221
|
+
self.app.config = config
|
|
222
|
+
self.app.env = env
|
|
223
|
+
self.app.jwt_encode = self.jwt_encode
|
|
224
|
+
self.app.jwt_decode = self.jwt_decode
|
|
225
|
+
# session
|
|
226
|
+
self.app.on_startup.append(self._init_session)
|
|
227
|
+
# router:
|
|
228
|
+
_auto_reg_module("controllers")
|
|
229
|
+
info("auto register controllers")
|
|
230
|
+
|
|
231
|
+
self.app.add_routes(self._route_table)
|
|
232
|
+
|
|
233
|
+
# load life
|
|
234
|
+
entry = conf_server.get("entry", entry_model)
|
|
235
|
+
self._load_life_cycle(entry)
|
|
236
|
+
|
|
237
|
+
# static
|
|
238
|
+
if conf_server is not None:
|
|
239
|
+
conf_st = conf_server.get("static")
|
|
240
|
+
if conf_st:
|
|
241
|
+
temp = os.path.join(os.path.abspath("."), "static")
|
|
242
|
+
self.app.router.add_static(**conf_st, path=temp)
|
|
243
|
+
info("Suggest using nginx or others instead.")
|
|
244
|
+
|
|
245
|
+
temp = os.path.join(os.path.abspath("."), "templates")
|
|
246
|
+
if os.path.exists(temp):
|
|
247
|
+
from jinja2 import Environment, FileSystemLoader
|
|
248
|
+
|
|
249
|
+
self.env = Environment(loader=FileSystemLoader(temp), autoescape=True)
|
|
250
|
+
|
|
251
|
+
return self
|
|
252
|
+
|
|
253
|
+
async def release(self):
|
|
254
|
+
try:
|
|
255
|
+
await self._close_database(self.app)
|
|
256
|
+
except:
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
async def _close_database(self, apps):
|
|
260
|
+
if apps is None or self.clean_up:
|
|
261
|
+
return
|
|
262
|
+
self.clean_up = True
|
|
263
|
+
|
|
264
|
+
for db in apps.db:
|
|
265
|
+
info(f"release database {db}.")
|
|
266
|
+
await apps.db[db].close()
|
|
267
|
+
|
|
268
|
+
# close for session
|
|
269
|
+
if "redis" in apps:
|
|
270
|
+
info(f"release redis session.")
|
|
271
|
+
await apps.redis.close()
|
|
272
|
+
if "memcached" in apps:
|
|
273
|
+
info(f"release memcached session")
|
|
274
|
+
apps.memcached.close()
|
|
275
|
+
|
|
276
|
+
async def _init_database(self, apps):
|
|
277
|
+
conf_db = self.config.get("database")
|
|
278
|
+
if conf_db:
|
|
279
|
+
for db_key in conf_db:
|
|
280
|
+
apps.db[db_key] = await create_engine(**conf_db[db_key])
|
|
281
|
+
|
|
282
|
+
async def _init_session(self, apps):
|
|
283
|
+
config = self.config or {}
|
|
284
|
+
sess = config.get("session", {})
|
|
285
|
+
|
|
286
|
+
max_age = sess.get("max_age")
|
|
287
|
+
httponly = sess.get("httponly")
|
|
288
|
+
cookie_name = sess.get("key", "CLOUDOLL_SESSION")
|
|
289
|
+
secure = sess.get("secure")
|
|
290
|
+
|
|
291
|
+
# redis
|
|
292
|
+
redis_conf = sess.get("redis")
|
|
293
|
+
mcache_conf = sess.get("memcached")
|
|
294
|
+
|
|
295
|
+
if redis_conf:
|
|
296
|
+
redis_url = redis_conf.get("url")
|
|
297
|
+
qs = {}
|
|
298
|
+
if not redis_url:
|
|
299
|
+
cfg, qs = parse_coon(redis_url)
|
|
300
|
+
redis_url = f"{cfg['type']}://{cfg['username']}:{cfg['password']}@{cfg['host']}:{cfg['port']}/{cfg['db']}"
|
|
301
|
+
|
|
302
|
+
redis = await aioredis.from_url(redis_url, **qs)
|
|
303
|
+
apps.redis = redis
|
|
304
|
+
storage = redis_storage.RedisStorage(
|
|
305
|
+
redis,
|
|
306
|
+
cookie_name=cookie_name,
|
|
307
|
+
max_age=_parse_int(max_age),
|
|
308
|
+
httponly=httponly,
|
|
309
|
+
secure=secure,
|
|
310
|
+
)
|
|
311
|
+
setup(apps, storage)
|
|
312
|
+
info("start redis session.")
|
|
313
|
+
elif mcache_conf:
|
|
314
|
+
host = mcache_conf.get("host")
|
|
315
|
+
port = mcache_conf.get("port", 11211)
|
|
316
|
+
|
|
317
|
+
mc = aiomcache.Client(host, port)
|
|
318
|
+
apps.memcached = mc
|
|
319
|
+
storage = memcached_storage.MemcachedStorage(
|
|
320
|
+
mc,
|
|
321
|
+
cookie_name=cookie_name,
|
|
322
|
+
max_age=_parse_int(max_age),
|
|
323
|
+
httponly=httponly,
|
|
324
|
+
secure=secure,
|
|
325
|
+
)
|
|
326
|
+
setup(apps, storage)
|
|
327
|
+
info("start memcached session.")
|
|
328
|
+
else:
|
|
329
|
+
dig = hashlib.sha256(cookie_name.encode()).digest()
|
|
330
|
+
fernet_key = base64.urlsafe_b64encode(dig)
|
|
331
|
+
secret_key = base64.urlsafe_b64decode(fernet_key)
|
|
332
|
+
|
|
333
|
+
# fernet_key = fernet.Fernet.generate_key()
|
|
334
|
+
# secret_key = base64.urlsafe_b64decode(fernet_key)
|
|
335
|
+
|
|
336
|
+
storage = cookie_storage.EncryptedCookieStorage(
|
|
337
|
+
secret_key,
|
|
338
|
+
cookie_name=cookie_name,
|
|
339
|
+
max_age=_parse_int(max_age),
|
|
340
|
+
httponly=httponly,
|
|
341
|
+
)
|
|
342
|
+
setup(apps, storage)
|
|
343
|
+
info("start local cookie.")
|
|
344
|
+
|
|
345
|
+
def run(self, **kw):
|
|
346
|
+
"""
|
|
347
|
+
run app
|
|
348
|
+
:params prot default 9001
|
|
349
|
+
:params host default 127.0.0.1
|
|
350
|
+
"""
|
|
351
|
+
defaults = {"host": "0.0.0.0", "port": 9001, "path": None}
|
|
352
|
+
conf = self.config.get("server", {})
|
|
353
|
+
conf = chainMap(defaults, conf, kw)
|
|
354
|
+
info(f"Server running on http://{conf['host']}:{conf['port']}")
|
|
355
|
+
info("(Press CTRL+C to quit.)")
|
|
356
|
+
web.run_app(
|
|
357
|
+
self.app,
|
|
358
|
+
loop=self._loop,
|
|
359
|
+
host="0.0.0.0",
|
|
360
|
+
port=conf["port"],
|
|
361
|
+
path=conf["path"],
|
|
362
|
+
access_log=None,
|
|
363
|
+
print=None,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def add_router(self, path, method, name, sa_ignore):
|
|
367
|
+
def inner(handler):
|
|
368
|
+
handler = RequestHandler(handler)
|
|
369
|
+
self.router.add_route(method, path, handler, name=name)
|
|
370
|
+
return handler
|
|
371
|
+
|
|
372
|
+
if sa_ignore:
|
|
373
|
+
hashstr = _sa_ignore_hash(method, path)
|
|
374
|
+
self.app.ignore_paths.add(hashstr)
|
|
375
|
+
return inner
|
|
376
|
+
|
|
377
|
+
def add_middleware(self, func):
|
|
378
|
+
func.__middleware_version__ = 1
|
|
379
|
+
self._middleware.append(func)
|
|
380
|
+
return func
|
|
381
|
+
|
|
382
|
+
def jwt_encode(self, payload):
|
|
383
|
+
jwt_conf = self.config.get("jwt", {})
|
|
384
|
+
key = jwt_conf.get("key")
|
|
385
|
+
exp = jwt_conf.get("exp")
|
|
386
|
+
if not key or not exp:
|
|
387
|
+
raise KeyError("Please set jwt key or exp...")
|
|
388
|
+
return jwt.encode(payload, key, exp)
|
|
389
|
+
|
|
390
|
+
def jwt_decode(self, token):
|
|
391
|
+
jwt_conf = self.config.get("jwt", {})
|
|
392
|
+
key = jwt_conf.get("key")
|
|
393
|
+
return jwt.decode(token, key)
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def route_table(self):
|
|
397
|
+
return self._route_table
|
|
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
|
+
|
|
407
|
+
@property
|
|
408
|
+
def on_startup(self):
|
|
409
|
+
return self.app.on_startup
|
|
410
|
+
|
|
411
|
+
@property
|
|
412
|
+
def on_shutdown(self):
|
|
413
|
+
return self.app.on_shutdown
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def on_cleanup(self):
|
|
417
|
+
return self.app.on_cleanup
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def on_task(self):
|
|
421
|
+
return self.app.cleanup_ctx
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class View(web.View):
|
|
425
|
+
async def _iter(self) -> StreamResponse:
|
|
426
|
+
request = self.request
|
|
427
|
+
if request.method not in hdrs.METH_ALL:
|
|
428
|
+
self._raise_allowed_methods()
|
|
429
|
+
func: Optional[Callable[[], Awaitable[StreamResponse]]]
|
|
430
|
+
func = getattr(self, request.method.lower(), None)
|
|
431
|
+
if func is None:
|
|
432
|
+
self._raise_allowed_methods()
|
|
433
|
+
return await _render_result(request, func)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
app = Application()
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class JsonEncoder(json.JSONEncoder):
|
|
440
|
+
def default(self, obj):
|
|
441
|
+
if isinstance(obj, datetime) or isinstance(obj, date):
|
|
442
|
+
return obj.__str__()
|
|
443
|
+
elif isinstance(obj, Decimal):
|
|
444
|
+
return str(obj)
|
|
445
|
+
elif isinstance(obj, set):
|
|
446
|
+
return list(obj)
|
|
447
|
+
elif isinstance(obj, Model):
|
|
448
|
+
return obj.__dict__
|
|
449
|
+
elif isinstance(obj, bytes):
|
|
450
|
+
return obj.decode("utf-8")
|
|
451
|
+
elif isinstance(obj, uuid.UUID) or isinstance(obj, Exception):
|
|
452
|
+
return str(obj)
|
|
453
|
+
else:
|
|
454
|
+
return super(JsonEncoder, self).default(obj)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
async def WebSocket(
|
|
458
|
+
request,
|
|
459
|
+
timeout: float = 10.0,
|
|
460
|
+
receive_timeout: Optional[float] = None,
|
|
461
|
+
autoclose: bool = True,
|
|
462
|
+
autoping: bool = True,
|
|
463
|
+
heartbeat: Optional[float] = None,
|
|
464
|
+
protocols: Iterable[str] = (),
|
|
465
|
+
compress: bool = True,
|
|
466
|
+
max_msg_size: int = 4 * 1024 * 1024,
|
|
467
|
+
) -> WebSocketResponse:
|
|
468
|
+
ws = WebSocketResponse(
|
|
469
|
+
timeout=timeout,
|
|
470
|
+
receive_timeout=receive_timeout,
|
|
471
|
+
autoclose=autoclose,
|
|
472
|
+
autoping=autoping,
|
|
473
|
+
heartbeat=heartbeat,
|
|
474
|
+
protocols=protocols,
|
|
475
|
+
compress=compress,
|
|
476
|
+
max_msg_size=max_msg_size,
|
|
477
|
+
)
|
|
478
|
+
await ws.prepare(request)
|
|
479
|
+
return ws
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
async def WebStream(
|
|
483
|
+
request,
|
|
484
|
+
status: int = 200,
|
|
485
|
+
reason: Optional[str] = None,
|
|
486
|
+
headers: Optional[LooseHeaders] = None,
|
|
487
|
+
):
|
|
488
|
+
stream = StreamResponse(status=status, reason=reason, headers=headers)
|
|
489
|
+
await stream.prepare(request)
|
|
490
|
+
return stream
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def get(path: str, name=None, sa_ignore=False):
|
|
494
|
+
return app.add_router(path, "GET", name, sa_ignore)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def post(path: str, name=None, sa_ignore=False):
|
|
498
|
+
return app.add_router(path, "POST", name, sa_ignore)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def put(path: str, name=None, sa_ignore=False):
|
|
502
|
+
return app.add_router(path, "PUT", name, sa_ignore)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def delete(path: str, name=None, sa_ignore=False):
|
|
506
|
+
return app.add_router(path, "DELETE", name, sa_ignore)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def routes(path: str, sa_ignore=False):
|
|
510
|
+
# todo: sa_ignore
|
|
511
|
+
return app.route_table.view(path)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def render_error(msg, status=500) -> Response:
|
|
515
|
+
return render_json({"message": msg, "code": status}, status=status)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def render_json(data, **kw) -> Response:
|
|
519
|
+
res = {}
|
|
520
|
+
if isinstance(data, list):
|
|
521
|
+
res["data"] = data
|
|
522
|
+
elif isinstance(data, dict):
|
|
523
|
+
res.update(data)
|
|
524
|
+
elif isinstance(data, tuple):
|
|
525
|
+
data = dict(data)
|
|
526
|
+
res.update(data)
|
|
527
|
+
else:
|
|
528
|
+
res["data"] = data
|
|
529
|
+
status = kw.get("status", 200)
|
|
530
|
+
if status == 200:
|
|
531
|
+
res["message"] = kw.get("message", "OK")
|
|
532
|
+
res["code"] = kw.get("code", 200)
|
|
533
|
+
|
|
534
|
+
res["timestamp"] = int(datetime.now().timestamp() * 1000)
|
|
535
|
+
text = json.dumps(res, ensure_ascii=False, cls=JsonEncoder)
|
|
536
|
+
return web.json_response(text=text, **kw)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def middleware(func):
|
|
540
|
+
return app.add_middleware(func)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def render(**kw) -> Response:
|
|
544
|
+
return Response(**kw)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def render_view(template=None, *args, **kw) -> Response:
|
|
548
|
+
body = None
|
|
549
|
+
if app.env is not None:
|
|
550
|
+
body = app.env.get_template(template).render(*args)
|
|
551
|
+
view = render(body=body, **kw)
|
|
552
|
+
view.content_type = "text/html;charset=utf-8"
|
|
553
|
+
return view
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def redirect(urlpath):
|
|
557
|
+
return web.HTTPFound(location=urlpath)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from aiohttp.web_exceptions import *
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = (
|
|
5
|
+
"HTTPException",
|
|
6
|
+
"HTTPError",
|
|
7
|
+
"HTTPRedirection",
|
|
8
|
+
"HTTPSuccessful",
|
|
9
|
+
"HTTPOk",
|
|
10
|
+
"HTTPCreated",
|
|
11
|
+
"HTTPAccepted",
|
|
12
|
+
"HTTPNonAuthoritativeInformation",
|
|
13
|
+
"HTTPNoContent",
|
|
14
|
+
"HTTPResetContent",
|
|
15
|
+
"HTTPPartialContent",
|
|
16
|
+
"HTTPMove",
|
|
17
|
+
"HTTPMultipleChoices",
|
|
18
|
+
"HTTPMovedPermanently",
|
|
19
|
+
"HTTPFound",
|
|
20
|
+
"HTTPSeeOther",
|
|
21
|
+
"HTTPNotModified",
|
|
22
|
+
"HTTPUseProxy",
|
|
23
|
+
"HTTPTemporaryRedirect",
|
|
24
|
+
"HTTPPermanentRedirect",
|
|
25
|
+
"HTTPClientError",
|
|
26
|
+
"HTTPBadRequest",
|
|
27
|
+
"HTTPUnauthorized",
|
|
28
|
+
"HTTPPaymentRequired",
|
|
29
|
+
"HTTPForbidden",
|
|
30
|
+
"HTTPNotFound",
|
|
31
|
+
"HTTPMethodNotAllowed",
|
|
32
|
+
"HTTPNotAcceptable",
|
|
33
|
+
"HTTPProxyAuthenticationRequired",
|
|
34
|
+
"HTTPRequestTimeout",
|
|
35
|
+
"HTTPConflict",
|
|
36
|
+
"HTTPGone",
|
|
37
|
+
"HTTPLengthRequired",
|
|
38
|
+
"HTTPPreconditionFailed",
|
|
39
|
+
"HTTPRequestEntityTooLarge",
|
|
40
|
+
"HTTPRequestURITooLong",
|
|
41
|
+
"HTTPUnsupportedMediaType",
|
|
42
|
+
"HTTPRequestRangeNotSatisfiable",
|
|
43
|
+
"HTTPExpectationFailed",
|
|
44
|
+
"HTTPMisdirectedRequest",
|
|
45
|
+
"HTTPUnprocessableEntity",
|
|
46
|
+
"HTTPFailedDependency",
|
|
47
|
+
"HTTPUpgradeRequired",
|
|
48
|
+
"HTTPPreconditionRequired",
|
|
49
|
+
"HTTPTooManyRequests",
|
|
50
|
+
"HTTPRequestHeaderFieldsTooLarge",
|
|
51
|
+
"HTTPUnavailableForLegalReasons",
|
|
52
|
+
"HTTPServerError",
|
|
53
|
+
"HTTPInternalServerError",
|
|
54
|
+
"HTTPNotImplemented",
|
|
55
|
+
"HTTPBadGateway",
|
|
56
|
+
"HTTPServiceUnavailable",
|
|
57
|
+
"HTTPGatewayTimeout",
|
|
58
|
+
"HTTPVersionNotSupported",
|
|
59
|
+
"HTTPVariantAlsoNegotiates",
|
|
60
|
+
"HTTPInsufficientStorage",
|
|
61
|
+
"HTTPNotExtended",
|
|
62
|
+
"HTTPNetworkAuthenticationRequired",
|
|
63
|
+
)
|
cloudoll/web/html.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
__author__ = "chuchur/chuchur.com"
|
|
5
|
+
from html.parser import HTMLParser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class parser(HTMLParser):
|
|
9
|
+
def __init__(self, tags: dict = None):
|
|
10
|
+
"""
|
|
11
|
+
标签属性查找
|
|
12
|
+
:params tags :{ "div":{ "id" : "list" ,"class": "table" }}
|
|
13
|
+
"""
|
|
14
|
+
HTMLParser.__init__(self)
|
|
15
|
+
self._text = []
|
|
16
|
+
self._tags = tags
|
|
17
|
+
self._images = []
|
|
18
|
+
self._videos = []
|
|
19
|
+
self._finds = []
|
|
20
|
+
|
|
21
|
+
def handle_starttag(self, tag, attrs):
|
|
22
|
+
# print('<%s>' % tag)
|
|
23
|
+
# if self._tags:
|
|
24
|
+
# for t in self._tags:
|
|
25
|
+
# if tag == t:
|
|
26
|
+
# s = attrs
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def handle_endtag(self, tag):
|
|
30
|
+
# print('</%s>' % tag)
|
|
31
|
+
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def handle_startendtag(self, tag, attrs):
|
|
35
|
+
"""
|
|
36
|
+
处理单闭合标签
|
|
37
|
+
"""
|
|
38
|
+
if tag == "img":
|
|
39
|
+
src = [v for k, v in attrs if k == "src"]
|
|
40
|
+
if len(src):
|
|
41
|
+
self._images.append(src[0])
|
|
42
|
+
if tag == "video":
|
|
43
|
+
src = [v for k, v in attrs if k == "src"]
|
|
44
|
+
if len(src):
|
|
45
|
+
self._videos.append(src[0])
|
|
46
|
+
# print('<%s/>' % tag)
|
|
47
|
+
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def handle_data(self, data):
|
|
51
|
+
self._text.append(data)
|
|
52
|
+
# print(data)
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
def handle_comment(self, data):
|
|
56
|
+
# print('<!--', data, '-->')
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def handle_entityref(self, name):
|
|
60
|
+
# print('&%s;' % name)
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def handle_charref(self, name):
|
|
64
|
+
# print('&#%s;' % name)
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def text(self):
|
|
69
|
+
return "".join(self._text)
|
|
70
|
+
|
|
71
|
+
@text.setter
|
|
72
|
+
def text(self, value):
|
|
73
|
+
self._text = value
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def images(self):
|
|
77
|
+
return self._images
|
|
78
|
+
|
|
79
|
+
@images.setter
|
|
80
|
+
def images(self, value):
|
|
81
|
+
self._images = value
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def videos(self):
|
|
85
|
+
return self._videos
|
|
86
|
+
|
|
87
|
+
@videos.setter
|
|
88
|
+
def videos(self, value):
|
|
89
|
+
self._videos = value
|
|
90
|
+
|
|
91
|
+
def parser(self, code: str):
|
|
92
|
+
self.feed(code)
|
|
93
|
+
return self
|