Skip to content

Commit

Permalink
Merge branch 'main' into prettify_docs
Browse files Browse the repository at this point in the history
  • Loading branch information
tiangolo committed Oct 29, 2023
2 parents 37db3c3 + 6457775 commit ec31941
Show file tree
Hide file tree
Showing 13 changed files with 431 additions and 31 deletions.
16 changes: 3 additions & 13 deletions .pre-commit-config.yaml
Expand Up @@ -13,23 +13,13 @@ repos:
- --unsafe
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
hooks:
- id: pyupgrade
args:
- --py3-plus
- --keep-runtime-typing
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.1
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.1.2
hooks:
- id: ruff
args:
- --fix
- repo: https://github.com/psf/black
rev: 23.10.0
hooks:
- id: black
- id: ruff-format
ci:
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate
11 changes: 11 additions & 0 deletions docs/release-notes.md
Expand Up @@ -2,6 +2,17 @@

## Latest Changes

* ✨ Do not allow invalid combinations of field parameters for columns and relationships, `sa_column` excludes `sa_column_args`, `primary_key`, `nullable`, etc.. PR [#681](https://github.com/tiangolo/sqlmodel/pull/681) by [@tiangolo](https://github.com/tiangolo).
## 0.0.10

### Features

* ✨ Add support for all `Field` parameters from Pydantic `1.9.0` and above, make Pydantic `1.9.0` the minimum required version. PR [#440](https://github.com/tiangolo/sqlmodel/pull/440) by [@daniil-berg](https://github.com/daniil-berg).

### Internal

* 🔧 Adopt Ruff for formatting. PR [#679](https://github.com/tiangolo/sqlmodel/pull/679) by [@tiangolo](https://github.com/tiangolo).

## 0.0.9

### Breaking Changes
Expand Down
11 changes: 9 additions & 2 deletions pyproject.toml
Expand Up @@ -32,12 +32,13 @@ classifiers = [
[tool.poetry.dependencies]
python = "^3.7"
SQLAlchemy = ">=1.4.36,<2.0.0"
pydantic = "^1.8.2"
pydantic = "^1.9.0"
sqlalchemy2-stubs = {version = "*", allow-prereleases = true}

[tool.poetry.group.dev.dependencies]
pytest = "^7.0.1"
mypy = "0.971"
# Needed by the code generator using templates
black = "^22.10.0"
mkdocs-material = "9.1.21"
pillow = "^9.3.0"
Expand All @@ -46,7 +47,7 @@ mdx-include = "^1.4.1"
coverage = {extras = ["toml"], version = "^6.2"}
fastapi = "^0.68.1"
requests = "^2.26.0"
ruff = "^0.1.1"
ruff = "^0.1.2"

[build-system]
requires = ["poetry-core"]
Expand Down Expand Up @@ -87,15 +88,21 @@ select = [
"I", # isort
"C", # flake8-comprehensions
"B", # flake8-bugbear
"UP", # pyupgrade
]
ignore = [
"E501", # line too long, handled by black
"B008", # do not perform function calls in argument defaults
"C901", # too complex
"W191", # indentation contains tabs
]

[tool.ruff.per-file-ignores]
# "__init__.py" = ["F401"]

[tool.ruff.isort]
known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]

[tool.ruff.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true
2 changes: 1 addition & 1 deletion scripts/format.sh
Expand Up @@ -2,4 +2,4 @@
set -x

ruff sqlmodel tests docs_src scripts --fix
black sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts
2 changes: 1 addition & 1 deletion scripts/lint.sh
Expand Up @@ -5,4 +5,4 @@ set -x

mypy sqlmodel
ruff sqlmodel tests docs_src scripts
black sqlmodel tests docs_src --check
ruff format sqlmodel tests docs_src --check
2 changes: 1 addition & 1 deletion sqlmodel/__init__.py
@@ -1,4 +1,4 @@
__version__ = "0.0.9"
__version__ = "0.0.10"

# Re-export from SQLAlchemy
from sqlalchemy.engine import create_mock_engine as create_mock_engine
Expand Down
167 changes: 156 additions & 11 deletions sqlmodel/main.py
Expand Up @@ -22,6 +22,7 @@
TypeVar,
Union,
cast,
overload,
)

from pydantic import BaseConfig, BaseModel
Expand Down Expand Up @@ -87,6 +88,28 @@ def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
"Passing sa_column_kwargs is not supported when "
"also passing a sa_column"
)
if primary_key is not Undefined:
raise RuntimeError(
"Passing primary_key is not supported when "
"also passing a sa_column"
)
if nullable is not Undefined:
raise RuntimeError(
"Passing nullable is not supported when " "also passing a sa_column"
)
if foreign_key is not Undefined:
raise RuntimeError(
"Passing foreign_key is not supported when "
"also passing a sa_column"
)
if unique is not Undefined:
raise RuntimeError(
"Passing unique is not supported when " "also passing a sa_column"
)
if index is not Undefined:
raise RuntimeError(
"Passing index is not supported when " "also passing a sa_column"
)
super().__init__(default=default, **kwargs)
self.primary_key = primary_key
self.nullable = nullable
Expand Down Expand Up @@ -126,6 +149,86 @@ def __init__(
self.sa_relationship_kwargs = sa_relationship_kwargs


@overload
def Field(
default: Any = Undefined,
*,
default_factory: Optional[NoArgAnyCallable] = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
exclude: Union[
AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], Any
] = None,
include: Union[
AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], Any
] = None,
const: Optional[bool] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
multiple_of: Optional[float] = None,
max_digits: Optional[int] = None,
decimal_places: Optional[int] = None,
min_items: Optional[int] = None,
max_items: Optional[int] = None,
unique_items: Optional[bool] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
allow_mutation: bool = True,
regex: Optional[str] = None,
discriminator: Optional[str] = None,
repr: bool = True,
primary_key: Union[bool, UndefinedType] = Undefined,
foreign_key: Any = Undefined,
unique: Union[bool, UndefinedType] = Undefined,
nullable: Union[bool, UndefinedType] = Undefined,
index: Union[bool, UndefinedType] = Undefined,
sa_column_args: Union[Sequence[Any], UndefinedType] = Undefined,
sa_column_kwargs: Union[Mapping[str, Any], UndefinedType] = Undefined,
schema_extra: Optional[Dict[str, Any]] = None,
) -> Any:
...


@overload
def Field(
default: Any = Undefined,
*,
default_factory: Optional[NoArgAnyCallable] = None,
alias: Optional[str] = None,
title: Optional[str] = None,
description: Optional[str] = None,
exclude: Union[
AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], Any
] = None,
include: Union[
AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], Any
] = None,
const: Optional[bool] = None,
gt: Optional[float] = None,
ge: Optional[float] = None,
lt: Optional[float] = None,
le: Optional[float] = None,
multiple_of: Optional[float] = None,
max_digits: Optional[int] = None,
decimal_places: Optional[int] = None,
min_items: Optional[int] = None,
max_items: Optional[int] = None,
unique_items: Optional[bool] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
allow_mutation: bool = True,
regex: Optional[str] = None,
discriminator: Optional[str] = None,
repr: bool = True,
sa_column: Union[Column, UndefinedType] = Undefined, # type: ignore
schema_extra: Optional[Dict[str, Any]] = None,
) -> Any:
...


