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/robot/slack.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from cloudoll.web import http
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
web_hook: https://infloww.slack.com/apps/A0F7XDUAZ-incoming-webhook?tab=settings&next_id=0
|
|
5
|
+
blocks_info: https://api.slack.com/messaging/composing/layouts
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Header:
|
|
10
|
+
def __init__(self, text: str):
|
|
11
|
+
self._text = text
|
|
12
|
+
|
|
13
|
+
def get_json(self):
|
|
14
|
+
return {"type": "header", "text": {"type": "plain_text", "text": self._text}}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Text:
|
|
18
|
+
def __init__(self, text: str | int):
|
|
19
|
+
self._text = text
|
|
20
|
+
|
|
21
|
+
def get_json(self):
|
|
22
|
+
return {"type": "section", "text": {"type": "mrkdwn", "text": self._text}}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Field:
|
|
26
|
+
def __init__(self, title: str | int, text: str | int):
|
|
27
|
+
self._title = title
|
|
28
|
+
self._text = text
|
|
29
|
+
|
|
30
|
+
def get_json(self):
|
|
31
|
+
return {"type": "mrkdwn", "text": f"{self._title}\n{self._text}"}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Link:
|
|
35
|
+
def __init__(self, title: str | int, url: str | int):
|
|
36
|
+
self._title = title
|
|
37
|
+
self._url = url
|
|
38
|
+
|
|
39
|
+
def get_json(self):
|
|
40
|
+
return {
|
|
41
|
+
"type": "context",
|
|
42
|
+
"elements": [{"type": "mrkdwn", "text": f"<{self._url}|_{self._title}_>"}],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Section:
|
|
47
|
+
def __init__(self):
|
|
48
|
+
self._fileds: list[Field] = []
|
|
49
|
+
|
|
50
|
+
def add(self, field: Field):
|
|
51
|
+
self._fileds.append(field.get_json())
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def get_json(self):
|
|
55
|
+
return {"type": "section", "fileds": self._fileds}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Elements:
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self._elements: list[Field] = []
|
|
61
|
+
|
|
62
|
+
def add(self, field: Field):
|
|
63
|
+
self._elements.append(field.get_json())
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def get_json(self):
|
|
67
|
+
return {"type": "context", "elements": self._elements}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Divider:
|
|
71
|
+
def __init__(self):
|
|
72
|
+
self._fileds: list[Field] = []
|
|
73
|
+
|
|
74
|
+
def get_json(self):
|
|
75
|
+
return {"type": "divider"}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Blocks:
|
|
79
|
+
def __init__(self):
|
|
80
|
+
self._blocks = []
|
|
81
|
+
|
|
82
|
+
def add(self, block: Section | Header | Text | Divider | Link):
|
|
83
|
+
self._blocks.append(block)
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def add_header(self, text):
|
|
87
|
+
self._blocks.append(Header(text))
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def add_link(self, title, url: str):
|
|
91
|
+
self._blocks.append(Link(title, url))
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
def add_text(self, text):
|
|
95
|
+
self._blocks.append(Text(text))
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
def add_divider(self):
|
|
99
|
+
self._blocks.append(Divider())
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def get_json(self):
|
|
103
|
+
blocks = []
|
|
104
|
+
for block in self._blocks:
|
|
105
|
+
blocks.append(block.get_json())
|
|
106
|
+
return blocks
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class Slack:
|
|
110
|
+
def __init__(self, url: str):
|
|
111
|
+
self._url = url
|
|
112
|
+
|
|
113
|
+
def send_notify(self, blocks: Blocks):
|
|
114
|
+
res = http.post(url=self._url, json={"blocks": blocks})
|
|
115
|
+
return res
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from ..web.settings import get_config
|
|
3
|
+
from ..logging import print_info
|
|
4
|
+
from .common import Object, chainMap
|
|
5
|
+
from .watch import AppTask
|
|
6
|
+
from aiohttp import web
|
|
7
|
+
from ..web import app
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from .m2d import create_models, create_tables
|
|
10
|
+
from ..orm import create_engine
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_app(**config_kwargs: Any):
|
|
15
|
+
config = Object(config_kwargs)
|
|
16
|
+
print_info("Current mode: ", config.mode)
|
|
17
|
+
app_config = get_config(config.environment)
|
|
18
|
+
# print(environment, host, port, mode, path, entry)
|
|
19
|
+
# return
|
|
20
|
+
if config.mode == 'production':
|
|
21
|
+
App = app.create(env=config.environment, config=app_config, entry_model=config.entry)
|
|
22
|
+
App.run()
|
|
23
|
+
else:
|
|
24
|
+
aux_app = web.Application(
|
|
25
|
+
logger=None,
|
|
26
|
+
)
|
|
27
|
+
defaults = {"host": "0.0.0.0", "port": 9001, "path": None}
|
|
28
|
+
conf_server = app_config.get("server", {})
|
|
29
|
+
env_server = {"host": config.host, "port": config.port, "path": config.path}
|
|
30
|
+
server = chainMap(defaults, conf_server, env_server)
|
|
31
|
+
app_config["server"] = server
|
|
32
|
+
aux_port = int(server.port) + 1
|
|
33
|
+
task = AppTask(Path(".").resolve(), app_config, entry=config.entry, env=config.environment)
|
|
34
|
+
aux_app.cleanup_ctx.append(task.cleanup_ctx)
|
|
35
|
+
web.run_app(
|
|
36
|
+
aux_app,
|
|
37
|
+
access_log=None,
|
|
38
|
+
host=config.host,
|
|
39
|
+
port=aux_port,
|
|
40
|
+
print=None,
|
|
41
|
+
shutdown_timeout=0.1,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def run_gen(**config_kwargs: Any):
|
|
46
|
+
config = Object(config_kwargs)
|
|
47
|
+
configs = get_config(config.environment)
|
|
48
|
+
db_configs = configs.get("database")
|
|
49
|
+
if db_configs is None:
|
|
50
|
+
raise KeyError(
|
|
51
|
+
f"Can't find the database config in conf.{config.environment}.yaml"
|
|
52
|
+
)
|
|
53
|
+
db_config = db_configs.get(config.database)
|
|
54
|
+
if db_config is None:
|
|
55
|
+
raise KeyError(
|
|
56
|
+
f"Can't find the database config key ->{config.database} in conf.{config.environment}.yaml"
|
|
57
|
+
)
|
|
58
|
+
sa = await create_engine(**db_config)
|
|
59
|
+
if sa.pool is None:
|
|
60
|
+
return
|
|
61
|
+
model_path = os.path.join(os.path.abspath("."), config.path)
|
|
62
|
+
tables = None
|
|
63
|
+
if config.table != "ALL":
|
|
64
|
+
tables = config.table.split(",")
|
|
65
|
+
|
|
66
|
+
if config.create == "model":
|
|
67
|
+
await create_models(sa, config.path, tables=tables)
|
|
68
|
+
print_info(f"Model save at:{model_path}")
|
|
69
|
+
elif config.create == "table":
|
|
70
|
+
if model_path is None:
|
|
71
|
+
raise ValueError("Need package name or model name.")
|
|
72
|
+
await create_tables(sa, model_path, tables=tables)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from ..web.settings import get_config
|
|
5
|
+
from ..logging import print_info
|
|
6
|
+
from .common import Object, chainMap
|
|
7
|
+
from ..utils.watch import AppTask
|
|
8
|
+
from aiohttp import web
|
|
9
|
+
from ..web import app
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from ..utils.m2d import create_models, create_tables
|
|
12
|
+
from ..orm import create_engine
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_app(**config_kwargs: Any):
|
|
17
|
+
config = Object(config_kwargs)
|
|
18
|
+
print_info("Current mode: ", config.mode)
|
|
19
|
+
app_config = get_config(config.environment)
|
|
20
|
+
# print(environment, host, port, mode, path, entry)
|
|
21
|
+
# return
|
|
22
|
+
if config.mode == 'production':
|
|
23
|
+
App = app.create(env=config.environment, config=app_config, entry_model=config.entry)
|
|
24
|
+
App.run()
|
|
25
|
+
else:
|
|
26
|
+
aux_app = web.Application(
|
|
27
|
+
logger=None,
|
|
28
|
+
)
|
|
29
|
+
defaults = {"host": "0.0.0.0", "port": 9001, "path": None}
|
|
30
|
+
conf_server = app_config.get("server", {})
|
|
31
|
+
env_server = {"host": config.host, "port": config.port, "path": config.path}
|
|
32
|
+
server = chainMap(defaults, conf_server, env_server)
|
|
33
|
+
app_config["server"] = server
|
|
34
|
+
aux_port = int(server.port) + 1
|
|
35
|
+
task = AppTask(Path(".").resolve(), app_config, entry=config.entry, env=config.environment)
|
|
36
|
+
aux_app.cleanup_ctx.append(task.cleanup_ctx)
|
|
37
|
+
web.run_app(
|
|
38
|
+
aux_app,
|
|
39
|
+
access_log=None,
|
|
40
|
+
host=config.host,
|
|
41
|
+
port=aux_port,
|
|
42
|
+
print=None,
|
|
43
|
+
shutdown_timeout=0.1,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def run_gen(**config_kwargs: Any):
|
|
48
|
+
config = Object(config_kwargs)
|
|
49
|
+
configs = get_config(config.environment)
|
|
50
|
+
db_configs = configs.get("database")
|
|
51
|
+
if db_configs is None:
|
|
52
|
+
raise KeyError(
|
|
53
|
+
f"Can't find the database config in conf.{config.environment}.yaml"
|
|
54
|
+
)
|
|
55
|
+
db_config = db_configs.get(config.database)
|
|
56
|
+
if db_config is None:
|
|
57
|
+
raise KeyError(
|
|
58
|
+
f"Can't find the database config key ->{config.database} in conf.{config.environment}.yaml"
|
|
59
|
+
)
|
|
60
|
+
sa = await create_engine(**db_config)
|
|
61
|
+
if sa.pool is None:
|
|
62
|
+
return
|
|
63
|
+
model_path = os.path.join(os.path.abspath("."), config.path)
|
|
64
|
+
tables = None
|
|
65
|
+
if config.table != "ALL":
|
|
66
|
+
tables = config.table.split(",")
|
|
67
|
+
|
|
68
|
+
if config.create == "model":
|
|
69
|
+
await create_models(sa, config.path, tables=tables)
|
|
70
|
+
print_info(f"Model save at:{model_path}")
|
|
71
|
+
elif config.create == "table":
|
|
72
|
+
if model_path is None:
|
|
73
|
+
raise ValueError("Need package name or model name.")
|
|
74
|
+
await create_tables(sa, model_path, tables=tables)
|
cloudoll/utils/common.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from ..logging import warning
|
|
3
|
+
|
|
4
|
+
EADDRINUSE: int
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Object(dict):
|
|
8
|
+
# __setattr__ = dict.__setitem__
|
|
9
|
+
# __getattr__ = dict.__getitem__
|
|
10
|
+
def __setattr__(self, key, value):
|
|
11
|
+
self[key] = value
|
|
12
|
+
|
|
13
|
+
def __getattr__(self, key):
|
|
14
|
+
return self.get(key, None)
|
|
15
|
+
|
|
16
|
+
def __delattr__(self, key):
|
|
17
|
+
try:
|
|
18
|
+
del self[key]
|
|
19
|
+
except KeyError:
|
|
20
|
+
raise AttributeError(f"'DotDict' object has no attribute '{key}'")
|
|
21
|
+
|
|
22
|
+
def __getstate__(self):
|
|
23
|
+
"""to fix pickle.dumps"""
|
|
24
|
+
return dict(self)
|
|
25
|
+
|
|
26
|
+
def __setstate__(self, state):
|
|
27
|
+
self.update(state)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def chainMap(*dicts):
|
|
31
|
+
merged_dict = Object()
|
|
32
|
+
for d in dicts:
|
|
33
|
+
for key, value in d.items():
|
|
34
|
+
if key not in merged_dict or value is not None:
|
|
35
|
+
merged_dict[key] = value
|
|
36
|
+
return merged_dict
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def check_port_open(port: int, delay: float = 1) -> None:
|
|
40
|
+
loop = asyncio.get_running_loop()
|
|
41
|
+
# the "s = socket.socket; s.bind" approach sometimes says a port is in use when it's not
|
|
42
|
+
# this approach replicates aiohttp so should always give the same answer
|
|
43
|
+
for i in range(5, 0, -1):
|
|
44
|
+
try:
|
|
45
|
+
server = await loop.create_server(
|
|
46
|
+
asyncio.Protocol, host="0.0.0.0", port=port
|
|
47
|
+
)
|
|
48
|
+
except OSError as e:
|
|
49
|
+
if e.errno != EADDRINUSE:
|
|
50
|
+
raise
|
|
51
|
+
warning("port %d is already in use, waiting %d...", port, i)
|
|
52
|
+
await asyncio.sleep(delay)
|
|
53
|
+
else:
|
|
54
|
+
server.close()
|
|
55
|
+
await server.wait_closed()
|
|
56
|
+
return
|
|
57
|
+
raise Exception("The port {} is already is use".format(port))
|
cloudoll/utils/m2d.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from ..logging import print_info, print_warn
|
|
5
|
+
import importlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def snake_to_camel(snake_str):
|
|
9
|
+
components = snake_str.split("_")
|
|
10
|
+
# camel_str = components[0] + ''.join(x.title() for x in components[1:])
|
|
11
|
+
# x.capitalize() //first
|
|
12
|
+
# x.title() //all
|
|
13
|
+
# return camel_str[0].upper() + camel_str[1:]
|
|
14
|
+
return "".join(x.title() for x in components[0:])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def create_model(pool, table_name) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Create table
|
|
20
|
+
:params table name
|
|
21
|
+
"""
|
|
22
|
+
print_info(f"create model from {table_name}")
|
|
23
|
+
# rows = await pool.all(f"show full COLUMNS from `{table_name}`", None)
|
|
24
|
+
rows = await get_table_cols(pool, table_name)
|
|
25
|
+
# print(rows)
|
|
26
|
+
tb = f"\nclass {snake_to_camel(table_name)}(Model):\n\n"
|
|
27
|
+
tb += f"\t__table__ = '{table_name}'\n\n"
|
|
28
|
+
for f in rows:
|
|
29
|
+
fields = get_col(f, pool.driver)
|
|
30
|
+
# print("fields",fields)
|
|
31
|
+
name = fields["name"]
|
|
32
|
+
column_type = fields["column_type"]
|
|
33
|
+
values = []
|
|
34
|
+
if fields["primary_key"]:
|
|
35
|
+
values.append("primary_key=True")
|
|
36
|
+
if fields["charset"]:
|
|
37
|
+
values.append(f"charset='{fields['charset']}'")
|
|
38
|
+
if fields["max_length"] and column_type != "tinyint":
|
|
39
|
+
values.append(
|
|
40
|
+
f"max_length=({fields['max_length']})"
|
|
41
|
+
if isinstance(fields["max_length"], str) and "," in fields["max_length"]
|
|
42
|
+
else f"max_length={fields['max_length']}"
|
|
43
|
+
)
|
|
44
|
+
if fields["scale_length"]:
|
|
45
|
+
values.append(f"scale_length={fields['scale_length']}")
|
|
46
|
+
if fields["default"]:
|
|
47
|
+
values.append(f"default='{fields['default']}'")
|
|
48
|
+
if fields["auto_increment"]:
|
|
49
|
+
values.append("auto_increment=True")
|
|
50
|
+
if fields["NOT_NULL"]:
|
|
51
|
+
values.append("not_null=True")
|
|
52
|
+
if fields["created_generated"]:
|
|
53
|
+
values.append("created_generated=True")
|
|
54
|
+
if fields["update_generated"]:
|
|
55
|
+
values.append("update_generated=True")
|
|
56
|
+
if fields["comment"]:
|
|
57
|
+
values.append(f"comment='{fields['comment']}'")
|
|
58
|
+
if "unsigned" in column_type:
|
|
59
|
+
column_type = column_type.replace(" unsigned", "")
|
|
60
|
+
values.append("unsigned=True")
|
|
61
|
+
name = re.sub(r"\s", "", name)
|
|
62
|
+
tb += f"\t{name} = models.{ColTypes[column_type].value}Field({', '.join(values)})\n"
|
|
63
|
+
tb += "\n"
|
|
64
|
+
return tb
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def get_table_cols(pool, table_name):
|
|
68
|
+
if pool.driver == "mysql":
|
|
69
|
+
return await pool.all(f"show full COLUMNS from `{table_name}`", None)
|
|
70
|
+
elif pool.driver == "postgres":
|
|
71
|
+
pri_row = await pool.one(
|
|
72
|
+
f"SELECT column_name from information_schema.key_column_usage WHERE table_name = '{table_name}'",
|
|
73
|
+
None,
|
|
74
|
+
)
|
|
75
|
+
sql = f"""SELECT column_name Field,
|
|
76
|
+
column_default Default,
|
|
77
|
+
data_type column_type,
|
|
78
|
+
is_nullable Null,
|
|
79
|
+
numeric_precision num_length,
|
|
80
|
+
numeric_scale scale_length,
|
|
81
|
+
character_maximum_length str_length,
|
|
82
|
+
datetime_precision date_length,
|
|
83
|
+
col_description('{table_name}'::regclass, ordinal_position) Comment
|
|
84
|
+
FROM information_schema.columns WHERE table_name ='{table_name}'"""
|
|
85
|
+
|
|
86
|
+
rows = await pool.all(sql, None)
|
|
87
|
+
for row in rows:
|
|
88
|
+
row["Key"] = "PRI" if pri_row and pri_row["column_name"] == row["field"] else None
|
|
89
|
+
|
|
90
|
+
# print("rows", rows)
|
|
91
|
+
|
|
92
|
+
return rows
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError("Database not support.")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def get_all_tables(pool):
|
|
98
|
+
if pool.driver == "mysql":
|
|
99
|
+
return await pool.all("show tables", None)
|
|
100
|
+
elif pool.driver == "postgres":
|
|
101
|
+
return await pool.all(
|
|
102
|
+
"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'",
|
|
103
|
+
None,
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
raise ValueError("Database not support.")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def create_models(pool, save_path: str = None, tables: list = None):
|
|
110
|
+
"""
|
|
111
|
+
Create models
|
|
112
|
+
:params tables
|
|
113
|
+
:params save_path
|
|
114
|
+
"""
|
|
115
|
+
if tables and len(tables) > 0:
|
|
116
|
+
tbs = tables
|
|
117
|
+
else:
|
|
118
|
+
# result = await pool.all("show tables", None)
|
|
119
|
+
all_tables = await get_all_tables(pool)
|
|
120
|
+
tbs = [list(c.values())[0] for c in all_tables]
|
|
121
|
+
content = ""
|
|
122
|
+
import_line = "from cloudoll.orm.model import models, Model\n\n"
|
|
123
|
+
for t in tbs:
|
|
124
|
+
content += await create_model(pool, t)
|
|
125
|
+
if save_path:
|
|
126
|
+
first_append = True
|
|
127
|
+
if os.path.exists(save_path):
|
|
128
|
+
with open(save_path, "r", encoding="utf-8") as f:
|
|
129
|
+
t = f.readlines(5)
|
|
130
|
+
first_append = import_line not in t
|
|
131
|
+
with open(save_path, "a", encoding="utf-8") as f:
|
|
132
|
+
if first_append:
|
|
133
|
+
content = import_line + content
|
|
134
|
+
f.write(content)
|
|
135
|
+
else:
|
|
136
|
+
return content
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def create_table(pool, models: list, tables: list = None):
|
|
140
|
+
for model in models:
|
|
141
|
+
# print(table.__name__)
|
|
142
|
+
if model.__name__ == "Model":
|
|
143
|
+
continue
|
|
144
|
+
tb = model.__table__
|
|
145
|
+
|
|
146
|
+
if tables and tb not in tables:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if tb.startswith("v_"):
|
|
150
|
+
print_warn(f"{tb} look like a view so skip.")
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# sql = f"DROP TABLE IF EXISTS `{tb}`;\n"
|
|
154
|
+
sql = ""
|
|
155
|
+
sql += f"CREATE TABLE `{tb}` (\n"
|
|
156
|
+
|
|
157
|
+
# labels = get_filed(model)
|
|
158
|
+
labels = model.__fields__
|
|
159
|
+
sqls = []
|
|
160
|
+
for f in labels:
|
|
161
|
+
lb = getattr(model, f)
|
|
162
|
+
row = get_col_sql(lb)
|
|
163
|
+
sqls.append(row)
|
|
164
|
+
sql += ",\n".join(sqls)
|
|
165
|
+
sql += ") ENGINE=InnoDB;"
|
|
166
|
+
|
|
167
|
+
print_info(f"create table {tb} ...\n\n")
|
|
168
|
+
# print(sql)
|
|
169
|
+
await pool.query(sql, None)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def create_tables(pool, model_name: str = None, tables: list = None):
|
|
173
|
+
|
|
174
|
+
# parts = model_name.split('.')
|
|
175
|
+
# package_name = '.'.join(parts[:-1]) or '.'
|
|
176
|
+
# module_name = parts[-1]
|
|
177
|
+
# print(module_name,package_name)
|
|
178
|
+
# models = importlib.import_module(module_name,package_name)
|
|
179
|
+
# module_classes = [cls for cls in vars(module_name).values() if isinstance(cls, type)]
|
|
180
|
+
|
|
181
|
+
name = os.path.basename(model_name)[:-3]
|
|
182
|
+
# 创建模块规范
|
|
183
|
+
module_spec = importlib.util.spec_from_file_location(name, model_name)
|
|
184
|
+
# 创建模块对象
|
|
185
|
+
module = importlib.util.module_from_spec(module_spec)
|
|
186
|
+
# 执行模块并加载其内容
|
|
187
|
+
module_spec.loader.exec_module(module)
|
|
188
|
+
# 获取模块中的所有实体
|
|
189
|
+
module_classes = [cls for cls in vars(module).values() if isinstance(cls, type)]
|
|
190
|
+
|
|
191
|
+
# print(module_classes)
|
|
192
|
+
await create_table(pool, models=module_classes, tables=tables)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_col(field, driver="mysql"):
|
|
196
|
+
if driver == "mysql":
|
|
197
|
+
fields = {
|
|
198
|
+
"name": field["Field"],
|
|
199
|
+
"column_type": None,
|
|
200
|
+
"primary_key": field["Key"] == "PRI",
|
|
201
|
+
"default": field["Default"],
|
|
202
|
+
"charset": field["Collation"],
|
|
203
|
+
"max_length": None,
|
|
204
|
+
"scale_length":None,
|
|
205
|
+
"auto_increment": field["Extra"] == "auto_increment",
|
|
206
|
+
"NOT_NULL": field["Null"] == "NO",
|
|
207
|
+
"created_generated": "DEFAULT_GENERATED" == field["Extra"],
|
|
208
|
+
"update_generated": "on update" in field["Extra"],
|
|
209
|
+
"comment": field["Comment"],
|
|
210
|
+
}
|
|
211
|
+
field_type = field["Type"]
|
|
212
|
+
t = re.match(r"(\w+)[(](.*?)[)]", field_type)
|
|
213
|
+
if not t:
|
|
214
|
+
fields["column_type"] = field_type
|
|
215
|
+
else:
|
|
216
|
+
fields["column_type"] = t.groups()[0]
|
|
217
|
+
fields["max_length"] = t.groups()[1]
|
|
218
|
+
return fields
|
|
219
|
+
elif driver == "postgres" or driver == "postgresql":
|
|
220
|
+
fields = {
|
|
221
|
+
"name": field["field"],
|
|
222
|
+
"column_type": field["column_type"].replace(" ", "_"),
|
|
223
|
+
"primary_key": field["Key"] == "PRI",
|
|
224
|
+
"default": field["default"],
|
|
225
|
+
"charset": None,
|
|
226
|
+
"max_length": field["num_length"]
|
|
227
|
+
or field["str_length"]
|
|
228
|
+
or field["date_length"],
|
|
229
|
+
"scale_length": field["scale_length"],
|
|
230
|
+
"auto_increment": None,
|
|
231
|
+
"NOT_NULL": field["null"] == "NO",
|
|
232
|
+
"created_generated": None,
|
|
233
|
+
"update_generated": None,
|
|
234
|
+
"comment": field["comment"],
|
|
235
|
+
}
|
|
236
|
+
return fields
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def get_col_sql(field):
|
|
240
|
+
sql = f"`{field.name}` {field.column_type}"
|
|
241
|
+
|
|
242
|
+
if field.max_length:
|
|
243
|
+
if isinstance(field.max_length, tuple):
|
|
244
|
+
sql += f"{field.max_length}"
|
|
245
|
+
elif field.scale_length:
|
|
246
|
+
sql += f"({field.max_length},{field.scale_length})"
|
|
247
|
+
else:
|
|
248
|
+
sql += f"({field.max_length})"
|
|
249
|
+
|
|
250
|
+
if field.charset:
|
|
251
|
+
# _ci 不区分大小写 _cs Yes
|
|
252
|
+
cs = field.charset.split("_")[0]
|
|
253
|
+
sql += f" CHARACTER SET {cs} COLLATE {field.charset}"
|
|
254
|
+
if field.primary_key:
|
|
255
|
+
sql += " PRIMARY KEY"
|
|
256
|
+
if field.auto_increment:
|
|
257
|
+
# todo: postgres SERIAL
|
|
258
|
+
sql += " AUTO_INCREMENT"
|
|
259
|
+
if field.NOT_NULL:
|
|
260
|
+
sql += " NOT NULL"
|
|
261
|
+
if field.default:
|
|
262
|
+
sql += " DEFAULT " + (
|
|
263
|
+
field.default if "(" in field.default else f"'{field.default}'"
|
|
264
|
+
)
|
|
265
|
+
# else:
|
|
266
|
+
# sql += " DEFAULT NULL"
|
|
267
|
+
# print(field.name, field.update_generated)
|
|
268
|
+
if field.update_generated:
|
|
269
|
+
sql += " ON UPDATE " + (
|
|
270
|
+
field.default if "(" in field.default else f"'{field.default}'"
|
|
271
|
+
)
|
|
272
|
+
if field.comment:
|
|
273
|
+
sql += f" COMMENT '{field.comment}'"
|
|
274
|
+
|
|
275
|
+
return sql
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def get_filed(model):
|
|
279
|
+
return [
|
|
280
|
+
attr
|
|
281
|
+
for attr in dir(model)
|
|
282
|
+
if not callable(getattr(model, attr)) and not attr.startswith("__")
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class ColTypes(enum.Enum):
|
|
287
|
+
char = "Char"
|
|
288
|
+
varchar = "VarChar"
|
|
289
|
+
text = "Text"
|
|
290
|
+
longtext = "LongText"
|
|
291
|
+
mediumtext = "MediumText"
|
|
292
|
+
tinyint = "Boolean"
|
|
293
|
+
smallint = "Integer"
|
|
294
|
+
mediuint = "Integer"
|
|
295
|
+
int = "Integer"
|
|
296
|
+
bigint = "BigInteger"
|
|
297
|
+
float = "Float"
|
|
298
|
+
double = "Double"
|
|
299
|
+
decimal = "Decimal"
|
|
300
|
+
numeric = "Numeric"
|
|
301
|
+
date = "Date"
|
|
302
|
+
datetime = "Datetime"
|
|
303
|
+
timestamp = "Timestamp"
|
|
304
|
+
json = "Json"
|
|
305
|
+
# pg
|
|
306
|
+
timestamp_with_time_zone = "Timestamp"
|
|
307
|
+
timestamp_without_time_zone = "Timestamp"
|
|
308
|
+
character_varying = "VarChar"
|
|
309
|
+
character = "Char"
|
|
310
|
+
integer = "Integer"
|
|
311
|
+
boolean = "Boolean"
|