From 2a6404574be0297632f8eef397da3ec01406dc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Oct 2023 10:45:35 +0400 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=91=B7=20Add=20pre-commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..9f7085f72f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +default_language_version: + python: python3.10 +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + - id: check-toml + - id: check-yaml + args: + - --unsafe + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/asottile/pyupgrade + rev: v3.7.0 + hooks: + - id: pyupgrade + args: + - --py3-plus + - --keep-runtime-typing +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.275 + hooks: + - id: ruff + args: + - --fix +- repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black +ci: + autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks + autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate From 66044e59da54ceb1789c614e30e2780ea1a0ead1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Oct 2023 10:59:11 +0400 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=94=A7=20Add=20pyproject.toml=20confi?= =?UTF-8?q?g=20for=20Ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e402727150..157e063e41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,16 +86,16 @@ skip_glob = [ [tool.mypy] # --strict disallow_any_generics = true -disallow_subclassing_any = true -disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_untyped_calls = true disallow_untyped_defs = true -disallow_incomplete_defs = true -check_untyped_defs = true -disallow_untyped_decorators = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true no_implicit_optional = true -warn_redundant_casts = true +warn_redundant_casts = true warn_unused_ignores = true -warn_return_any = true +warn_return_any = true implicit_reexport = false strict_equality = true # --strict end @@ -104,4 +104,23 @@ strict_equality = true module = "sqlmodel.sql.expression" warn_unused_ignores = false -# invalidate CI cache: 1 +[tool.ruff] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear +] +ignore = [ + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "C901", # too complex +] + +[tool.ruff.per-file-ignores] +# "__init__.py" = ["F401"] + +[tool.ruff.isort] +known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"] From d2145c4d43a6344d9b406a038713923f7f4715e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Oct 2023 11:02:58 +0400 Subject: [PATCH 3/6] =?UTF-8?q?=E2=9E=95=20Replace=20isort,=20flake8,=20au?= =?UTF-8?q?toflake=20with=20Ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 157e063e41..5c7f7079a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ sqlalchemy2-stubs = {version = "*", allow-prereleases = true} [tool.poetry.dev-dependencies] pytest = "^7.0.1" mypy = "0.971" -flake8 = "^5.0.4" black = "^22.10.0" mkdocs = "^1.2.1" mkdocs-material = "^8.1.4" @@ -48,8 +47,9 @@ mdx-include = "^1.4.1" coverage = {extras = ["toml"], version = "^6.2"} fastapi = "^0.68.1" requests = "^2.26.0" -autoflake = "^1.4" -isort = "^5.9.3" + +[tool.poetry.group.dev.dependencies] +ruff = "^0.1.1" [build-system] requires = ["poetry-core"] @@ -75,14 +75,6 @@ exclude_lines = [ "if TYPE_CHECKING:", ] -[tool.isort] -profile = "black" -known_third_party = ["sqlmodel"] -skip_glob = [ - "sqlmodel/__init__.py", - ] - - [tool.mypy] # --strict disallow_any_generics = true From 21d35bdad688ab7fa81c0088cecf66c5225ff119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Oct 2023 11:03:51 +0400 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=94=A8=20Update=20lint=20and=20format?= =?UTF-8?q?=20scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/format.sh | 5 ++--- scripts/lint.sh | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/format.sh b/scripts/format.sh index 0d456398fb..b6aebd10d4 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -1,6 +1,5 @@ #!/bin/sh -e set -x -autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place sqlmodel docs_src tests --exclude=__init__.py -black sqlmodel tests docs_src -isort sqlmodel tests docs_src +ruff sqlmodel tests docs_src scripts --fix +black sqlmodel tests docs_src scripts diff --git a/scripts/lint.sh b/scripts/lint.sh index 4191d90f1f..b328e3d9ac 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -4,6 +4,5 @@ set -e set -x mypy sqlmodel -flake8 sqlmodel tests docs_src +ruff sqlmodel tests docs_src scripts black sqlmodel tests docs_src --check -isort sqlmodel tests docs_src scripts --check-only From 42aa921abb9b59285a094944a7de1cdb60a7c58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Oct 2023 11:25:23 +0400 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=8E=A8=20Format=20with=20Ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlmodel/__init__.py | 62 +++++++++++++++++++++----------------------- sqlmodel/main.py | 29 ++++++++++++++------- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/sqlmodel/__init__.py b/sqlmodel/__init__.py index 720aa8c929..3aa6e0d2ac 100644 --- a/sqlmodel/__init__.py +++ b/sqlmodel/__init__.py @@ -5,12 +5,12 @@ from sqlalchemy.engine import engine_from_config as engine_from_config from sqlalchemy.inspection import inspect as inspect from sqlalchemy.schema import BLANK_SCHEMA as BLANK_SCHEMA +from sqlalchemy.schema import DDL as DDL from sqlalchemy.schema import CheckConstraint as CheckConstraint from sqlalchemy.schema import Column as Column from sqlalchemy.schema import ColumnDefault as ColumnDefault from sqlalchemy.schema import Computed as Computed from sqlalchemy.schema import Constraint as Constraint -from sqlalchemy.schema import DDL as DDL from sqlalchemy.schema import DefaultClause as DefaultClause from sqlalchemy.schema import FetchedValue as FetchedValue from sqlalchemy.schema import ForeignKey as ForeignKey @@ -23,6 +23,14 @@ from sqlalchemy.schema import Table as Table from sqlalchemy.schema import ThreadLocalMetaData as ThreadLocalMetaData from sqlalchemy.schema import UniqueConstraint as UniqueConstraint +from sqlalchemy.sql import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT +from sqlalchemy.sql import ( + LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY, +) +from sqlalchemy.sql import LABEL_STYLE_NONE as LABEL_STYLE_NONE +from sqlalchemy.sql import ( + LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL, +) from sqlalchemy.sql import alias as alias from sqlalchemy.sql import all_ as all_ from sqlalchemy.sql import and_ as and_ @@ -48,14 +56,6 @@ from sqlalchemy.sql import intersect as intersect from sqlalchemy.sql import intersect_all as intersect_all from sqlalchemy.sql import join as join -from sqlalchemy.sql import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT -from sqlalchemy.sql import ( - LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY, -) -from sqlalchemy.sql import LABEL_STYLE_NONE as LABEL_STYLE_NONE -from sqlalchemy.sql import ( - LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL, -) from sqlalchemy.sql import lambda_stmt as lambda_stmt from sqlalchemy.sql import lateral as lateral from sqlalchemy.sql import literal as literal @@ -85,55 +85,53 @@ from sqlalchemy.sql import within_group as within_group from sqlalchemy.types import ARRAY as ARRAY from sqlalchemy.types import BIGINT as BIGINT -from sqlalchemy.types import BigInteger as BigInteger from sqlalchemy.types import BINARY as BINARY from sqlalchemy.types import BLOB as BLOB from sqlalchemy.types import BOOLEAN as BOOLEAN -from sqlalchemy.types import Boolean as Boolean from sqlalchemy.types import CHAR as CHAR from sqlalchemy.types import CLOB as CLOB from sqlalchemy.types import DATE as DATE -from sqlalchemy.types import Date as Date from sqlalchemy.types import DATETIME as DATETIME -from sqlalchemy.types import DateTime as DateTime from sqlalchemy.types import DECIMAL as DECIMAL -from sqlalchemy.types import Enum as Enum from sqlalchemy.types import FLOAT as FLOAT -from sqlalchemy.types import Float as Float from sqlalchemy.types import INT as INT from sqlalchemy.types import INTEGER as INTEGER -from sqlalchemy.types import Integer as Integer -from sqlalchemy.types import Interval as Interval from sqlalchemy.types import JSON as JSON -from sqlalchemy.types import LargeBinary as LargeBinary from sqlalchemy.types import NCHAR as NCHAR from sqlalchemy.types import NUMERIC as NUMERIC -from sqlalchemy.types import Numeric as Numeric from sqlalchemy.types import NVARCHAR as NVARCHAR -from sqlalchemy.types import PickleType as PickleType from sqlalchemy.types import REAL as REAL from sqlalchemy.types import SMALLINT as SMALLINT +from sqlalchemy.types import TEXT as TEXT +from sqlalchemy.types import TIME as TIME +from sqlalchemy.types import TIMESTAMP as TIMESTAMP +from sqlalchemy.types import VARBINARY as VARBINARY +from sqlalchemy.types import VARCHAR as VARCHAR +from sqlalchemy.types import BigInteger as BigInteger +from sqlalchemy.types import Boolean as Boolean +from sqlalchemy.types import Date as Date +from sqlalchemy.types import DateTime as DateTime +from sqlalchemy.types import Enum as Enum +from sqlalchemy.types import Float as Float +from sqlalchemy.types import Integer as Integer +from sqlalchemy.types import Interval as Interval +from sqlalchemy.types import LargeBinary as LargeBinary +from sqlalchemy.types import Numeric as Numeric +from sqlalchemy.types import PickleType as PickleType from sqlalchemy.types import SmallInteger as SmallInteger from sqlalchemy.types import String as String -from sqlalchemy.types import TEXT as TEXT from sqlalchemy.types import Text as Text -from sqlalchemy.types import TIME as TIME from sqlalchemy.types import Time as Time -from sqlalchemy.types import TIMESTAMP as TIMESTAMP from sqlalchemy.types import TypeDecorator as TypeDecorator from sqlalchemy.types import Unicode as Unicode from sqlalchemy.types import UnicodeText as UnicodeText -from sqlalchemy.types import VARBINARY as VARBINARY -from sqlalchemy.types import VARCHAR as VARCHAR -# Extensions and modifications of SQLAlchemy in SQLModel +# From SQLModel, modifications of SQLAlchemy or equivalents of Pydantic from .engine.create import create_engine as create_engine +from .main import Field as Field +from .main import Relationship as Relationship +from .main import SQLModel as SQLModel from .orm.session import Session as Session -from .sql.expression import select as select from .sql.expression import col as col +from .sql.expression import select as select from .sql.sqltypes import AutoString as AutoString - -# Export SQLModel specifics (equivalent to Pydantic) -from .main import SQLModel as SQLModel -from .main import Field as Field -from .main import Relationship as Relationship diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 7dec60ddac..d5a7302438 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -26,15 +26,24 @@ from pydantic import BaseConfig, BaseModel from pydantic.errors import ConfigError, DictError -from pydantic.fields import SHAPE_SINGLETON +from pydantic.fields import SHAPE_SINGLETON, ModelField, Undefined, UndefinedType from pydantic.fields import FieldInfo as PydanticFieldInfo -from pydantic.fields import ModelField, Undefined, UndefinedType from pydantic.main import ModelMetaclass, validate_model from pydantic.typing import NoArgAnyCallable, resolve_annotations from pydantic.utils import ROOT_KEY, Representation -from sqlalchemy import Boolean, Column, Date, DateTime +from sqlalchemy import ( + Boolean, + Column, + Date, + DateTime, + Float, + ForeignKey, + Integer, + Interval, + Numeric, + inspect, +) from sqlalchemy import Enum as sa_Enum -from sqlalchemy import Float, ForeignKey, Integer, Interval, Numeric, inspect from sqlalchemy.orm import RelationshipProperty, declared_attr, registry, relationship from sqlalchemy.orm.attributes import set_attribute from sqlalchemy.orm.decl_api import DeclarativeMeta @@ -305,9 +314,9 @@ def get_config(name: str) -> Any: config_registry = cast(registry, config_registry) # If it was passed by kwargs, ensure it's also set in config new_cls.__config__.registry = config_table - setattr(new_cls, "_sa_registry", config_registry) - setattr(new_cls, "metadata", config_registry.metadata) - setattr(new_cls, "__abstract__", True) + setattr(new_cls, "_sa_registry", config_registry) # noqa: B010 + setattr(new_cls, "metadata", config_registry.metadata) # noqa: B010 + setattr(new_cls, "__abstract__", True) # noqa: B010 return new_cls # Override SQLAlchemy, allow both SQLAlchemy and plain Pydantic models @@ -320,7 +329,7 @@ def __init__( # triggers an error base_is_table = False for base in bases: - config = getattr(base, "__config__") + config = getattr(base, "__config__") # noqa: B009 if config and getattr(config, "table", False): base_is_table = True break @@ -351,7 +360,7 @@ def __init__( rel_kwargs["back_populates"] = rel_info.back_populates if rel_info.link_model: ins = inspect(rel_info.link_model) - local_table = getattr(ins, "local_table") + local_table = getattr(ins, "local_table") # noqa: B009 if local_table is None: raise RuntimeError( "Couldn't find the secondary table for " @@ -430,7 +439,7 @@ def get_column_from_field(field: ModelField) -> Column: # type: ignore # Override derived nullability if the nullable property is set explicitly # on the field if hasattr(field.field_info, "nullable"): - field_nullable = getattr(field.field_info, "nullable") + field_nullable = getattr(field.field_info, "nullable") # noqa: B009 if field_nullable != Undefined: nullable = field_nullable args = [] From 85967a7a5d9406b2b3843a4c11315c6b8d4edfc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Oct 2023 11:31:08 +0400 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=94=A7=20Update=20Poetry=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5c7f7079a7..73d8b3ac92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ SQLAlchemy = ">=1.4.17,<=1.4.41" pydantic = "^1.8.2" sqlalchemy2-stubs = {version = "*", allow-prereleases = true} -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^7.0.1" mypy = "0.971" black = "^22.10.0" @@ -47,8 +47,6 @@ mdx-include = "^1.4.1" coverage = {extras = ["toml"], version = "^6.2"} fastapi = "^0.68.1" requests = "^2.26.0" - -[tool.poetry.group.dev.dependencies] ruff = "^0.1.1" [build-system]