def Field(
default: Any = Undefined,
*,
Expand All @@ -145,15 +248,20 @@ def Field(
lt: Optional[float] = None,
le: Optional[float] = None,
multiple_of: Optional[float] = None,
max_digits: Optional[int] = None,
decimal_places: Optional[int] = None,
min_items: Optional[int] = None,
max_items: Optional[int] = None,
unique_items: Optional[bool] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
allow_mutation: bool = True,
regex: Optional[str] = None,
primary_key: bool = False,
foreign_key: Optional[Any] = None,
unique: bool = False,
discriminator: Optional[str] = None,
repr: bool = True,
primary_key: Union[bool, UndefinedType] = Undefined,
foreign_key: Any = Undefined,
unique: Union[bool, UndefinedType] = Undefined,
nullable: Union[bool, UndefinedType] = Undefined,
index: Union[bool, UndefinedType] = Undefined,
sa_column: Union[Column, UndefinedType] = Undefined, # type: ignore
Expand All @@ -176,12 +284,17 @@ def Field(
lt=lt,
le=le,
multiple_of=multiple_of,
max_digits=max_digits,
decimal_places=decimal_places,
min_items=min_items,
max_items=max_items,
unique_items=unique_items,
min_length=min_length,
max_length=max_length,
allow_mutation=allow_mutation,
regex=regex,
discriminator=discriminator,
repr=repr,
primary_key=primary_key,
foreign_key=foreign_key,
unique=unique,
Expand All @@ -196,6 +309,27 @@ def Field(
return field_info


@overload
def Relationship(
*,
back_populates: Optional[str] = None,
link_model: Optional[Any] = None,
sa_relationship_args: Optional[Sequence[Any]] = None,
sa_relationship_kwargs: Optional[Mapping[str, Any]] = None,
) -> Any:
...


@overload
def Relationship(
*,
back_populates: Optional[str] = None,
link_model: Optional[Any] = None,
sa_relationship: Optional[RelationshipProperty] = None, # type: ignore
) -> Any:
...


def Relationship(
*,
back_populates: Optional[str] = None,
Expand Down Expand Up @@ -430,21 +564,28 @@ def get_column_from_field(field: ModelField) -> Column: # type: ignore
if isinstance(sa_column, Column):
return sa_column
sa_type = get_sqlalchemy_type(field)
primary_key = getattr(field.field_info, "primary_key", False)
primary_key = getattr(field.field_info, "primary_key", Undefined)
if primary_key is Undefined:
primary_key = False
index = getattr(field.field_info, "index", Undefined)
if index is Undefined:
index = False
nullable = not primary_key and _is_field_noneable(field)
# 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") # noqa: B009
if field_nullable != Undefined:
nullable = field_nullable
field_nullable = getattr(field.field_info, "nullable", Undefined) # noqa: B009
if field_nullable != Undefined:
assert not isinstance(field_nullable, UndefinedType)
nullable = field_nullable
args = []
foreign_key = getattr(field.field_info, "foreign_key", None)
unique = getattr(field.field_info, "unique", False)
foreign_key = getattr(field.field_info, "foreign_key", Undefined)
if foreign_key is Undefined:
foreign_key = None
unique = getattr(field.field_info, "unique", Undefined)
if unique is Undefined:
unique = False
if foreign_key:
assert isinstance(foreign_key, str)
args.append(ForeignKey(foreign_key))
kwargs = {
"primary_key": primary_key,
Expand Down Expand Up @@ -587,7 +728,11 @@ def parse_obj(

def __repr_args__(self) -> Sequence[Tuple[Optional[str], Any]]:
# Don't show SQLAlchemy private attributes
return [(k, v) for k, v in self.__dict__.items() if not k.startswith("_sa_")]
return [
(k, v)
for k, v in super().__repr_args__()
if not (isinstance(k, str) and k.startswith("_sa_"))
]

# From Pydantic, override to enforce validation with dict
@classmethod
Expand Down
3 changes: 1 addition & 2 deletions tests/conftest.py
Expand Up @@ -42,8 +42,7 @@ def coverage_run(*, module: str, cwd: Union[str, Path]) -> subprocess.CompletedP
module,
],
cwd=str(cwd),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
capture_output=True,
encoding="utf-8",
)
return result
Expand Down

0 comments on commit ec31941

Please sign in to comment.