cli impl
This commit is contained in:
parent
a5a5de529b
commit
d385647fba
1
.gitignore
vendored
1
.gitignore
vendored
@ -141,3 +141,4 @@ dmypy.json
|
||||
cython_debug/
|
||||
|
||||
.idea
|
||||
migrations
|
70
alice/cli.py
70
alice/cli.py
@ -3,8 +3,13 @@ import os
|
||||
import sys
|
||||
from enum import Enum
|
||||
|
||||
import click
|
||||
from click import BadParameter, ClickException
|
||||
import asyncclick as click
|
||||
from asyncclick import BadParameter, ClickException
|
||||
from tortoise import Tortoise, generate_schema_for_client
|
||||
|
||||
from alice.backends.mysql import MysqlDDL
|
||||
from alice.migrate import Migrate
|
||||
from alice.utils import get_app_connection
|
||||
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
@ -15,15 +20,15 @@ class Color(str, Enum):
|
||||
|
||||
|
||||
@click.group(context_settings={'help_option_names': ['-h', '--help']})
|
||||
@click.option('-c', '--config', default='settings',
|
||||
@click.option('-c', '--config', default='settings', show_default=True,
|
||||
help='Tortoise-ORM config module, will read config variable from it, default is `settings`.')
|
||||
@click.option('-t', '--tortoise-orm', default='TORTOISE_ORM',
|
||||
@click.option('-t', '--tortoise-orm', default='TORTOISE_ORM', show_default=True,
|
||||
help='Tortoise-ORM config dict variable, default is `TORTOISE_ORM`.')
|
||||
@click.option('-l', '--location', default='./migrations',
|
||||
@click.option('-l', '--location', default='./migrations', show_default=True,
|
||||
help='Migrate store location, default is `./migrations`.')
|
||||
@click.option('--connection', default='default', help='Tortoise-ORM connection name, default is `default`.')
|
||||
@click.option('-a', '--app', default='models', show_default=True, help='Tortoise-ORM app name, default is `models`.')
|
||||
@click.pass_context
|
||||
def cli(ctx, config, tortoise_orm, location, connection):
|
||||
async def cli(ctx, config, tortoise_orm, location, app):
|
||||
ctx.ensure_object(dict)
|
||||
try:
|
||||
config_module = importlib.import_module(config)
|
||||
@ -31,10 +36,16 @@ def cli(ctx, config, tortoise_orm, location, connection):
|
||||
if not config:
|
||||
raise BadParameter(param_hint=['--config'],
|
||||
message=f'Can\'t get "{tortoise_orm}" from module "{config_module}"')
|
||||
|
||||
await Tortoise.init(config=config)
|
||||
|
||||
ctx.obj['config'] = config
|
||||
ctx.obj['location'] = location
|
||||
if connection not in config.get('connections').keys():
|
||||
raise BadParameter(param_hint=['--connection'], message=f'No connection found in "{config}"')
|
||||
ctx.obj['app'] = app
|
||||
|
||||
if app not in config.get('apps').keys():
|
||||
raise BadParameter(param_hint=['--app'], message=f'No app found in "{config}"')
|
||||
|
||||
except ModuleNotFoundError:
|
||||
raise BadParameter(param_hint=['--tortoise-orm'], message=f'No module named "{config}"')
|
||||
|
||||
@ -43,6 +54,17 @@ def cli(ctx, config, tortoise_orm, location, connection):
|
||||
@click.pass_context
|
||||
def migrate(ctx):
|
||||
config = ctx.obj['config']
|
||||
location = ctx.obj['location']
|
||||
app = ctx.obj['app']
|
||||
|
||||
old_models = Migrate.read_old_models(app, location)
|
||||
print(old_models)
|
||||
|
||||
new_models = Tortoise.apps.get(app)
|
||||
print(new_models)
|
||||
|
||||
ret = Migrate(MysqlDDL(get_app_connection(config, app))).diff_models(old_models, new_models)
|
||||
print(ret)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@ -58,26 +80,38 @@ def downgrade():
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--safe', is_flag=True, default=True,
|
||||
help='When set to true, creates the table only when it does not already exist..', show_default=True)
|
||||
@click.pass_context
|
||||
def initdb():
|
||||
pass
|
||||
async def initdb(ctx, safe):
|
||||
location = ctx.obj['location']
|
||||
config = ctx.obj['config']
|
||||
app = ctx.obj['app']
|
||||
|
||||
await generate_schema_for_client(get_app_connection(config, app), safe)
|
||||
|
||||
Migrate.write_old_models(app, location)
|
||||
|
||||
click.secho(f'Success initdb for app `{app}`', fg=Color.green)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--overwrite', type=bool, default=False, help='Overwrite old_models.py.')
|
||||
@click.option('--overwrite', is_flag=True, default=False, help=f'Overwrite {Migrate.old_models}.', show_default=True)
|
||||
@click.pass_context
|
||||
def init(ctx, overwrite):
|
||||
location = ctx.obj['location']
|
||||
config = ctx.obj['config']
|
||||
if not os.path.isdir(location) or overwrite:
|
||||
app = ctx.obj['app']
|
||||
if not os.path.isdir(location):
|
||||
os.mkdir(location)
|
||||
connections = config.get('connections').keys()
|
||||
for connection in connections:
|
||||
dirname = os.path.join(location, connection)
|
||||
dirname = os.path.join(location, app)
|
||||
if not os.path.isdir(dirname):
|
||||
os.mkdir(dirname)
|
||||
click.secho(f'Success create migrate location {dirname}', fg=Color.green)
|
||||
if overwrite:
|
||||
pass
|
||||
Migrate.write_old_models(app, location)
|
||||
else:
|
||||
raise ClickException('Already inited')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli(_anyio_backend='asyncio')
|
||||
|
@ -1,8 +1,12 @@
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
from copy import deepcopy
|
||||
|
||||
import dill
|
||||
from typing import List, Type, Dict
|
||||
|
||||
from tortoise import Model, ForeignKeyFieldInstance
|
||||
from tortoise import Model, ForeignKeyFieldInstance, Tortoise
|
||||
from tortoise.fields import Field
|
||||
|
||||
from alice.backends import DDL
|
||||
@ -11,11 +15,30 @@ from alice.backends import DDL
|
||||
class Migrate:
|
||||
operators: List
|
||||
ddl: DDL
|
||||
old_models = 'old_models.pickle'
|
||||
|
||||
def __init__(self, ddl: DDL):
|
||||
self.operators = []
|
||||
self.ddl = ddl
|
||||
|
||||
@staticmethod
|
||||
def write_old_models(app, location):
|
||||
ret = Tortoise.apps.get(app)
|
||||
old_models = {}
|
||||
for k, v in ret.items():
|
||||
old_models[k] = deepcopy(v)
|
||||
|
||||
dirname = os.path.join(location, app)
|
||||
|
||||
with open(os.path.join(dirname, Migrate.old_models), 'wb') as f:
|
||||
dill.dump(old_models, f, )
|
||||
|
||||
@staticmethod
|
||||
def read_old_models(app, location):
|
||||
dirname = os.path.join(location, app)
|
||||
with open(os.path.join(dirname, Migrate.old_models), 'rb') as f:
|
||||
return dill.load(f, )
|
||||
|
||||
def diff_models_module(self, old_models_module, new_models_module):
|
||||
old_module = importlib.import_module(old_models_module)
|
||||
old_models = {}
|
||||
|
@ -1,17 +1,11 @@
|
||||
import re
|
||||
from tortoise import Tortoise
|
||||
|
||||
|
||||
def cp_models(old_model_file, new_model_file, new_app):
|
||||
def get_app_connection(config: dict, app: str):
|
||||
"""
|
||||
cp models file to old_models.py and rename model app
|
||||
:param old_app:
|
||||
:param new_app:
|
||||
:param old_model_file:
|
||||
:param new_model_file:
|
||||
:return:r
|
||||
get tortoise connection by app
|
||||
:param config:
|
||||
:param app:
|
||||
:return:
|
||||
"""
|
||||
pattern = r'(ManyToManyField|ForeignKeyField|OneToOneField)\((model_name)?(\"|\')(?P<app>\w+).+\)'
|
||||
with open(old_model_file, 'r') as f:
|
||||
content = f.read()
|
||||
ret = re.sub(pattern, rf'{new_app} \g<app>', content)
|
||||
print(ret)
|
||||
return Tortoise.get_connection(config.get('apps').get(app).get('default_connection')),
|
||||
|
92
poetry.lock
generated
92
poetry.lock
generated
@ -20,6 +20,47 @@ optional = false
|
||||
python-versions = ">=3.6"
|
||||
version = "0.13.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
name = "anyio"
|
||||
optional = false
|
||||
python-versions = ">=3.5.3"
|
||||
version = "1.3.0"
|
||||
|
||||
[package.dependencies]
|
||||
async-generator = "*"
|
||||
sniffio = ">=1.1"
|
||||
|
||||
[package.extras]
|
||||
curio = ["curio (>=0.9)"]
|
||||
doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||
test = ["coverage (>=4.5)", "hypothesis (>=4.0)", "pytest (>=3.7.2)", "uvloop"]
|
||||
trio = ["trio (>=0.12)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Async generators and context managers for Python 3.5+"
|
||||
name = "async-generator"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "1.10"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A simple anyio-compatible fork of Click, for powerful command line utilities."
|
||||
name = "asyncclick"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
version = "7.0.9"
|
||||
|
||||
[package.dependencies]
|
||||
anyio = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage", "pytest-runner", "pytest-trio", "pytest (>=3)", "sphinx", "tox"]
|
||||
docs = ["sphinx"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Enhance the standard unittest package with features for testing asyncio libraries"
|
||||
@ -48,14 +89,6 @@ optional = false
|
||||
python-versions = "*"
|
||||
version = "2.1.3"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Composable command line interface toolkit"
|
||||
name = "click"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "7.1.2"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
@ -75,6 +108,17 @@ idna = ["idna (>=2.1)"]
|
||||
pep8test = ["flake8", "flake8-import-order", "pep8-naming"]
|
||||
test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "serialize all of python"
|
||||
name = "dill"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*"
|
||||
version = "0.3.1.1"
|
||||
|
||||
[package.extras]
|
||||
graph = ["objgraph (>=1.7.2)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "the modular source code checker: pep8 pyflakes and co"
|
||||
@ -156,6 +200,14 @@ optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "1.14.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
name = "sniffio"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "1.1.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "tasks runner for python projects"
|
||||
@ -203,7 +255,7 @@ python-versions = "*"
|
||||
version = "3.7.4.2"
|
||||
|
||||
[metadata]
|
||||
content-hash = "708876857d4653fd45cb251e9a1689c4158966f42da2efca1e8167becef89837"
|
||||
content-hash = "4809b238c12841eb28a6517843828716f207e9ed41b273bb681ae7a831e34af4"
|
||||
python-versions = "^3.8"
|
||||
|
||||
[metadata.files]
|
||||
@ -215,6 +267,17 @@ aiosqlite = [
|
||||
{file = "aiosqlite-0.13.0-py3-none-any.whl", hash = "sha256:50688c40632ae249f986ab3ae2c66a45c0535b84a5d4aae0e0be572b5fed6909"},
|
||||
{file = "aiosqlite-0.13.0.tar.gz", hash = "sha256:6e92961ae9e606b43b05e29b129e346b29e400fcbd63e3c0c564d89230257645"},
|
||||
]
|
||||
anyio = [
|
||||
{file = "anyio-1.3.0-py3-none-any.whl", hash = "sha256:db2c3d21576870b95d4fd0b8f4a0f9c64057f777c578f3a8127179a17c8c067e"},
|
||||
{file = "anyio-1.3.0.tar.gz", hash = "sha256:7deae0315dd10aa41c21528b83352e4b52f44e6153a21081a3d1cd8c03728e46"},
|
||||
]
|
||||
async-generator = [
|
||||
{file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"},
|
||||
{file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"},
|
||||
]
|
||||
asyncclick = [
|
||||
{file = "asyncclick-7.0.9.tar.gz", hash = "sha256:62cebf3eca36d973802e2dd521ca1db11c5bf4544e9795e093d1a53cb688a8c2"},
|
||||
]
|
||||
asynctest = [
|
||||
{file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"},
|
||||
{file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"},
|
||||
@ -252,10 +315,6 @@ cffi = [
|
||||
ciso8601 = [
|
||||
{file = "ciso8601-2.1.3.tar.gz", hash = "sha256:bdbb5b366058b1c87735603b23060962c439ac9be66f1ae91e8c7dbd7d59e262"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
||||
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"},
|
||||
{file = "cryptography-2.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b"},
|
||||
@ -277,6 +336,9 @@ cryptography = [
|
||||
{file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"},
|
||||
{file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"},
|
||||
]
|
||||
dill = [
|
||||
{file = "dill-0.3.1.1.tar.gz", hash = "sha256:42d8ef819367516592a825746a18073ced42ca169ab1f5f4044134703e7a049c"},
|
||||
]
|
||||
flake8 = [
|
||||
{file = "flake8-3.8.1-py2.py3-none-any.whl", hash = "sha256:6c1193b0c3f853ef763969238f6c81e9e63ace9d024518edc020d5f1d6d93195"},
|
||||
{file = "flake8-3.8.1.tar.gz", hash = "sha256:ea6623797bf9a52f4c9577d780da0bb17d65f870213f7b5bcc9fca82540c31d5"},
|
||||
@ -313,6 +375,10 @@ six = [
|
||||
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
|
||||
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
|
||||
]
|
||||
sniffio = [
|
||||
{file = "sniffio-1.1.0-py3-none-any.whl", hash = "sha256:20ed6d5b46f8ae136d00b9dcb807615d83ed82ceea6b2058cecb696765246da5"},
|
||||
{file = "sniffio-1.1.0.tar.gz", hash = "sha256:8e3810100f69fe0edd463d02ad407112542a11ffdc29f67db2bf3771afb87a21"},
|
||||
]
|
||||
taskipy = [
|
||||
{file = "taskipy-1.2.1-py3-none-any.whl", hash = "sha256:99bdaf5b19791c2345806847147e0fc2d28e1ac9446058def5a8b6b3fc9f23e2"},
|
||||
{file = "taskipy-1.2.1.tar.gz", hash = "sha256:5eb2c3b1606c896c7fa799848e71e8883b880759224958d07ba760e5db263175"},
|
||||
|
@ -8,7 +8,8 @@ authors = ["long2ice <long2ice@gmail.com>"]
|
||||
python = "^3.8"
|
||||
tortoise-orm = {git = "https://github.com/tortoise/tortoise-orm.git", branch = "develop"}
|
||||
aiomysql = "*"
|
||||
click = "*"
|
||||
asyncclick = "*"
|
||||
dill = "*"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
taskipy = "*"
|
||||
|
@ -1,13 +1,17 @@
|
||||
aiomysql==0.0.20
|
||||
aiosqlite==0.13.0
|
||||
anyio==1.3.0
|
||||
async-generator==1.10
|
||||
asyncclick==7.0.9
|
||||
cffi==1.14.0
|
||||
ciso8601==2.1.3; sys_platform != "win32" and implementation_name == "cpython"
|
||||
click==7.1.2
|
||||
cryptography==2.9.2
|
||||
dill==0.3.1.1
|
||||
iso8601==0.1.12; sys_platform == "win32" or implementation_name != "cpython"
|
||||
pycparser==2.20
|
||||
pymysql==0.9.2
|
||||
pypika==0.37.6
|
||||
six==1.14.0
|
||||
tortoise-orm==0.16.10
|
||||
sniffio==1.1.0
|
||||
-e git+https://github.com/tortoise/tortoise-orm.git@72f84f0848dc68041157f03e60cd1c92b0ee5137#egg=tortoise-orm
|
||||
typing-extensions==3.7.4.2
|
||||
|
@ -1,69 +0,0 @@
|
||||
import datetime
|
||||
from enum import IntEnum
|
||||
from tortoise import fields, Model
|
||||
|
||||
|
||||
class ProductType(IntEnum):
|
||||
article = 1
|
||||
page = 2
|
||||
|
||||
|
||||
class PermissionAction(IntEnum):
|
||||
create = 1
|
||||
delete = 2
|
||||
update = 3
|
||||
read = 4
|
||||
|
||||
|
||||
class Status(IntEnum):
|
||||
on = 1
|
||||
off = 0
|
||||
|
||||
|
||||
class User(Model):
|
||||
username = fields.CharField(max_length=20, unique=True)
|
||||
password = fields.CharField(max_length=200)
|
||||
last_login = fields.DatetimeField(description='Last Login', default=datetime.datetime.now)
|
||||
is_active = fields.BooleanField(default=True, description='Is Active')
|
||||
is_superuser = fields.BooleanField(default=False, description='Is SuperUser')
|
||||
avatar = fields.CharField(max_length=200, default='')
|
||||
intro = fields.TextField(default='')
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.username}'
|
||||
|
||||
|
||||
class Category(Model):
|
||||
slug = fields.CharField(max_length=200)
|
||||
name = fields.CharField(max_length=200)
|
||||
user = fields.ForeignKeyField('new_models.User', description='User')
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.name}'
|
||||
|
||||
|
||||
class Product(Model):
|
||||
categories = fields.ManyToManyField('new_models.Category')
|
||||
name = fields.CharField(max_length=50)
|
||||
view_num = fields.IntField(description='View Num')
|
||||
sort = fields.IntField()
|
||||
is_reviewed = fields.BooleanField(description='Is Reviewed')
|
||||
type = fields.IntEnumField(ProductType, description='Product Type')
|
||||
image = fields.CharField(max_length=200)
|
||||
body = fields.TextField()
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.name}'
|
||||
|
||||
|
||||
class Config(Model):
|
||||
label = fields.CharField(max_length=200)
|
||||
key = fields.CharField(max_length=20)
|
||||
value = fields.JSONField()
|
||||
status: Status = fields.IntEnumField(Status, default=Status.on)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.pk}#{self.label}'
|
9
tests/test_utils.py
Normal file
9
tests/test_utils.py
Normal file
@ -0,0 +1,9 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from alice.utils import cp_models
|
||||
|
||||
|
||||
class TestUtils(TestCase):
|
||||
def test_cp_models(self):
|
||||
ret = cp_models('models.py', 'new_models.py', 'new_models')
|
||||
print(ret)
|
Loading…
x
Reference in New Issue
Block a user