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 ADDED
@@ -0,0 +1 @@
1
+ __version__ = "2.4.0"
cloudoll/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
cloudoll/cli.py ADDED
@@ -0,0 +1,76 @@
1
+ import click
2
+ import asyncio
3
+ import os
4
+ import sys
5
+ from .logging import print_error
6
+ from . import __version__
7
+ from typing import Any
8
+ from .utils.cli_main import run_app, run_gen
9
+ sys.path.append(os.path.abspath("."))
10
+
11
+
12
+ @click.group()
13
+ @click.version_option(__version__, "-V", "--version", prog_name="cloudoll")
14
+ def cli() -> None:
15
+ pass
16
+
17
+
18
+ life_cycle = ["on_startup", "on_shutdown", "on_cleanup", "cleanup_ctx"]
19
+
20
+
21
+ @cli.command()
22
+ @click.option("-p", "--path", help="Model's relative path,save models or create tables", default="models.py",)
23
+ @click.option("-c", "--create", help="For create model or table, model / table", default="model")
24
+ @click.option("-t", "--table", help="Table's name or Model's name, split by `,` or 'ALL'", required=True,)
25
+ @click.option("-env", "--environment", help="Environment, local / test / prod", default="local")
26
+ @click.option("-db", "--database", help="Database name, pick the database in conf.{env}.yaml", default="mysql",)
27
+ def gen(**config: Any) -> None:
28
+ """Help to create models or tables."""
29
+
30
+ # click.echo(create)
31
+ async def start():
32
+ try:
33
+ sa = await run_gen(**config)
34
+ except Exception as e:
35
+ # traceback.print_exc()
36
+ print_error('Error: %s', e)
37
+ click.echo("input `cloudoll --help` for more helps.")
38
+ finally:
39
+ await sa.close()
40
+
41
+ asyncio.run(start())
42
+
43
+
44
+ @cli.command()
45
+ @click.option("-env", "--environment", help="Environment, local / test / prod", default="local")
46
+ @click.option("-p", "--port", help="Server's port", default=None)
47
+ @click.option("-h", "--host", help="Server's host", default=None)
48
+ @click.option("-m", "--mode", help="development or production mode", default='development')
49
+ @click.option("-path", "--path", help="Unix file system path to serve on. Specifying a path will cause hostname and port arguments to be ignored.",)
50
+ @click.option("-e", "--entry", help="Entry point model name. delfault name app", default="app")
51
+ def start(**config: Any) -> None:
52
+ """Start a services."""
53
+ try:
54
+ active_config = {k: v for k, v in config.items() if v is not None}
55
+ run_app(**active_config)
56
+ except Exception as e:
57
+ print_error('Error: %s', e)
58
+ # traceback.format_exc()
59
+ sys.exit(2)
60
+
61
+
62
+ _project_existing = click.Path(dir_okay=True)
63
+
64
+
65
+ @cli.command()
66
+ @click.argument("project-name", type=_project_existing, required=True)
67
+ def create(project_name) -> None:
68
+ full_path = os.path.join(os.path.abspath("."), project_name)
69
+ # log = logging.getLogger('cloudoll.create')
70
+ if os.path.exists(full_path):
71
+ print_error(f"Project name `{project_name}` already exist.")
72
+ # return None
73
+
74
+
75
+ if __name__ == "__main__":
76
+ cli()
File without changes
@@ -0,0 +1,48 @@
1
+ ERROR = {
2
+ CUSTOM: {code: -1, message: "%s"},
3
+ HSF_ERROR: {code: -6, message: "HSF 调用失败:%s。"},
4
+ SYSTEM_ERROR: {code: -10, message: "系统错误。"},
5
+ BAD_REQUEST: {code: -400, message: "错误的 http 请求。"},
6
+ NOT_FOUND: {code: -404, message: "您请求的 http 资源没有找到。"},
7
+ INTERNAL_SERVER_ERROR: {code: -500, message: "内部服务器错误。"},
8
+ DB: {code: -700, message: "%s"},
9
+ DATA_OCCUPIED: {code: -701, message: "数据被占用。"},
10
+ WHAT_REQUIRE: {code: -1001, message: "缺少参数 %s。"},
11
+ WHAT_WRONG_RANGE: {code: -1002, message: "%s 的取值范围错误。最小 %s,最大 %s"},
12
+ WHAT_WRONG_FORMAT: {code: -1003, message: "%s 格式不正确。"},
13
+ WHAT_NOT_SAME: {code: -1004, message: "输入的 %s 值不一致。"},
14
+ WHAT_NOT_EXISTS: {code: -1005, message: "%s 不存在。"},
15
+ WHAT_TOO_MUCH: {code: -1006, message: "%s 太多了。"},
16
+ WHAT_TOO_LITTLE: {code: -1007, message: "%s 太少了。"},
17
+ WHAT_NOT_BELONGS_TO_YOU: {code: -1008, message: "%s 不属于你。"},
18
+ WHAT_TOO_LONG: {code: -1009, message: "%s 太长了,请不要超过 %s。"},
19
+ WHAT_TOO_SHORT: {code: -1010, message: "%s 太短了,请不要少于 %s。"},
20
+ WHAT_EXISTED: {code: -1011, message: "%s 已经存在。"},
21
+ WHAT_OCCUPIED: {code: -1012, message: "%s 被占用。"},
22
+ WHAT_TIMEOUT: {code: -1013, message: "%s 已超时。"},
23
+ WHAT_EXPIRED: {code: -1014, message: "%s 已过期。"},
24
+ WHAT_ILLEGAL: {code: -1015, message: "%s 不合法。"},
25
+ WHAT_NOT_FOUND: {code: -1016, message: "%s 没找到。"},
26
+ WHAT_WRONG_LENGTH_RANGE: {code: -1017, message: "%s 的长度错误,最短是 %s,最长 %s。"},
27
+ WHAT_WRONG_TYPE: {code: -1018, message: "%s 的类型错误"},
28
+ ACCESS_TOKEN_NOT_FOUND: {code: -2001, message: "access_token 不存在。"},
29
+ ACCESS_TOKEN_EXPIRED: {code: -2002, message: "access_token 已经过期。"},
30
+ TICKET_EXPIRED: {code: -2050, message: "票据已经过期,请重新获取。"},
31
+ TICKET_VERIFY_FAILED: {code: -2051, message: "票据校验失败。篡改登录信息是违法行为!"},
32
+ TICKET_ILLEGAL: {code: -2052, message: "非法票据。篡改登录信息是违法行为!"},
33
+ SIGN_VERIFY_FAILED: {code: -2053, message: "签名验证失败。"},
34
+ PASSWORD_NOT_STRONG: {code: -3001, message: "密码太简单。%s"},
35
+ CHINA_MOBILE_ILLEGAL: {code: -3002, message: "不正确的手机号码。"},
36
+ EMAIL_ILLEGAL: {code: -3003, message: "不正确的Email。"},
37
+ CAPTCHA_VALIDATE_FAIL: {code: -3004, message: "验证码校验失败。"},
38
+ PASSPORT_ILLEGAL: {code: -3005, message: "不正确的登录凭据,必须是手机或者 Email。"},
39
+ MEMBER_ONLY: {code: -3006, message: "您还没有登录,请登录。"},
40
+ NO_RIGHTS: {code: -3007, message: "您没有权限,请联系管理员授权。"},
41
+ LOGIN_ERROR: {code: -3050, message: "登录失败"},
42
+ LOGIN_ERROR_BECAUSE: {code: -3051, message: "登录失败。%s"},
43
+ MEMBER_NOT_EXISTS: {code: -3052, message: "用户不存在。"},
44
+ MEMBER_NOT_APPROVED: {code: -3053, message: "尚未审批通过,请耐心等待。"},
45
+ MEMBER_FORBIDDEN: {code: -3054, message: "您已经被系统禁止。"},
46
+ MEMBER_LOGIN_TOO_MUCH_FAILURE: {code: -3055, message: "您登录失败的次数太多。暂时被系统锁定"},
47
+ IP_LOGIN_TOO_MUCH: {code: -3056, message: "您登录失败的次数太多。暴力破解他人密码是违法行为!"},
48
+ }
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ __author__ = "chuchur/chuchur.com"
5
+
6
+ import logging as loger
7
+ import os
8
+ from datetime import datetime
9
+ from logging.handlers import RotatingFileHandler
10
+ from logging import INFO, DEBUG, ERROR, CRITICAL, DEBUG
11
+ import colorlog
12
+
13
+ __ALL__ = (
14
+ "debug",
15
+ "info",
16
+ "warning",
17
+ "error",
18
+ "exception",
19
+ "critical",
20
+ "print_info",
21
+ "print_error",
22
+ "print_warn",
23
+ )
24
+ _COLORS = {
25
+ # 终端输出日志颜色配置
26
+ "DEBUG": "white",
27
+ "INFO": "green",
28
+ "WARNING": "yellow",
29
+ "ERROR": "red",
30
+ "CRITICAL": "bold_red",
31
+ }
32
+
33
+
34
+ class ColorAnsi:
35
+ RED = "\033[91m"
36
+ GREEN = "\033[92m"
37
+ YELLOW = "\033[93m"
38
+ BLUE = "\033[94m"
39
+ END = "\033[0m"
40
+
41
+
42
+ _FORMATS = {
43
+ # 终端输出格式
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 %(message)s",
46
+ # 日志输出格式
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 %(message)s",
49
+ }
50
+
51
+ _LOG_SIZE = 1024 * 1024 * 1 # 1MB 日志最大1MB
52
+ _LOG_FILES_COUNT = 3 # 最多保留3个日志文件
53
+
54
+
55
+ class HandleLog:
56
+ """
57
+ 建日志记录器(logging.getLogger)
58
+ 设置日志级别(logger.setLevel)
59
+ 创建日志文件(logging.FileHandler)
60
+ 设置日志格式(logging.Formatter)
61
+ 日志处理程序记录到记录器(addHandler)
62
+ """
63
+
64
+ def __init__(self):
65
+ # cur_path = os.path.dirname(os.path.realpath(__file__)) # 当前项目路径
66
+ # log_path = os.path.join(os.path.normpath(os.getcwd() + os.sep + os.pardir), 'logs')
67
+ # self.__logger = logging.getLogger()
68
+ self.getLogger()
69
+ log_path = os.path.join(os.path.normpath(os.getcwd() + os.sep), "logs")
70
+ # now_time = datetime.now().strftime("%Y-%m-%d") # 当前日期格式化
71
+ if not os.path.exists(log_path):
72
+ os.mkdir(log_path) # 若不存在logs文件夹,则自动创建
73
+ self.log_path = log_path
74
+ self.log_path_prefix = None # 当前日期格式化
75
+ self.all_handle = None
76
+ self.error_handle = None
77
+
78
+ # console
79
+ # if __debug__:
80
+ console_handle = colorlog.StreamHandler()
81
+ console_fmt = colorlog.ColoredFormatter(
82
+ _FORMATS["console_format"], log_colors=_COLORS
83
+ )
84
+ console_handle.setFormatter(console_fmt)
85
+ console_handle.setLevel(DEBUG)
86
+ self.__logger.addHandler(console_handle)
87
+
88
+ def setLevel(self, level):
89
+ self.__logger.setLevel(level)
90
+
91
+ def getLogger(self, name=None):
92
+ self.__logger = loger.getLogger("cloudoll") # 创建日志记录器
93
+ self.__logger.setLevel(INFO)
94
+ # self._set_handle()
95
+ return self.__logger
96
+
97
+ def __create_handler(self, log_path, level):
98
+ handler = RotatingFileHandler(
99
+ log_path,
100
+ maxBytes=_LOG_SIZE,
101
+ backupCount=_LOG_FILES_COUNT,
102
+ encoding="utf-8",
103
+ )
104
+ formatter = loger.Formatter(_FORMATS["log_format"], datefmt="%Y-%m-%d %H:%M-%S")
105
+ handler.setFormatter(formatter)
106
+ handler.setLevel(level=level)
107
+ self.__logger.addHandler(handler)
108
+ return handler
109
+
110
+ def __free(self):
111
+ if self.all_handle:
112
+ self.__logger.removeHandler(self.all_handle)
113
+ self.all_handle.close()
114
+ if self.error_handle:
115
+ self.__logger.removeHandler(self.error_handle)
116
+ self.error_handle.close()
117
+
118
+ def log(self, method):
119
+ logger = self.__logger
120
+ log_path_prefix = datetime.now().strftime("%Y-%m-%d") # 当前日期格式化
121
+ if self.log_path_prefix != log_path_prefix:
122
+ self.log_path_prefix = log_path_prefix
123
+ self.__free()
124
+ # 收集所有日志信息文件
125
+ all_log_path = os.path.join(self.log_path, "%s-all.log" % log_path_prefix)
126
+ # 收集错误日志信息文件
127
+ error_log_path = os.path.join(
128
+ self.log_path, "%s-error.log" % log_path_prefix
129
+ )
130
+
131
+ # set handle
132
+ # self.__logger.setLevel(logging.INFO) # 设置默认日志记录器记录级别
133
+ # all
134
+ self.all_handle = self.__create_handler(all_log_path, INFO)
135
+ # error
136
+ self.error_handle = self.__create_handler(error_log_path, ERROR)
137
+
138
+ return getattr(logger, method, None)
139
+
140
+
141
+ _handler = None
142
+
143
+
144
+ def print_format(color_ansi, type, *args):
145
+ ft = color_ansi + "{}" + ColorAnsi.END
146
+ timestamp = ft.format(datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " " + type)
147
+ text = " ".join(ft.format(str(arg)) for arg in args)
148
+ print(timestamp, text)
149
+
150
+
151
+ def print_info(*args):
152
+ print_format(ColorAnsi.GREEN, "INFO", *args)
153
+
154
+
155
+ def print_error(*args):
156
+ print_format(ColorAnsi.RED, "ERROR", *args)
157
+
158
+
159
+ def print_warn(*args):
160
+ print_format(ColorAnsi.YELLOW, "WARN", *args)
161
+
162
+
163
+ def _get_handle():
164
+ global _handler
165
+ handler = _handler if _handler else HandleLog()
166
+ _handler = handler
167
+ return handler
168
+
169
+
170
+ def setLevel(level: int):
171
+ _get_handle().setLevel(level)
172
+
173
+
174
+ def getLogger(name=None):
175
+ return _get_handle().getLogger(name=name)
176
+
177
+
178
+ debug = _get_handle().log("debug")
179
+
180
+ info = _get_handle().log("info")
181
+
182
+ warning = _get_handle().log("warning")
183
+
184
+ error = _get_handle().log("error")
185
+
186
+ exception = _get_handle().log("exception")
187
+
188
+ critical = _get_handle().log("critical")
File without changes
cloudoll/mail/smtp.py ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ __author__ = "chuchur"
5
+
6
+ """
7
+ MAIL = {
8
+ "smtp_server": "smtp.qq.com",
9
+ "account": "123456789@qq.com",
10
+ "account_name": "chuchur",
11
+ "password": "abcdefg",
12
+ "prot": 465, # 587
13
+ "debug_level": 1,
14
+ }
15
+
16
+ m = smtp.Mail(**MAIL)
17
+ # 标题
18
+ m.subject = "test title"
19
+ # 正文
20
+ m.content = "long long ago..."
21
+
22
+ # 嵌入html 和 html 调用附件
23
+ m.addfile("/home/chuchur/img/a.jpg") # cid 0
24
+ m.addfile("/home/chuchur/img/b.jpg") # cid 1
25
+ m.addhtml("<html><body><h1>Hello</h1>" + '<p><img src="cid:0"><img src="cid:1"></p>' + "</body></html>")
26
+
27
+ # 多个收件人
28
+ m.add_to_addr("李彦宏", "liyanhong@baidu.com")
29
+ m.add_to_addr("马云", "jackma@alibaba.com")
30
+
31
+ # 附件
32
+ m.addfile(filepathA)
33
+ m.addfile(filepathB)
34
+
35
+ # 发送
36
+ m.send()
37
+
38
+ """
39
+
40
+ import smtplib, os
41
+ from email import encoders
42
+ from email.header import Header
43
+ from email.mime.text import MIMEText
44
+ from email.mime.base import MIMEBase
45
+ from email.mime.multipart import MIMEMultipart
46
+
47
+ from email.utils import parseaddr, formataddr
48
+ import mimetypes
49
+ from ..logging import print_info, print_error
50
+
51
+
52
+ def _format_addr(s):
53
+ name, addr = parseaddr(s)
54
+ return formataddr((Header(name, "utf-8").encode(), addr))
55
+
56
+
57
+ class Client(object):
58
+ def __init__(self, **config):
59
+ self._content = None
60
+ self._subject = None
61
+ smtp_server = config.get("smtp_server")
62
+ debug_level = config.get("debug_level", 1)
63
+ port = config.get("port", 25)
64
+ self._account = config.get("account", "")
65
+ self._account_name = config.get("account_name", "")
66
+ self._password = config.get("password", "")
67
+ self._server = smtplib.SMTP_SSL(smtp_server, port)
68
+ # self._server.starttls() # 调用starttls()方法加密
69
+ self._server.set_debuglevel(debug_level) # 打印出和SMTP服务器交互的所有信息
70
+ self._msg = MIMEMultipart()
71
+ self._to_addr = []
72
+ self._mime_type = "plain" # 'html'
73
+ self._file_index = 0
74
+
75
+ def _login(self):
76
+ try:
77
+ account = self._account
78
+ password = self._password
79
+ if account is None or password is None:
80
+ raise KeyError("请设置账号密码")
81
+ print_info("登录中...")
82
+ self._server.login(account, password)
83
+ except BaseException as e:
84
+ print_error(e)
85
+ raise
86
+
87
+ def send(self):
88
+ """
89
+ 发送邮件
90
+ """
91
+ try:
92
+ self._login()
93
+ # msg = MIMEText(self._content, "plain", "uft-8")
94
+ msg = self._msg
95
+ msg["From"] = _format_addr("%s <%s>" % (self._account_name, self._account))
96
+ to = []
97
+ for t in self._to_addr:
98
+ to.append("%s <%s>" % (t["name"], t["addr"]))
99
+ msg["To"] = _format_addr(",".join(to))
100
+
101
+ # 邮件标题
102
+ msg["Subject"] = Header(self._subject, "utf-8").encode()
103
+
104
+ # 邮件正文
105
+ msg.attach(MIMEText(self._content, self._mime_type, "utf-8"))
106
+ to_addr = list(map(lambda x: x["addr"], self._to_addr))
107
+ self._server.sendmail(self._account, to_addr, msg.as_string())
108
+ self._server.quit()
109
+ except BaseException as er:
110
+ print_error(er)
111
+
112
+ def add_to_addr(self, nick, addr):
113
+ """
114
+ 添加收件人
115
+ :params nick 收件人昵称
116
+ :params addr 收件人地址
117
+ """
118
+ if addr is None:
119
+ raise KeyError("请输入邮箱地址")
120
+ obj = {"name": nick, "addr": addr}
121
+ self._to_addr.append(obj)
122
+
123
+ def addhtml(self, htmltext):
124
+ """
125
+ 添加html正文
126
+ :params htmltext html正文内容
127
+ """
128
+ self._msg.attach(MIMEText(htmltext, "html", "utf-8"))
129
+
130
+ def addfile(self, filepath):
131
+ """
132
+ 添加附件
133
+ :params filepath 附件绝对路径
134
+ """
135
+ index = self._file_index
136
+ with open(filepath, "rb") as f:
137
+ mime_type = ""
138
+ mime_types = mimetypes.guess_type(filepath) # image/png
139
+ if len(mime_types) > 0:
140
+ mime_type = mime_types[0]
141
+ else:
142
+ print_error("无法匹配附件类型,可以尝试安装httpd服务")
143
+ ## 这里如果拿不到type 需要安装httpd ,dnf install httpd
144
+ [t, n] = mime_type.split("/")
145
+ filename = os.path.basename(filepath) # 'a.txt'
146
+ # 设置附件的MIME和文件名
147
+ mime = MIMEBase(t, n, filename=filename)
148
+ # 加上头信息
149
+ mime.add_header("Content-Disposition", "attachment", filename=filename)
150
+ mime.add_header("Content-ID", "<%s>" % index)
151
+ mime.add_header("X-Attachment-Id", "%s" % index)
152
+ # 把附件内容读入
153
+ mime.set_payload(f.read())
154
+ # 用Base64编码
155
+ encoders.encode_base64(mime)
156
+
157
+ # 添加到MIMEMultipart
158
+ self._msg.attach(mime)
159
+ self._file_index += 1
160
+
161
+ @property
162
+ def mime_type(self):
163
+ return self._mime_type
164
+
165
+ @mime_type.setter
166
+ def mime_type(self, value):
167
+ self._mime_type = value
168
+
169
+ @property
170
+ def subject(self):
171
+ """
172
+ 邮件标题内容
173
+ """
174
+ return self._subject
175
+
176
+ @subject.setter
177
+ def subject(self, value):
178
+ """
179
+ 邮件标题内容
180
+ :params value 标题内容
181
+ """
182
+ self._subject = value
183
+
184
+ @property
185
+ def content(self):
186
+ """
187
+ 邮件正文内容
188
+ """
189
+ return self._content
190
+
191
+ @content.setter
192
+ def content(self, value):
193
+ """
194
+ 邮件正文内容
195
+ :params value 正文内容
196
+ """
197
+ self._content = value