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.
Files changed (48) hide show
  1. {cloudoll-2.2.38 → cloudoll-2.3.0}/PKG-INFO +13 -2
  2. cloudoll-2.3.0/cloudoll/__init__.py +1 -0
  3. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/cli.py +1 -1
  4. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/logging/__init__.py +2 -2
  5. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/model.py +3 -3
  6. cloudoll-2.2.38/cloudoll/utils/cli_mian.py → cloudoll-2.3.0/cloudoll/utils/cli_main.py +2 -4
  7. cloudoll-2.3.0/cloudoll/utils/common.py +51 -0
  8. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/watch.py +14 -23
  9. cloudoll-2.3.0/cloudoll/web/__init__.py +51 -0
  10. cloudoll-2.2.38/cloudoll/web/__init__.py → cloudoll-2.3.0/cloudoll/web/core.py +134 -199
  11. cloudoll-2.3.0/cloudoll/web/http.py +79 -0
  12. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/web/settings.py +4 -4
  13. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/PKG-INFO +13 -2
  14. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/SOURCES.txt +2 -1
  15. {cloudoll-2.2.38 → cloudoll-2.3.0}/setup.py +0 -3
  16. cloudoll-2.2.38/cloudoll/__init__.py +0 -1
  17. cloudoll-2.2.38/cloudoll/utils/common.py +0 -29
  18. cloudoll-2.2.38/cloudoll/web/http.py +0 -139
  19. {cloudoll-2.2.38 → cloudoll-2.3.0}/LICENSE +0 -0
  20. {cloudoll-2.2.38 → cloudoll-2.3.0}/README.md +0 -0
  21. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/__main__.py +0 -0
  22. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/error/__init__.py +0 -0
  23. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/error/errors.py +0 -0
  24. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/mail/__init__.py +0 -0
  25. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/mail/smtp.py +0 -0
  26. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/__init__.py +0 -0
  27. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/base.py +0 -0
  28. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/err.py +0 -0
  29. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/field.py +0 -0
  30. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/mysql.py +0 -0
  31. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/parse.py +0 -0
  32. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/orm/postgres.py +0 -0
  33. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/robot/__init__.py +0 -0
  34. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/robot/dingtalk.py +0 -0
  35. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/robot/feishu.py +0 -0
  36. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/robot/slack.py +0 -0
  37. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/__init__.py +0 -0
  38. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/m2d.py +0 -0
  39. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/patch_loop.py +0 -0
  40. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/utils/snowflake.py +0 -0
  41. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/web/exception.py +0 -0
  42. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/web/html.py +0 -0
  43. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll/web/jwt.py +0 -0
  44. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/dependency_links.txt +0 -0
  45. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/entry_points.txt +0 -0
  46. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/requires.txt +0 -0
  47. {cloudoll-2.2.38 → cloudoll-2.3.0}/cloudoll.egg-info/top_level.txt +0 -0
  48. {cloudoll-2.2.38 → cloudoll-2.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cloudoll
3
- Version: 2.2.38
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"
@@ -5,7 +5,7 @@ import sys
5
5
  from .logging import print_error
6
6
  from . import __version__
7
7
  from typing import Any
8
- from .utils.cli_mian import run_app, run_gen
8
+ from .utils.cli_main import run_app, run_gen
9
9
  sys.path.append(os.path.abspath("."))
10
10
 
11
11
 
@@ -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 %(funcName)s %(message)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 %(funcName)s %(message)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 print_warn
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
- print_warn(f"Duplicate primary key for {table_name}")
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
- print_warn(f"{table_name} Missing primary key")
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 ..utils.watch import AppTask
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 ..utils.m2d import create_models, create_tables
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, check_port_open
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 print_info, print_warn
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
- print_info(f"{len(changes)} changes, restarting server")
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
- print_info(f"{act}ing dev server")
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
- print(f"Error starting server: {e}")
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
- print_info("stopping server process...")
175
+ info("stopping server process...")
185
176
  if self._process.pid:
186
- print_info("sending SIGINT")
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
- print_warn("process has not terminated, sending SIGKILL")
181
+ warning("process has not terminated, sending SIGKILL")
191
182
  self._process.kill()
192
183
  self._process.join(1)
193
184
  else:
194
- print_info("process stopped")
185
+ info("process stopped")
195
186
  else:
196
- print_warn(
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 print_warn, print_info
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
- content_type = request.content_type
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 set_session_route(request):
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
- async def check_port_open(port: int, delay: float = 1) -> None:
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
- print_info(f"{module_dir} not detected")
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
- auto_reg_module(module_dir + os.sep + filename)
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 parse_int(num):
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
- print_warn(f"Entry model:{entry_model} can not find.")
173
+ warning(f"Entry model:{entry_model} can not find.")
193
174
 
194
- def init_parse(self):
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
- auto_reg_module("middlewares")
222
- print_info("auto register middlewares")
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=parse_int(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
- # self._init_session()
245
- # router:
246
- auto_reg_module("controllers")
247
- print_info("auto register controllers")
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
- print_warn("Suggest using nginx or others instead.")
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
- print_info(f"release database {db}.")
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
- print_info(f"release redis session.")
270
+ info(f"release redis session.")
289
271
  await apps.redis.close()
290
272
  if "memcached" in apps:
291
- print_info(f"release memcached session")
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=parse_int(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
- print_info("start redis session.")
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=parse_int(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
- print_info("start memcached session.")
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=parse_int(max_age),
339
+ max_age=_parse_int(max_age),
358
340
  httponly=httponly,
359
341
  )
360
342
  setup(apps, storage)
361
- print_info("start local cookie.")
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
- # print(conf)
375
- # check_port_open(conf["host"], conf["port"])
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
- # # old
388
- # if self.loop is None:
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.app.router.add_route(method, path, handler, name=name)
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
- def wrapper(func):
406
- mid = func()
407
- mid.__middleware_version__ = 1
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
- method: Optional[Callable[[], Awaitable[StreamResponse]]]
452
- method = getattr(self, request.method.lower(), None)
453
- if method is None:
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
- # result = await method()
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 print_error, print_info
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
- print_info(f"loading config {conf_path}")
11
+ info(f"loading config {conf_path}")
12
12
  if not os.path.exists(conf_path):
13
- print_error(f"Configuration file does not exist: {conf_path}")
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
- print_error(e)
21
+ error(e)
22
22
  return config or {}
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cloudoll
3
- Version: 2.2.38
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/cli_mian.py
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