diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 3d1304e..86108d4 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,4 @@ -**v0.8.2** +**v0.8.3** 1. Added fundamental concept of TableMetaModel - class that unifies metadata parsed from different classes/ORM models types/DDLs to one standard to allow easy way convert one models to another in next releases it will be used for converter from one type of models to another. 2. Fixed issue: https://github.com/xnuinside/omymodels/issues/18 "NOW() not recognized as now()" diff --git a/README.md b/README.md index e9580ce..bb6949e 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ Please describe issue that you want to solve and open the PR, I will review it a Any questions? Ping me in Telegram: https://t.me/xnuinside ## Changelog -**v0.8.2** +**v0.8.3** 1. Added fundamental concept of TableMetaModel - class that unifies metadata parsed from different classes/ORM models types/DDLs to one standard to allow easy way convert one models to another in next releases it will be used for converter from one type of models to another. 2. Fixed issue: https://github.com/xnuinside/omymodels/issues/18 "NOW() not recognized as now()" diff --git a/docs/README.rst b/docs/README.rst index 4c6727f..9989269 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -293,7 +293,7 @@ Any questions? Ping me in Telegram: https://t.me/xnuinside Changelog --------- -**v0.8.2** +**v0.8.3** #. Added fundamental concept of TableMetaModel - class that unifies metadata parsed from different classes/ORM models types/DDLs to one standard to allow easy way convert one models to another diff --git a/omymodels/common.py b/omymodels/common.py index 14dec19..a13c266 100644 --- a/omymodels/common.py +++ b/omymodels/common.py @@ -5,14 +5,13 @@ from typing import Optional, List, Dict -from typing_extensions import final from simple_ddl_parser import DDLParser, parse_from_file from omymodels.gino import core as g from omymodels.pydantic import core as p from omymodels.dataclass import core as d from omymodels.sqlalchemy import core as s from omymodels.sqlalchemy_core import core as sc -from omymodels.meta_model import TableMeta, Column, Type +from omymodels.meta_model import TableMeta, Type def get_tables_information( @@ -46,7 +45,7 @@ def create_models( data = get_tables_information(ddl, ddl_path) data = remove_quotes_from_strings(data) data = convert_ddl_to_models(data) - if not data['tables']: + if not data["tables"]: sys.exit(0) # generate code output = generate_models_file( @@ -60,15 +59,15 @@ def create_models( def convert_ddl_to_models(data): - final_data = {'tables': [], 'types': []} + final_data = {"tables": [], "types": []} tables = [] - for table in data['tables']: + for table in data["tables"]: tables.append(TableMeta(**table)) - final_data['tables'] = tables - _types = [] - for _type in data['types']: + final_data["tables"] = tables + _types = [] + for _type in data["types"]: _types.append(Type(**_type)) - final_data['types'] = _types + final_data["types"] = _types return final_data @@ -113,20 +112,20 @@ def generate_models_file( schema_global=schema_global, defaults_off=defaults_off, ) - header = model_generator.create_header( - data["tables"], schema=schema_global) + header = model_generator.create_header(data["tables"], schema=schema_global) output = render_jinja2_template(models_type, models_str, header) return output def render_jinja2_template(models_type: str, models: str, headers: str) -> str: - template_file = pathlib.Path(__file__).parent / models_type / f'{models_type}.jinja2' + template_file = ( + pathlib.Path(__file__).parent / models_type / f"{models_type}.jinja2" + ) with open(template_file) as t: template = t.read() template = Template(template) - params = {"models": models, - "headers": headers} + params = {"models": models, "headers": headers} return template.render(**params) diff --git a/omymodels/dataclass/core.py b/omymodels/dataclass/core.py index 97aaefa..8c71674 100644 --- a/omymodels/dataclass/core.py +++ b/omymodels/dataclass/core.py @@ -37,7 +37,7 @@ def generate_attr(self, column: Dict, defaults_off: bool) -> str: self.typing_imports.add(_type.split("[")[0]) elif "datetime" in _type: self.datetime_import = True - self.additional_imports.add('field') + self.additional_imports.add("field") elif "[" in column.type: self.typing_imports.add("List") _type = f"List[{_type}]" @@ -75,14 +75,12 @@ def generate_model( **kwargs, ) -> str: model = "" - + # mean one model one table model += "\n\n" model += ( dt.dataclass_class.format( - class_name=create_class_name( - table.name, singular, exceptions - ), + class_name=create_class_name(table.name, singular, exceptions), table_name=table.name, ) ) + "\n\n" @@ -114,8 +112,10 @@ def create_header(self, *args, **kwargs) -> str: if self.additional_imports: self.additional_imports = f', {",".join(self.additional_imports)}' else: - self.additional_imports = '' - header += dt.dataclass_imports.format(additional_imports=self.additional_imports) + self.additional_imports = "" + header += dt.dataclass_imports.format( + additional_imports=self.additional_imports + ) return header def generate_type( diff --git a/omymodels/dataclass/templates.py b/omymodels/dataclass/templates.py index 28cf991..3df642e 100644 --- a/omymodels/dataclass/templates.py +++ b/omymodels/dataclass/templates.py @@ -16,4 +16,4 @@ class {class_name}:""" enum_import = "from enum import {enums}" uuid_import = "from uuid import UUID" -field_datetime_now = "field(default_factory=datetime.datetime.now)" \ No newline at end of file +field_datetime_now = "field(default_factory=datetime.datetime.now)" diff --git a/omymodels/gino/core.py b/omymodels/gino/core.py index f6f1cb8..72fb5de 100644 --- a/omymodels/gino/core.py +++ b/omymodels/gino/core.py @@ -92,7 +92,7 @@ def setup_column_attributes( column = self.add_reference_to_the_column( column_data.name, column, column_data.references ) - if not column_data.nullable and not column_data.name in table_pk: + if not column_data.nullable and column_data.name not in table_pk: column += gt.required if column_data.default is not None: column = self.prepare_column_default(column_data, column) @@ -104,13 +104,13 @@ def setup_column_attributes( if "columns" in table_data.alter: for alter_column in table_data.alter["columns"]: if ( - alter_column['name'] == column_data.name + alter_column["name"] == column_data.name and not alter_column["constraint_name"] and alter_column["references"] ): column = self.add_reference_to_the_column( - alter_column['name'], column, alter_column["references"] + alter_column["name"], column, alter_column["references"] ) return column @@ -222,12 +222,7 @@ def generate_model( ) for column in table.columns: model += self.generate_column(column, table.primary_key, table) - if ( - table.indexes - or table.alter - or table.checks - or not schema_global - ): + if table.indexes or table.alter or table.checks or not schema_global: model = self.add_table_args(model, table, schema_global) # create sequence return model diff --git a/omymodels/meta_model.py b/omymodels/meta_model.py index 872d784..e5869b0 100644 --- a/omymodels/meta_model.py +++ b/omymodels/meta_model.py @@ -3,11 +3,12 @@ class TableProperties(BaseModel): + indexes: List class Column(BaseModel): - + name: str type: str size: Optional[Union[str, int, tuple]] @@ -19,29 +20,31 @@ class Column(BaseModel): generated_as: Optional[str] other_properties: Optional[dict] references: Optional[dict] - - @validator('size') + + @validator("size") def size_must_contain_space(cls, v): if isinstance(v, str) and v.isnumeric(): return int(v) return v - + class TableMeta(BaseModel): - name: str = Field(alias='table_name') - table_schema: Optional[str] = Field(alias='schema') + name: str = Field(alias="table_name") + table_schema: Optional[str] = Field(alias="schema") columns: List[Column] - indexes: Optional[List[dict]] = Field(alias='index') + indexes: Optional[List[dict]] = Field(alias="index") alter: Optional[dict] checks: Optional[List[dict]] properties: Optional[TableProperties] primary_key: List - + class Config: + """ pydantic class config """ + arbitrary_types_allowed = True class Type(BaseModel): - name: str = Field(alias='type_name') + name: str = Field(alias="type_name") base_type: str - properties: Optional[dict] \ No newline at end of file + properties: Optional[dict] diff --git a/omymodels/pydantic/core.py b/omymodels/pydantic/core.py index e135a32..3797ff0 100644 --- a/omymodels/pydantic/core.py +++ b/omymodels/pydantic/core.py @@ -78,9 +78,7 @@ def generate_model( model += "\n\n" model += ( pt.pydantic_class.format( - class_name=create_class_name( - table.name, singular, exceptions - ), + class_name=create_class_name(table.name, singular, exceptions), table_name=table.name, ) ) + "\n\n" diff --git a/omymodels/sqlalchemy/core.py b/omymodels/sqlalchemy/core.py index 0c76905..82efa91 100644 --- a/omymodels/sqlalchemy/core.py +++ b/omymodels/sqlalchemy/core.py @@ -92,7 +92,7 @@ def setup_column_attributes( column = self.add_reference_to_the_column( column_data.name, column, column_data.references ) - if not column_data.nullable and not column_data.name in table_pk: + if not column_data.nullable and column_data.name not in table_pk: column += st.required if column_data.default is not None: column = self.prepare_column_default(column_data, column) @@ -104,13 +104,13 @@ def setup_column_attributes( if "columns" in table_data.alter: for alter_column in table_data.alter["columns"]: if ( - alter_column['name'] == column_data.name + alter_column["name"] == column_data.name and not alter_column["constraint_name"] and alter_column["references"] ): column = self.add_reference_to_the_column( - alter_column['name'], column, alter_column["references"] + alter_column["name"], column, alter_column["references"] ) return column @@ -133,7 +133,8 @@ def generate_column( """ method to generate full column defention """ column_type = self.prepare_column_type(column_data) column = self.setup_column_attributes( - column_data, table_pk, column_type, table_data) + column_data, table_pk, column_type, table_data + ) column += ")\n" return column @@ -214,19 +215,14 @@ def generate_model( ) -> str: """ method to prepare one Model defention - name & tablename & columns """ model = "" - + model = st.model_template.format( model_name=create_class_name(table.name, singular, exceptions), table_name=table.name, ) for column in table.columns: model += self.generate_column(column, table.primary_key, table) - if ( - table.indexes - or table.alter - or table.checks - or not schema_global - ): + if table.indexes or table.alter or table.checks or not schema_global: model = self.add_table_args(model, table, schema_global) return model diff --git a/omymodels/sqlalchemy_core/core.py b/omymodels/sqlalchemy_core/core.py index f5b3692..39e8c3a 100644 --- a/omymodels/sqlalchemy_core/core.py +++ b/omymodels/sqlalchemy_core/core.py @@ -92,7 +92,7 @@ def get_column_attributes( properties.append( self.column_reference(column_data.name, column_data.references) ) - if not column_data.nullable and not column_data.name in table_pk: + if not column_data.nullable and column_data.name not in table_pk: properties.append(st.required) if column_data.default is not None: properties.append(self.column_default(column_data)) @@ -103,14 +103,14 @@ def get_column_attributes( if "columns" in table_data.alter: for alter_column in table_data.alter["columns"]: if ( - alter_column['name'] == column_data.name + alter_column["name"] == column_data.name and not alter_column["constraint_name"] and alter_column["references"] and not column_data.references ): properties.append( self.column_reference( - alter_column['name'], alter_column["references"] + alter_column["name"], alter_column["references"] ) ) return properties diff --git a/poetry.lock b/poetry.lock index 35f6867..a6fe79c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -134,7 +134,7 @@ test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pret name = "dataclasses" version = "0.8" description = "A backport of the dataclasses module for Python 3.6" -category = "dev" +category = "main" optional = false python-versions = ">=3.6, <3.7" @@ -347,6 +347,22 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "pydantic" +version = "1.8.2" +description = "Data validation and settings management using python 3.6 type hinting" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} +typing-extensions = ">=3.7.4.3" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + [[package]] name = "pygments" version = "2.9.0" @@ -542,7 +558,7 @@ python-versions = "*" name = "typing-extensions" version = "3.10.0.0" description = "Backported and Experimental Type Hints for Python 3.5+" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -589,8 +605,8 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" -python-versions = "^3.6" -content-hash = "83789278aa9f30256298d8423bf834d02c1b9653ca93dcc8db6c07d75bde3466" +python-versions = ">=3.6.1,<4.0" +content-hash = "65655f5409d5caab89617ba494eae19709c5539b5954f54dd6ff71782d8c5104" [metadata.files] appdirs = [ @@ -795,6 +811,30 @@ pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] +pydantic = [ + {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, + {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, + {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, + {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, + {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, + {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, + {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, + {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, + {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, + {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, + {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, + {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, + {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, + {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, +] pygments = [ {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, diff --git a/pyproject.toml b/pyproject.toml index 815a498..9e695d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "omymodels" -version = "0.8.2" -description = "O! My Models (omymodels) is a library to generate Python Models for SQLAlchemy (ORM & Core), GinoORM, Pydantic, Pydal tables & Python Dataclasses from SQL DDL. OMyModels also allow you to covert one type of Models to another. Right supported conversion from SQLAlchemy to Pydal tables." +version = "0.8.3" +description = "O! My Models (omymodels) is a library to generate Python Models for SQLAlchemy (ORM & Core), GinoORM, Pydantic, Pydal tables & Python Dataclasses from SQL DDL. " authors = ["Iuliia Volkova "] license = "MIT" readme = "docs/README.rst" @@ -18,10 +18,11 @@ classifiers = [ ] [tool.poetry.dependencies] -python = "^3.6" +python = ">=3.6.1,<4.0" simple-ddl-parser = "0.16.0" Jinja2 = "^3.0.1" py-models-parser = "^0.5.0" +pydantic = "^1.8.2" [tool.poetry.dev-dependencies] pytest = "^5.2" diff --git a/tests/test_sqlalchemy.py b/tests/test_sqlalchemy.py index 6303882..9a0159e 100644 --- a/tests/test_sqlalchemy.py +++ b/tests/test_sqlalchemy.py @@ -172,4 +172,4 @@ class Material(Base): ); """ result = create_models(ddl, models_type="sqlalchemy") - assert expected == result["code"] \ No newline at end of file + assert expected == result["code"]