complete InspectDb

This commit is contained in:
long2ice 2020-12-25 21:44:26 +08:00
parent 55a6d4bbc7
commit 5ae8b9e85f
2 changed files with 91 additions and 9 deletions

View File

@ -7,8 +7,8 @@
## Introduction ## Introduction
Aerich is a database migrations tool for Tortoise-ORM, which like alembic for SQLAlchemy, or Django ORM with it\'s Aerich is a database migrations tool for Tortoise-ORM, which like alembic for SQLAlchemy, or Django ORM with it\'s own
own migrations solution. migrations solution.
**Important: You can only use absolutely import in your `models.py` to make `aerich` work.** **Important: You can only use absolutely import in your `models.py` to make `aerich` work.**
@ -40,14 +40,14 @@ Commands:
history List all migrate items. history List all migrate items.
init Init config file and generate root migrate location. init Init config file and generate root migrate location.
init-db Generate schema and generate app migrate location. init-db Generate schema and generate app migrate location.
inspectdb Introspects the database tables to standard output as...
migrate Generate migrate changes file. migrate Generate migrate changes file.
upgrade Upgrade to latest version. upgrade Upgrade to latest version.
``` ```
## Usage ## Usage
You need add `aerich.models` to your `Tortoise-ORM` config first, You need add `aerich.models` to your `Tortoise-ORM` config first, example:
example:
```python ```python
TORTOISE_ORM = { TORTOISE_ORM = {
@ -109,7 +109,8 @@ Success migrate 1_202029051520102929_drop_column.sql
Format of migrate filename is Format of migrate filename is
`{version_num}_{datetime}_{name|update}.sql`. `{version_num}_{datetime}_{name|update}.sql`.
And if `aerich` guess you are renaming a column, it will ask `Rename {old_column} to {new_column} [True]`, you can choice `True` to rename column without column drop, or choice `False` to drop column then create. And if `aerich` guess you are renaming a column, it will ask `Rename {old_column} to {new_column} [True]`, you can
choice `True` to rename column without column drop, or choice `False` to drop column then create.
### Upgrade to latest version ### Upgrade to latest version
@ -163,6 +164,33 @@ Now your db rollback to specified version.
1_202029051520102929_drop_column.sql 1_202029051520102929_drop_column.sql
``` ```
### Inspect db tables to TortoiseORM model
```shell
Usage: aerich inspectdb [OPTIONS]
Introspects the database tables to standard output as TortoiseORM model.
Options:
-t, --table TEXT Which tables to inspect.
-h, --help Show this message and exit.
```
Inspect all tables and print to console:
```shell
aerich --app models inspectdb -t user
```
Inspect a specified table in default app and redirect to `models.py`:
```shell
aerich inspectdb -t user > models.py
```
Note that this command is restricted, which is not supported in some solutions, such as `IntEnumField`
and `ForeignKeyField` and so on.
### Multiple databases ### Multiple databases
```python ```python
@ -173,7 +201,7 @@ tortoise_orm = {
}, },
"apps": { "apps": {
"models": {"models": ["tests.models", "aerich.models"], "default_connection": "default"}, "models": {"models": ["tests.models", "aerich.models"], "default_connection": "default"},
"models_second": {"models": ["tests.models_second"], "default_connection": "second",}, "models_second": {"models": ["tests.models_second"], "default_connection": "second", },
}, },
} }
``` ```

View File

@ -1,3 +1,4 @@
import sys
from typing import List, Optional from typing import List, Optional
from ddlparse import DdlParse from ddlparse import DdlParse
@ -6,6 +7,17 @@ from tortoise.backends.mysql.client import MySQLSchemaGenerator
class InspectDb: class InspectDb:
_table_template = "class {table}(Model):\n"
_field_template_mapping = {
"INT": " {field} = fields.IntField({pk}{unique}{comment})",
"SMALLINT": " {field} = fields.IntField({pk}{unique}{comment})",
"TINYINT": " {field} = fields.BooleanField({null}{default}{comment})",
"VARCHAR": " {field} = fields.CharField({pk}{unique}{length}{null}{default}{comment})",
"LONGTEXT": " {field} = fields.TextField({null}{default}{comment})",
"TEXT": " {field} = fields.TextField({null}{default}{comment})",
"DATETIME": " {field} = fields.DatetimeField({null}{default}{comment})",
}
def __init__(self, conn: BaseDBAsyncClient, tables: Optional[List[str]] = None): def __init__(self, conn: BaseDBAsyncClient, tables: Optional[List[str]] = None):
self.conn = conn self.conn = conn
self.tables = tables self.tables = tables
@ -14,9 +26,9 @@ class InspectDb:
async def show_create_tables(self): async def show_create_tables(self):
if self.DIALECT == MySQLSchemaGenerator.DIALECT: if self.DIALECT == MySQLSchemaGenerator.DIALECT:
if not self.tables: if not self.tables:
sql_tables = f"SELECT table_name FROM information_schema.tables WHERE table_schema = '{self.conn.database}';" sql_tables = f"SELECT table_name FROM information_schema.tables WHERE table_schema = '{self.conn.database}';" # nosec: B608
ret = await self.conn.execute_query(sql_tables) ret = await self.conn.execute_query(sql_tables)
self.tables = map(lambda x: x[0], ret) self.tables = map(lambda x: x["TABLE_NAME"], ret[1])
for table in self.tables: for table in self.tables:
sql_show_create_table = f"SHOW CREATE TABLE {table}" sql_show_create_table = f"SHOW CREATE TABLE {table}"
ret = await self.conn.execute_query(sql_show_create_table) ret = await self.conn.execute_query(sql_show_create_table)
@ -26,7 +38,49 @@ class InspectDb:
async def inspect(self): async def inspect(self):
ddl_list = self.show_create_tables() ddl_list = self.show_create_tables()
result = "from tortoise import Model, fields\n\n\n"
tables = []
async for ddl in ddl_list: async for ddl in ddl_list:
parser = DdlParse(ddl, DdlParse.DATABASE.mysql) parser = DdlParse(ddl, DdlParse.DATABASE.mysql)
table = parser.parse() table = parser.parse()
print(table) name = table.name.title()
columns = table.columns
fields = []
model = self._table_template.format(table=name)
for column_name, column in columns.items():
comment = default = length = unique = null = pk = ""
if column.primary_key:
pk = "pk=True, "
if column.unique:
unique = "unique=True, "
if column.data_type == "VARCHAR":
length = f"max_length={column.length}, "
if not column.not_null:
null = "null=True, "
if column.default is not None:
if column.data_type == "TINYINT":
default = f"default={'True' if column.default == '1' else 'False'}, "
elif column.data_type == "DATETIME":
if "CURRENT_TIMESTAMP" in column.default:
if "ON UPDATE CURRENT_TIMESTAMP" in ddl:
default = "auto_now_add=True, "
else:
default = "auto_now=True, "
else:
default = f"default={column.default}, "
if column.comment:
comment = f"description='{column.comment}', "
field = self._field_template_mapping[column.data_type].format(
field=column_name,
pk=pk,
unique=unique,
length=length,
null=null,
default=default,
comment=comment,
)
fields.append(field)
tables.append(model + "\n".join(fields))
sys.stdout.write(result + "\n\n\n".join(tables))