Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0251f0f
add ty to lint, precommit, pyproject and uv lock
svlandeg Mar 9, 2026
a52380b
add type annotation to kwargs dict
svlandeg Mar 9, 2026
3203f83
remove unnecessary type ignore statements
svlandeg Mar 9, 2026
5d9dd59
🎨 Auto format
pre-commit-ci-lite[bot] Mar 9, 2026
5c9b1d5
avoid using double underscore when any positional-or-keyword paramete…
svlandeg Mar 9, 2026
c1b0465
one more
svlandeg Mar 9, 2026
732847d
remove mypy
svlandeg Mar 9, 2026
2f8efa3
Merge branch 'main' into feat/ty
svlandeg Mar 10, 2026
dc8593b
use same names as upstream class
svlandeg Mar 10, 2026
1dceaa7
fix generator
svlandeg Mar 10, 2026
02cd800
Merge branch 'main' into feat/ty
svlandeg Mar 14, 2026
c49bf7f
Merge branch 'main' into feat/ty
svlandeg Mar 16, 2026
277366f
use / to denote positional-only parameters
svlandeg Mar 18, 2026
b511af0
🎨 Auto format
pre-commit-ci-lite[bot] Mar 18, 2026
7aebcc9
remove class_registry
svlandeg Mar 18, 2026
c6e97e7
revert attr naming and add ty ignore instead
svlandeg Mar 18, 2026
9103510
Merge branch 'main' into feat/ty
svlandeg Mar 18, 2026
807d9d6
ty check tests/test_select_typing.py as well
svlandeg Mar 25, 2026
51b72e1
Merge branch 'main' into feat/ty
svlandeg Mar 25, 2026
fa6df49
ensure that a ty warning creates an error
svlandeg Mar 26, 2026
0f2ea9c
🎨 Auto format
pre-commit-ci-lite[bot] Mar 26, 2026
9cd8a87
Merge branch 'main' into feat/ty
svlandeg Mar 26, 2026
fef2ff7
upgrade lower ty bound to 0.0.25
svlandeg Mar 26, 2026
a1e581a
change mypy codes to ty codes
svlandeg Mar 27, 2026
becee62
Merge branch 'main' into feat/ty
svlandeg Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,9 @@ repos:
language: unsupported
types: [python]

- id: local-mypy
name: mypy check
entry: uv run mypy sqlmodel tests/test_select_typing.py
- id: local-ty
name: ty check
entry: uv run ty check sqlmodel tests/test_select_typing.py
require_serial: true
language: unsupported
pass_filenames: false
Expand Down
15 changes: 4 additions & 11 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,9 @@ tests = [
"fastapi >=0.128.0",
"httpx >=0.28.1",
"jinja2 >=3.1.6",
"mypy >=1.19.1",
"pytest >=7.0.1",
"ruff >=0.15.6",
"ty>=0.0.25",
"typing-extensions >=4.15.0",
]

Expand DownExpand Up@@ -125,16 +125,6 @@ exclude_lines = [
[tool.coverage.html]
show_contexts = true

[tool.mypy]
strict = true
exclude = "sqlmodel.sql._expression_select_gen"

[[tool.mypy.overrides]]
module = "docs_src.*"
disallow_incomplete_defs = false
disallow_untyped_defs = false
disallow_untyped_calls = false

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
Expand All@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.ty.terminal]
error-on-warning = true
2 changes: 1 addition & 1 deletion scripts/generate_select.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ class Arg(BaseModel):
else:
t_type = f"_T{i}"
t_var = f"_TCCA[{t_type}]"
arg = Arg(name=f"__ent{i}", annotation=t_var)
arg = Arg(name=f"ent{i}", annotation=t_var)
ret_type = t_type
args.append(arg)
return_types.append(ret_type)
Expand Down
4 changes: 2 additions & 2 deletions scripts/lint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
set -e
set -x

mypy sqlmodel
mypy tests/test_select_typing.py
ty check sqlmodel
ty check tests/test_select_typing.py
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
29 changes: 13 additions & 16 deletions sqlmodel/main.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import builtins
import ipaddress
import uuid
import weakref
from collections.abc import Callable, Mapping, Sequence, Set
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
Expand DownExpand Up@@ -52,7 +51,7 @@
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import deprecated

from ._compat import ( # type: ignore[attr-defined]
from ._compat import (
PYDANTIC_MINOR_VERSION,
BaseConfig,
ModelMetaclass,
Expand DownExpand Up@@ -101,7 +100,7 @@ def __dataclass_transform__(
return lambda a: a


class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
# mypy - ignore that PydanticFieldInfo is @final
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
primary_key = kwargs.pop("primary_key", False)
Expand DownExpand Up@@ -177,7 +176,7 @@ def __init__(
cascade_delete: bool | None = False,
passive_deletes: bool | Literal["all"] | None = False,
link_model: Any | None = None,
sa_relationship: RelationshipProperty | None = None, # type: ignore
sa_relationship: RelationshipProperty | None = None,
sa_relationship_args: Sequence[Any] | None = None,
sa_relationship_kwargs: Mapping[str, Any] | None = None,
) -> None:
Expand DownExpand Up@@ -398,7 +397,7 @@ def Field(
nullable: bool | UndefinedType = Undefined,
index: bool | UndefinedType = Undefined,
sa_type: type[Any] | UndefinedType = Undefined,
sa_column: Column | UndefinedType = Undefined, # type: ignore
sa_column: Column | UndefinedType = Undefined,
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
schema_extra: dict[str, Any] | None = None,
Expand DownExpand Up@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
model_fields: ClassVar[dict[str, FieldInfo]]

# Replicate SQLAlchemy
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #1806 (comment)

if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
else:
super().__setattr__(name, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, name: str) -> None: # ty: ignore[invalid-method-override]
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
else:
Expand DownExpand Up@@ -609,10 +608,10 @@ def get_config(name: str) -> Any:
# This could be done by reading new_cls.model_config['table'] in FastAPI, but
# that's very specific about SQLModel, so let's have another config that
# other future tools based on Pydantic can use.
new_cls.model_config["read_from_attributes"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_from_attributes"] = True # ty: ignore[invalid-key]
# For compatibility with older versions
# TODO: remove this in the future
new_cls.model_config["read_with_orm_mode"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_with_orm_mode"] = True # ty: ignore[invalid-key]

config_registry = get_config("registry")
if config_registry is not Undefined:
Expand DownExpand Up@@ -649,7 +648,7 @@ def __init__(
# Plain forward references, for models not yet defined, are not
# handled well by SQLAlchemy without Mapped, so, wrap the
# annotations in Mapped here
cls.__annotations__[rel_name] = Mapped[ann] # type: ignore[valid-type]
cls.__annotations__[rel_name] = Mapped[ann]
relationship_to = get_relationship_to(
name=rel_name, rel_info=rel_info, annotation=ann
)
Expand DownExpand Up@@ -738,7 +737,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
raise ValueError(f"{type_} has no matching SQLAlchemy type")


def get_column_from_field(field: Any) -> Column: # type: ignore
def get_column_from_field(field: Any) -> Column:
field_info = field
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
if isinstance(sa_column, Column):
Expand DownExpand Up@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
assert isinstance(foreign_key, str)
assert isinstance(ondelete_value, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
kwargs = {
kwargs: dict[str, Any] = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
Expand All@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
return Column(sa_type, *args, **kwargs)


class_registry = weakref.WeakValueDictionary() # type: ignore

default_registry = registry()

_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
Expand DownExpand Up@@ -852,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
Expand All@@ -872,7 +869,7 @@ def __tablename__(cls) -> str:
return cls.__name__.lower()

@classmethod
def model_validate( # type: ignore[override]
def model_validate( # ty: ignore[invalid-method-override]
cls: type[_TSQLModel],
obj: Any,
*,
Expand Down
4 changes: 2 additions & 2 deletions sqlmodel/sql/_expression_select_cls.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Comment thread
svlandeg marked this conversation as resolved.

def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `HAVING` clause, joined to the existing clause via `AND`, if any.
"""
return super().having(*having) # type: ignore[arg-type]
return super().having(*having)


class Select(SelectBase[_T]):
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0251f0f
add ty to lint, precommit, pyproject and uv lock
svlandeg Mar 9, 2026
a52380b
add type annotation to kwargs dict
svlandeg Mar 9, 2026
3203f83
remove unnecessary type ignore statements
svlandeg Mar 9, 2026
5d9dd59
🎨 Auto format
pre-commit-ci-lite[bot] Mar 9, 2026
5c9b1d5
avoid using double underscore when any positional-or-keyword paramete…
svlandeg Mar 9, 2026
c1b0465
one more
svlandeg Mar 9, 2026
732847d
remove mypy
svlandeg Mar 9, 2026
2f8efa3
Merge branch 'main' into feat/ty
svlandeg Mar 10, 2026
dc8593b
use same names as upstream class
svlandeg Mar 10, 2026
1dceaa7
fix generator
svlandeg Mar 10, 2026
02cd800
Merge branch 'main' into feat/ty
svlandeg Mar 14, 2026
c49bf7f
Merge branch 'main' into feat/ty
svlandeg Mar 16, 2026
277366f
use / to denote positional-only parameters
svlandeg Mar 18, 2026
b511af0
🎨 Auto format
pre-commit-ci-lite[bot] Mar 18, 2026
7aebcc9
remove class_registry
svlandeg Mar 18, 2026
c6e97e7
revert attr naming and add ty ignore instead
svlandeg Mar 18, 2026
9103510
Merge branch 'main' into feat/ty
svlandeg Mar 18, 2026
807d9d6
ty check tests/test_select_typing.py as well
svlandeg Mar 25, 2026
51b72e1
Merge branch 'main' into feat/ty
svlandeg Mar 25, 2026
fa6df49
ensure that a ty warning creates an error
svlandeg Mar 26, 2026
0f2ea9c
🎨 Auto format
pre-commit-ci-lite[bot] Mar 26, 2026
9cd8a87
Merge branch 'main' into feat/ty
svlandeg Mar 26, 2026
fef2ff7
upgrade lower ty bound to 0.0.25
svlandeg Mar 26, 2026
a1e581a
change mypy codes to ty codes
svlandeg Mar 27, 2026
becee62
Merge branch 'main' into feat/ty
svlandeg Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,9 @@ repos:
language: unsupported
types: [python]

- id: local-mypy
name: mypy check
entry: uv run mypy sqlmodel tests/test_select_typing.py
- id: local-ty
name: ty check
entry: uv run ty check sqlmodel tests/test_select_typing.py
require_serial: true
language: unsupported
pass_filenames: false
Expand Down
15 changes: 4 additions & 11 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,9 @@ tests = [
"fastapi >=0.128.0",
"httpx >=0.28.1",
"jinja2 >=3.1.6",
"mypy >=1.19.1",
"pytest >=7.0.1",
"ruff >=0.15.6",
"ty>=0.0.25",
"typing-extensions >=4.15.0",
]

Expand DownExpand Up@@ -125,16 +125,6 @@ exclude_lines = [
[tool.coverage.html]
show_contexts = true

[tool.mypy]
strict = true
exclude = "sqlmodel.sql._expression_select_gen"

[[tool.mypy.overrides]]
module = "docs_src.*"
disallow_incomplete_defs = false
disallow_untyped_defs = false
disallow_untyped_calls = false

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
Expand All@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.ty.terminal]
error-on-warning = true
2 changes: 1 addition & 1 deletion scripts/generate_select.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ class Arg(BaseModel):
else:
t_type = f"_T{i}"
t_var = f"_TCCA[{t_type}]"
arg = Arg(name=f"__ent{i}", annotation=t_var)
arg = Arg(name=f"ent{i}", annotation=t_var)
ret_type = t_type
args.append(arg)
return_types.append(ret_type)
Expand Down
4 changes: 2 additions & 2 deletions scripts/lint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
set -e
set -x

mypy sqlmodel
mypy tests/test_select_typing.py
ty check sqlmodel
ty check tests/test_select_typing.py
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
29 changes: 13 additions & 16 deletions sqlmodel/main.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import builtins
import ipaddress
import uuid
import weakref
from collections.abc import Callable, Mapping, Sequence, Set
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
Expand DownExpand Up@@ -52,7 +51,7 @@
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import deprecated

from ._compat import ( # type: ignore[attr-defined]
from ._compat import (
PYDANTIC_MINOR_VERSION,
BaseConfig,
ModelMetaclass,
Expand DownExpand Up@@ -101,7 +100,7 @@ def __dataclass_transform__(
return lambda a: a


class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
# mypy - ignore that PydanticFieldInfo is @final
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
primary_key = kwargs.pop("primary_key", False)
Expand DownExpand Up@@ -177,7 +176,7 @@ def __init__(
cascade_delete: bool | None = False,
passive_deletes: bool | Literal["all"] | None = False,
link_model: Any | None = None,
sa_relationship: RelationshipProperty | None = None, # type: ignore
sa_relationship: RelationshipProperty | None = None,
sa_relationship_args: Sequence[Any] | None = None,
sa_relationship_kwargs: Mapping[str, Any] | None = None,
) -> None:
Expand DownExpand Up@@ -398,7 +397,7 @@ def Field(
nullable: bool | UndefinedType = Undefined,
index: bool | UndefinedType = Undefined,
sa_type: type[Any] | UndefinedType = Undefined,
sa_column: Column | UndefinedType = Undefined, # type: ignore
sa_column: Column | UndefinedType = Undefined,
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
schema_extra: dict[str, Any] | None = None,
Expand DownExpand Up@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
model_fields: ClassVar[dict[str, FieldInfo]]

# Replicate SQLAlchemy
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #1806 (comment)

if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
else:
super().__setattr__(name, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, name: str) -> None: # ty: ignore[invalid-method-override]
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
else:
Expand DownExpand Up@@ -609,10 +608,10 @@ def get_config(name: str) -> Any:
# This could be done by reading new_cls.model_config['table'] in FastAPI, but
# that's very specific about SQLModel, so let's have another config that
# other future tools based on Pydantic can use.
new_cls.model_config["read_from_attributes"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_from_attributes"] = True # ty: ignore[invalid-key]
# For compatibility with older versions
# TODO: remove this in the future
new_cls.model_config["read_with_orm_mode"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_with_orm_mode"] = True # ty: ignore[invalid-key]

config_registry = get_config("registry")
if config_registry is not Undefined:
Expand DownExpand Up@@ -649,7 +648,7 @@ def __init__(
# Plain forward references, for models not yet defined, are not
# handled well by SQLAlchemy without Mapped, so, wrap the
# annotations in Mapped here
cls.__annotations__[rel_name] = Mapped[ann] # type: ignore[valid-type]
cls.__annotations__[rel_name] = Mapped[ann]
relationship_to = get_relationship_to(
name=rel_name, rel_info=rel_info, annotation=ann
)
Expand DownExpand Up@@ -738,7 +737,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
raise ValueError(f"{type_} has no matching SQLAlchemy type")


def get_column_from_field(field: Any) -> Column: # type: ignore
def get_column_from_field(field: Any) -> Column:
field_info = field
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
if isinstance(sa_column, Column):
Expand DownExpand Up@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
assert isinstance(foreign_key, str)
assert isinstance(ondelete_value, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
kwargs = {
kwargs: dict[str, Any] = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
Expand All@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
return Column(sa_type, *args, **kwargs)


class_registry = weakref.WeakValueDictionary() # type: ignore

default_registry = registry()

_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
Expand DownExpand Up@@ -852,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
Expand All@@ -872,7 +869,7 @@ def __tablename__(cls) -> str:
return cls.__name__.lower()

@classmethod
def model_validate( # type: ignore[override]
def model_validate( # ty: ignore[invalid-method-override]
cls: type[_TSQLModel],
obj: Any,
*,
Expand Down
4 changes: 2 additions & 2 deletions sqlmodel/sql/_expression_select_cls.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Comment thread
svlandeg marked this conversation as resolved.

def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `HAVING` clause, joined to the existing clause via `AND`, if any.
"""
return super().having(*having) # type: ignore[arg-type]
return super().having(*having)


class Select(SelectBase[_T]):
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0251f0f
add ty to lint, precommit, pyproject and uv lock
svlandeg Mar 9, 2026
a52380b
add type annotation to kwargs dict
svlandeg Mar 9, 2026
3203f83
remove unnecessary type ignore statements
svlandeg Mar 9, 2026
5d9dd59
🎨 Auto format
pre-commit-ci-lite[bot] Mar 9, 2026
5c9b1d5
avoid using double underscore when any positional-or-keyword paramete…
svlandeg Mar 9, 2026
c1b0465
one more
svlandeg Mar 9, 2026
732847d
remove mypy
svlandeg Mar 9, 2026
2f8efa3
Merge branch 'main' into feat/ty
svlandeg Mar 10, 2026
dc8593b
use same names as upstream class
svlandeg Mar 10, 2026
1dceaa7
fix generator
svlandeg Mar 10, 2026
02cd800
Merge branch 'main' into feat/ty
svlandeg Mar 14, 2026
c49bf7f
Merge branch 'main' into feat/ty
svlandeg Mar 16, 2026
277366f
use / to denote positional-only parameters
svlandeg Mar 18, 2026
b511af0
🎨 Auto format
pre-commit-ci-lite[bot] Mar 18, 2026
7aebcc9
remove class_registry
svlandeg Mar 18, 2026
c6e97e7
revert attr naming and add ty ignore instead
svlandeg Mar 18, 2026
9103510
Merge branch 'main' into feat/ty
svlandeg Mar 18, 2026
807d9d6
ty check tests/test_select_typing.py as well
svlandeg Mar 25, 2026
51b72e1
Merge branch 'main' into feat/ty
svlandeg Mar 25, 2026
fa6df49
ensure that a ty warning creates an error
svlandeg Mar 26, 2026
0f2ea9c
🎨 Auto format
pre-commit-ci-lite[bot] Mar 26, 2026
9cd8a87
Merge branch 'main' into feat/ty
svlandeg Mar 26, 2026
fef2ff7
upgrade lower ty bound to 0.0.25
svlandeg Mar 26, 2026
a1e581a
change mypy codes to ty codes
svlandeg Mar 27, 2026
becee62
Merge branch 'main' into feat/ty
svlandeg Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,9 @@ repos:
language: unsupported
types: [python]

- id: local-mypy
name: mypy check
entry: uv run mypy sqlmodel tests/test_select_typing.py
- id: local-ty
name: ty check
entry: uv run ty check sqlmodel tests/test_select_typing.py
require_serial: true
language: unsupported
pass_filenames: false
Expand Down
15 changes: 4 additions & 11 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,9 @@ tests = [
"fastapi >=0.128.0",
"httpx >=0.28.1",
"jinja2 >=3.1.6",
"mypy >=1.19.1",
"pytest >=7.0.1",
"ruff >=0.15.6",
"ty>=0.0.25",
"typing-extensions >=4.15.0",
]

Expand DownExpand Up@@ -125,16 +125,6 @@ exclude_lines = [
[tool.coverage.html]
show_contexts = true

[tool.mypy]
strict = true
exclude = "sqlmodel.sql._expression_select_gen"

[[tool.mypy.overrides]]
module = "docs_src.*"
disallow_incomplete_defs = false
disallow_untyped_defs = false
disallow_untyped_calls = false

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
Expand All@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.ty.terminal]
error-on-warning = true
2 changes: 1 addition & 1 deletion scripts/generate_select.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ class Arg(BaseModel):
else:
t_type = f"_T{i}"
t_var = f"_TCCA[{t_type}]"
arg = Arg(name=f"__ent{i}", annotation=t_var)
arg = Arg(name=f"ent{i}", annotation=t_var)
ret_type = t_type
args.append(arg)
return_types.append(ret_type)
Expand Down
4 changes: 2 additions & 2 deletions scripts/lint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
set -e
set -x

mypy sqlmodel
mypy tests/test_select_typing.py
ty check sqlmodel
ty check tests/test_select_typing.py
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
29 changes: 13 additions & 16 deletions sqlmodel/main.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import builtins
import ipaddress
import uuid
import weakref
from collections.abc import Callable, Mapping, Sequence, Set
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
Expand DownExpand Up@@ -52,7 +51,7 @@
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import deprecated

from ._compat import ( # type: ignore[attr-defined]
from ._compat import (
PYDANTIC_MINOR_VERSION,
BaseConfig,
ModelMetaclass,
Expand DownExpand Up@@ -101,7 +100,7 @@ def __dataclass_transform__(
return lambda a: a


class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
# mypy - ignore that PydanticFieldInfo is @final
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
primary_key = kwargs.pop("primary_key", False)
Expand DownExpand Up@@ -177,7 +176,7 @@ def __init__(
cascade_delete: bool | None = False,
passive_deletes: bool | Literal["all"] | None = False,
link_model: Any | None = None,
sa_relationship: RelationshipProperty | None = None, # type: ignore
sa_relationship: RelationshipProperty | None = None,
sa_relationship_args: Sequence[Any] | None = None,
sa_relationship_kwargs: Mapping[str, Any] | None = None,
) -> None:
Expand DownExpand Up@@ -398,7 +397,7 @@ def Field(
nullable: bool | UndefinedType = Undefined,
index: bool | UndefinedType = Undefined,
sa_type: type[Any] | UndefinedType = Undefined,
sa_column: Column | UndefinedType = Undefined, # type: ignore
sa_column: Column | UndefinedType = Undefined,
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
schema_extra: dict[str, Any] | None = None,
Expand DownExpand Up@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
model_fields: ClassVar[dict[str, FieldInfo]]

# Replicate SQLAlchemy
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #1806 (comment)

if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
else:
super().__setattr__(name, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, name: str) -> None: # ty: ignore[invalid-method-override]
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
else:
Expand DownExpand Up@@ -609,10 +608,10 @@ def get_config(name: str) -> Any:
# This could be done by reading new_cls.model_config['table'] in FastAPI, but
# that's very specific about SQLModel, so let's have another config that
# other future tools based on Pydantic can use.
new_cls.model_config["read_from_attributes"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_from_attributes"] = True # ty: ignore[invalid-key]
# For compatibility with older versions
# TODO: remove this in the future
new_cls.model_config["read_with_orm_mode"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_with_orm_mode"] = True # ty: ignore[invalid-key]

config_registry = get_config("registry")
if config_registry is not Undefined:
Expand DownExpand Up@@ -649,7 +648,7 @@ def __init__(
# Plain forward references, for models not yet defined, are not
# handled well by SQLAlchemy without Mapped, so, wrap the
# annotations in Mapped here
cls.__annotations__[rel_name] = Mapped[ann] # type: ignore[valid-type]
cls.__annotations__[rel_name] = Mapped[ann]
relationship_to = get_relationship_to(
name=rel_name, rel_info=rel_info, annotation=ann
)
Expand DownExpand Up@@ -738,7 +737,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
raise ValueError(f"{type_} has no matching SQLAlchemy type")


def get_column_from_field(field: Any) -> Column: # type: ignore
def get_column_from_field(field: Any) -> Column:
field_info = field
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
if isinstance(sa_column, Column):
Expand DownExpand Up@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
assert isinstance(foreign_key, str)
assert isinstance(ondelete_value, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
kwargs = {
kwargs: dict[str, Any] = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
Expand All@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
return Column(sa_type, *args, **kwargs)


class_registry = weakref.WeakValueDictionary() # type: ignore

default_registry = registry()

_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
Expand DownExpand Up@@ -852,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
Expand All@@ -872,7 +869,7 @@ def __tablename__(cls) -> str:
return cls.__name__.lower()

@classmethod
def model_validate( # type: ignore[override]
def model_validate( # ty: ignore[invalid-method-override]
cls: type[_TSQLModel],
obj: Any,
*,
Expand Down
4 changes: 2 additions & 2 deletions sqlmodel/sql/_expression_select_cls.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Comment thread
svlandeg marked this conversation as resolved.

def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `HAVING` clause, joined to the existing clause via `AND`, if any.
"""
return super().having(*having) # type: ignore[arg-type]
return super().having(*having)


class Select(SelectBase[_T]):
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0251f0f
add ty to lint, precommit, pyproject and uv lock
svlandeg Mar 9, 2026
a52380b
add type annotation to kwargs dict
svlandeg Mar 9, 2026
3203f83
remove unnecessary type ignore statements
svlandeg Mar 9, 2026
5d9dd59
🎨 Auto format
pre-commit-ci-lite[bot] Mar 9, 2026
5c9b1d5
avoid using double underscore when any positional-or-keyword paramete…
svlandeg Mar 9, 2026
c1b0465
one more
svlandeg Mar 9, 2026
732847d
remove mypy
svlandeg Mar 9, 2026
2f8efa3
Merge branch 'main' into feat/ty
svlandeg Mar 10, 2026
dc8593b
use same names as upstream class
svlandeg Mar 10, 2026
1dceaa7
fix generator
svlandeg Mar 10, 2026
02cd800
Merge branch 'main' into feat/ty
svlandeg Mar 14, 2026
c49bf7f
Merge branch 'main' into feat/ty
svlandeg Mar 16, 2026
277366f
use / to denote positional-only parameters
svlandeg Mar 18, 2026
b511af0
🎨 Auto format
pre-commit-ci-lite[bot] Mar 18, 2026
7aebcc9
remove class_registry
svlandeg Mar 18, 2026
c6e97e7
revert attr naming and add ty ignore instead
svlandeg Mar 18, 2026
9103510
Merge branch 'main' into feat/ty
svlandeg Mar 18, 2026
807d9d6
ty check tests/test_select_typing.py as well
svlandeg Mar 25, 2026
51b72e1
Merge branch 'main' into feat/ty
svlandeg Mar 25, 2026
fa6df49
ensure that a ty warning creates an error
svlandeg Mar 26, 2026
0f2ea9c
🎨 Auto format
pre-commit-ci-lite[bot] Mar 26, 2026
9cd8a87
Merge branch 'main' into feat/ty
svlandeg Mar 26, 2026
fef2ff7
upgrade lower ty bound to 0.0.25
svlandeg Mar 26, 2026
a1e581a
change mypy codes to ty codes
svlandeg Mar 27, 2026
becee62
Merge branch 'main' into feat/ty
svlandeg Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,9 @@ repos:
language: unsupported
types: [python]

- id: local-mypy
name: mypy check
entry: uv run mypy sqlmodel tests/test_select_typing.py
- id: local-ty
name: ty check
entry: uv run ty check sqlmodel tests/test_select_typing.py
require_serial: true
language: unsupported
pass_filenames: false
Expand Down
15 changes: 4 additions & 11 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,9 @@ tests = [
"fastapi >=0.128.0",
"httpx >=0.28.1",
"jinja2 >=3.1.6",
"mypy >=1.19.1",
"pytest >=7.0.1",
"ruff >=0.15.6",
"ty>=0.0.25",
"typing-extensions >=4.15.0",
]

Expand DownExpand Up@@ -125,16 +125,6 @@ exclude_lines = [
[tool.coverage.html]
show_contexts = true

[tool.mypy]
strict = true
exclude = "sqlmodel.sql._expression_select_gen"

[[tool.mypy.overrides]]
module = "docs_src.*"
disallow_incomplete_defs = false
disallow_untyped_defs = false
disallow_untyped_calls = false

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
Expand All@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.ty.terminal]
error-on-warning = true
2 changes: 1 addition & 1 deletion scripts/generate_select.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ class Arg(BaseModel):
else:
t_type = f"_T{i}"
t_var = f"_TCCA[{t_type}]"
arg = Arg(name=f"__ent{i}", annotation=t_var)
arg = Arg(name=f"ent{i}", annotation=t_var)
ret_type = t_type
args.append(arg)
return_types.append(ret_type)
Expand Down
4 changes: 2 additions & 2 deletions scripts/lint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
set -e
set -x

mypy sqlmodel
mypy tests/test_select_typing.py
ty check sqlmodel
ty check tests/test_select_typing.py
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
29 changes: 13 additions & 16 deletions sqlmodel/main.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import builtins
import ipaddress
import uuid
import weakref
from collections.abc import Callable, Mapping, Sequence, Set
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
Expand DownExpand Up@@ -52,7 +51,7 @@
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import deprecated

from ._compat import ( # type: ignore[attr-defined]
from ._compat import (
PYDANTIC_MINOR_VERSION,
BaseConfig,
ModelMetaclass,
Expand DownExpand Up@@ -101,7 +100,7 @@ def __dataclass_transform__(
return lambda a: a


class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
# mypy - ignore that PydanticFieldInfo is @final
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
primary_key = kwargs.pop("primary_key", False)
Expand DownExpand Up@@ -177,7 +176,7 @@ def __init__(
cascade_delete: bool | None = False,
passive_deletes: bool | Literal["all"] | None = False,
link_model: Any | None = None,
sa_relationship: RelationshipProperty | None = None, # type: ignore
sa_relationship: RelationshipProperty | None = None,
sa_relationship_args: Sequence[Any] | None = None,
sa_relationship_kwargs: Mapping[str, Any] | None = None,
) -> None:
Expand DownExpand Up@@ -398,7 +397,7 @@ def Field(
nullable: bool | UndefinedType = Undefined,
index: bool | UndefinedType = Undefined,
sa_type: type[Any] | UndefinedType = Undefined,
sa_column: Column | UndefinedType = Undefined, # type: ignore
sa_column: Column | UndefinedType = Undefined,
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
schema_extra: dict[str, Any] | None = None,
Expand DownExpand Up@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
model_fields: ClassVar[dict[str, FieldInfo]]

# Replicate SQLAlchemy
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #1806 (comment)

if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
else:
super().__setattr__(name, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, name: str) -> None: # ty: ignore[invalid-method-override]
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
else:
Expand DownExpand Up@@ -609,10 +608,10 @@ def get_config(name: str) -> Any:
# This could be done by reading new_cls.model_config['table'] in FastAPI, but
# that's very specific about SQLModel, so let's have another config that
# other future tools based on Pydantic can use.
new_cls.model_config["read_from_attributes"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_from_attributes"] = True # ty: ignore[invalid-key]
# For compatibility with older versions
# TODO: remove this in the future
new_cls.model_config["read_with_orm_mode"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_with_orm_mode"] = True # ty: ignore[invalid-key]

config_registry = get_config("registry")
if config_registry is not Undefined:
Expand DownExpand Up@@ -649,7 +648,7 @@ def __init__(
# Plain forward references, for models not yet defined, are not
# handled well by SQLAlchemy without Mapped, so, wrap the
# annotations in Mapped here
cls.__annotations__[rel_name] = Mapped[ann] # type: ignore[valid-type]
cls.__annotations__[rel_name] = Mapped[ann]
relationship_to = get_relationship_to(
name=rel_name, rel_info=rel_info, annotation=ann
)
Expand DownExpand Up@@ -738,7 +737,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
raise ValueError(f"{type_} has no matching SQLAlchemy type")


def get_column_from_field(field: Any) -> Column: # type: ignore
def get_column_from_field(field: Any) -> Column:
field_info = field
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
if isinstance(sa_column, Column):
Expand DownExpand Up@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
assert isinstance(foreign_key, str)
assert isinstance(ondelete_value, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
kwargs = {
kwargs: dict[str, Any] = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
Expand All@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
return Column(sa_type, *args, **kwargs)


class_registry = weakref.WeakValueDictionary() # type: ignore

default_registry = registry()

_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
Expand DownExpand Up@@ -852,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
Expand All@@ -872,7 +869,7 @@ def __tablename__(cls) -> str:
return cls.__name__.lower()

@classmethod
def model_validate( # type: ignore[override]
def model_validate( # ty: ignore[invalid-method-override]
cls: type[_TSQLModel],
obj: Any,
*,
Expand Down
4 changes: 2 additions & 2 deletions sqlmodel/sql/_expression_select_cls.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Comment thread
svlandeg marked this conversation as resolved.

def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `HAVING` clause, joined to the existing clause via `AND`, if any.
"""
return super().having(*having) # type: ignore[arg-type]
return super().having(*having)


class Select(SelectBase[_T]):
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0251f0f
add ty to lint, precommit, pyproject and uv lock
svlandeg Mar 9, 2026
a52380b
add type annotation to kwargs dict
svlandeg Mar 9, 2026
3203f83
remove unnecessary type ignore statements
svlandeg Mar 9, 2026
5d9dd59
🎨 Auto format
pre-commit-ci-lite[bot] Mar 9, 2026
5c9b1d5
avoid using double underscore when any positional-or-keyword paramete…
svlandeg Mar 9, 2026
c1b0465
one more
svlandeg Mar 9, 2026
732847d
remove mypy
svlandeg Mar 9, 2026
2f8efa3
Merge branch 'main' into feat/ty
svlandeg Mar 10, 2026
dc8593b
use same names as upstream class
svlandeg Mar 10, 2026
1dceaa7
fix generator
svlandeg Mar 10, 2026
02cd800
Merge branch 'main' into feat/ty
svlandeg Mar 14, 2026
c49bf7f
Merge branch 'main' into feat/ty
svlandeg Mar 16, 2026
277366f
use / to denote positional-only parameters
svlandeg Mar 18, 2026
b511af0
🎨 Auto format
pre-commit-ci-lite[bot] Mar 18, 2026
7aebcc9
remove class_registry
svlandeg Mar 18, 2026
c6e97e7
revert attr naming and add ty ignore instead
svlandeg Mar 18, 2026
9103510
Merge branch 'main' into feat/ty
svlandeg Mar 18, 2026
807d9d6
ty check tests/test_select_typing.py as well
svlandeg Mar 25, 2026
51b72e1
Merge branch 'main' into feat/ty
svlandeg Mar 25, 2026
fa6df49
ensure that a ty warning creates an error
svlandeg Mar 26, 2026
0f2ea9c
🎨 Auto format
pre-commit-ci-lite[bot] Mar 26, 2026
9cd8a87
Merge branch 'main' into feat/ty
svlandeg Mar 26, 2026
fef2ff7
upgrade lower ty bound to 0.0.25
svlandeg Mar 26, 2026
a1e581a
change mypy codes to ty codes
svlandeg Mar 27, 2026
becee62
Merge branch 'main' into feat/ty
svlandeg Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,9 @@ repos:
language: unsupported
types: [python]

- id: local-mypy
name: mypy check
entry: uv run mypy sqlmodel tests/test_select_typing.py
- id: local-ty
name: ty check
entry: uv run ty check sqlmodel tests/test_select_typing.py
require_serial: true
language: unsupported
pass_filenames: false
Expand Down
15 changes: 4 additions & 11 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,9 @@ tests = [
"fastapi >=0.128.0",
"httpx >=0.28.1",
"jinja2 >=3.1.6",
"mypy >=1.19.1",
"pytest >=7.0.1",
"ruff >=0.15.6",
"ty>=0.0.25",
"typing-extensions >=4.15.0",
]

Expand DownExpand Up@@ -125,16 +125,6 @@ exclude_lines = [
[tool.coverage.html]
show_contexts = true

[tool.mypy]
strict = true
exclude = "sqlmodel.sql._expression_select_gen"

[[tool.mypy.overrides]]
module = "docs_src.*"
disallow_incomplete_defs = false
disallow_untyped_defs = false
disallow_untyped_calls = false

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
Expand All@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.ty.terminal]
error-on-warning = true
2 changes: 1 addition & 1 deletion scripts/generate_select.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ class Arg(BaseModel):
else:
t_type = f"_T{i}"
t_var = f"_TCCA[{t_type}]"
arg = Arg(name=f"__ent{i}", annotation=t_var)
arg = Arg(name=f"ent{i}", annotation=t_var)
ret_type = t_type
args.append(arg)
return_types.append(ret_type)
Expand Down
4 changes: 2 additions & 2 deletions scripts/lint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
set -e
set -x

mypy sqlmodel
mypy tests/test_select_typing.py
ty check sqlmodel
ty check tests/test_select_typing.py
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
29 changes: 13 additions & 16 deletions sqlmodel/main.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import builtins
import ipaddress
import uuid
import weakref
from collections.abc import Callable, Mapping, Sequence, Set
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
Expand DownExpand Up@@ -52,7 +51,7 @@
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import deprecated

from ._compat import ( # type: ignore[attr-defined]
from ._compat import (
PYDANTIC_MINOR_VERSION,
BaseConfig,
ModelMetaclass,
Expand DownExpand Up@@ -101,7 +100,7 @@ def __dataclass_transform__(
return lambda a: a


class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
# mypy - ignore that PydanticFieldInfo is @final
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
primary_key = kwargs.pop("primary_key", False)
Expand DownExpand Up@@ -177,7 +176,7 @@ def __init__(
cascade_delete: bool | None = False,
passive_deletes: bool | Literal["all"] | None = False,
link_model: Any | None = None,
sa_relationship: RelationshipProperty | None = None, # type: ignore
sa_relationship: RelationshipProperty | None = None,
sa_relationship_args: Sequence[Any] | None = None,
sa_relationship_kwargs: Mapping[str, Any] | None = None,
) -> None:
Expand DownExpand Up@@ -398,7 +397,7 @@ def Field(
nullable: bool | UndefinedType = Undefined,
index: bool | UndefinedType = Undefined,
sa_type: type[Any] | UndefinedType = Undefined,
sa_column: Column | UndefinedType = Undefined, # type: ignore
sa_column: Column | UndefinedType = Undefined,
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
schema_extra: dict[str, Any] | None = None,
Expand DownExpand Up@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
model_fields: ClassVar[dict[str, FieldInfo]]

# Replicate SQLAlchemy
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #1806 (comment)

if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
else:
super().__setattr__(name, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, name: str) -> None: # ty: ignore[invalid-method-override]
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
else:
Expand DownExpand Up@@ -609,10 +608,10 @@ def get_config(name: str) -> Any:
# This could be done by reading new_cls.model_config['table'] in FastAPI, but
# that's very specific about SQLModel, so let's have another config that
# other future tools based on Pydantic can use.
new_cls.model_config["read_from_attributes"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_from_attributes"] = True # ty: ignore[invalid-key]
# For compatibility with older versions
# TODO: remove this in the future
new_cls.model_config["read_with_orm_mode"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_with_orm_mode"] = True # ty: ignore[invalid-key]

config_registry = get_config("registry")
if config_registry is not Undefined:
Expand DownExpand Up@@ -649,7 +648,7 @@ def __init__(
# Plain forward references, for models not yet defined, are not
# handled well by SQLAlchemy without Mapped, so, wrap the
# annotations in Mapped here
cls.__annotations__[rel_name] = Mapped[ann] # type: ignore[valid-type]
cls.__annotations__[rel_name] = Mapped[ann]
relationship_to = get_relationship_to(
name=rel_name, rel_info=rel_info, annotation=ann
)
Expand DownExpand Up@@ -738,7 +737,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
raise ValueError(f"{type_} has no matching SQLAlchemy type")


def get_column_from_field(field: Any) -> Column: # type: ignore
def get_column_from_field(field: Any) -> Column:
field_info = field
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
if isinstance(sa_column, Column):
Expand DownExpand Up@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
assert isinstance(foreign_key, str)
assert isinstance(ondelete_value, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
kwargs = {
kwargs: dict[str, Any] = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
Expand All@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
return Column(sa_type, *args, **kwargs)


class_registry = weakref.WeakValueDictionary() # type: ignore

default_registry = registry()

_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
Expand DownExpand Up@@ -852,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
Expand All@@ -872,7 +869,7 @@ def __tablename__(cls) -> str:
return cls.__name__.lower()

@classmethod
def model_validate( # type: ignore[override]
def model_validate( # ty: ignore[invalid-method-override]
cls: type[_TSQLModel],
obj: Any,
*,
Expand Down
4 changes: 2 additions & 2 deletions sqlmodel/sql/_expression_select_cls.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Comment thread
svlandeg marked this conversation as resolved.

def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `HAVING` clause, joined to the existing clause via `AND`, if any.
"""
return super().having(*having) # type: ignore[arg-type]
return super().having(*having)


class Select(SelectBase[_T]):
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0251f0f
add ty to lint, precommit, pyproject and uv lock
svlandeg Mar 9, 2026
a52380b
add type annotation to kwargs dict
svlandeg Mar 9, 2026
3203f83
remove unnecessary type ignore statements
svlandeg Mar 9, 2026
5d9dd59
🎨 Auto format
pre-commit-ci-lite[bot] Mar 9, 2026
5c9b1d5
avoid using double underscore when any positional-or-keyword paramete…
svlandeg Mar 9, 2026
c1b0465
one more
svlandeg Mar 9, 2026
732847d
remove mypy
svlandeg Mar 9, 2026
2f8efa3
Merge branch 'main' into feat/ty
svlandeg Mar 10, 2026
dc8593b
use same names as upstream class
svlandeg Mar 10, 2026
1dceaa7
fix generator
svlandeg Mar 10, 2026
02cd800
Merge branch 'main' into feat/ty
svlandeg Mar 14, 2026
c49bf7f
Merge branch 'main' into feat/ty
svlandeg Mar 16, 2026
277366f
use / to denote positional-only parameters
svlandeg Mar 18, 2026
b511af0
🎨 Auto format
pre-commit-ci-lite[bot] Mar 18, 2026
7aebcc9
remove class_registry
svlandeg Mar 18, 2026
c6e97e7
revert attr naming and add ty ignore instead
svlandeg Mar 18, 2026
9103510
Merge branch 'main' into feat/ty
svlandeg Mar 18, 2026
807d9d6
ty check tests/test_select_typing.py as well
svlandeg Mar 25, 2026
51b72e1
Merge branch 'main' into feat/ty
svlandeg Mar 25, 2026
fa6df49
ensure that a ty warning creates an error
svlandeg Mar 26, 2026
0f2ea9c
🎨 Auto format
pre-commit-ci-lite[bot] Mar 26, 2026
9cd8a87
Merge branch 'main' into feat/ty
svlandeg Mar 26, 2026
fef2ff7
upgrade lower ty bound to 0.0.25
svlandeg Mar 26, 2026
a1e581a
change mypy codes to ty codes
svlandeg Mar 27, 2026
becee62
Merge branch 'main' into feat/ty
svlandeg Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,9 @@ repos:
language: unsupported
types: [python]

- id: local-mypy
name: mypy check
entry: uv run mypy sqlmodel tests/test_select_typing.py
- id: local-ty
name: ty check
entry: uv run ty check sqlmodel tests/test_select_typing.py
require_serial: true
language: unsupported
pass_filenames: false
Expand Down
15 changes: 4 additions & 11 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,9 @@ tests = [
"fastapi >=0.128.0",
"httpx >=0.28.1",
"jinja2 >=3.1.6",
"mypy >=1.19.1",
"pytest >=7.0.1",
"ruff >=0.15.6",
"ty>=0.0.25",
"typing-extensions >=4.15.0",
]

Expand DownExpand Up@@ -125,16 +125,6 @@ exclude_lines = [
[tool.coverage.html]
show_contexts = true

[tool.mypy]
strict = true
exclude = "sqlmodel.sql._expression_select_gen"

[[tool.mypy.overrides]]
module = "docs_src.*"
disallow_incomplete_defs = false
disallow_untyped_defs = false
disallow_untyped_calls = false

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
Expand All@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.ty.terminal]
error-on-warning = true
2 changes: 1 addition & 1 deletion scripts/generate_select.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ class Arg(BaseModel):
else:
t_type = f"_T{i}"
t_var = f"_TCCA[{t_type}]"
arg = Arg(name=f"__ent{i}", annotation=t_var)
arg = Arg(name=f"ent{i}", annotation=t_var)
ret_type = t_type
args.append(arg)
return_types.append(ret_type)
Expand Down
4 changes: 2 additions & 2 deletions scripts/lint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
set -e
set -x

mypy sqlmodel
mypy tests/test_select_typing.py
ty check sqlmodel
ty check tests/test_select_typing.py
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
29 changes: 13 additions & 16 deletions sqlmodel/main.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import builtins
import ipaddress
import uuid
import weakref
from collections.abc import Callable, Mapping, Sequence, Set
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
Expand DownExpand Up@@ -52,7 +51,7 @@
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import deprecated

from ._compat import ( # type: ignore[attr-defined]
from ._compat import (
PYDANTIC_MINOR_VERSION,
BaseConfig,
ModelMetaclass,
Expand DownExpand Up@@ -101,7 +100,7 @@ def __dataclass_transform__(
return lambda a: a


class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
# mypy - ignore that PydanticFieldInfo is @final
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
primary_key = kwargs.pop("primary_key", False)
Expand DownExpand Up@@ -177,7 +176,7 @@ def __init__(
cascade_delete: bool | None = False,
passive_deletes: bool | Literal["all"] | None = False,
link_model: Any | None = None,
sa_relationship: RelationshipProperty | None = None, # type: ignore
sa_relationship: RelationshipProperty | None = None,
sa_relationship_args: Sequence[Any] | None = None,
sa_relationship_kwargs: Mapping[str, Any] | None = None,
) -> None:
Expand DownExpand Up@@ -398,7 +397,7 @@ def Field(
nullable: bool | UndefinedType = Undefined,
index: bool | UndefinedType = Undefined,
sa_type: type[Any] | UndefinedType = Undefined,
sa_column: Column | UndefinedType = Undefined, # type: ignore
sa_column: Column | UndefinedType = Undefined,
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
schema_extra: dict[str, Any] | None = None,
Expand DownExpand Up@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
model_fields: ClassVar[dict[str, FieldInfo]]

# Replicate SQLAlchemy
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #1806 (comment)

if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
else:
super().__setattr__(name, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, name: str) -> None: # ty: ignore[invalid-method-override]
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
else:
Expand DownExpand Up@@ -609,10 +608,10 @@ def get_config(name: str) -> Any:
# This could be done by reading new_cls.model_config['table'] in FastAPI, but
# that's very specific about SQLModel, so let's have another config that
# other future tools based on Pydantic can use.
new_cls.model_config["read_from_attributes"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_from_attributes"] = True # ty: ignore[invalid-key]
# For compatibility with older versions
# TODO: remove this in the future
new_cls.model_config["read_with_orm_mode"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_with_orm_mode"] = True # ty: ignore[invalid-key]

config_registry = get_config("registry")
if config_registry is not Undefined:
Expand DownExpand Up@@ -649,7 +648,7 @@ def __init__(
# Plain forward references, for models not yet defined, are not
# handled well by SQLAlchemy without Mapped, so, wrap the
# annotations in Mapped here
cls.__annotations__[rel_name] = Mapped[ann] # type: ignore[valid-type]
cls.__annotations__[rel_name] = Mapped[ann]
relationship_to = get_relationship_to(
name=rel_name, rel_info=rel_info, annotation=ann
)
Expand DownExpand Up@@ -738,7 +737,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
raise ValueError(f"{type_} has no matching SQLAlchemy type")


def get_column_from_field(field: Any) -> Column: # type: ignore
def get_column_from_field(field: Any) -> Column:
field_info = field
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
if isinstance(sa_column, Column):
Expand DownExpand Up@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
assert isinstance(foreign_key, str)
assert isinstance(ondelete_value, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
kwargs = {
kwargs: dict[str, Any] = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
Expand All@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
return Column(sa_type, *args, **kwargs)


class_registry = weakref.WeakValueDictionary() # type: ignore

default_registry = registry()

_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
Expand DownExpand Up@@ -852,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
Expand All@@ -872,7 +869,7 @@ def __tablename__(cls) -> str:
return cls.__name__.lower()

@classmethod
def model_validate( # type: ignore[override]
def model_validate( # ty: ignore[invalid-method-override]
cls: type[_TSQLModel],
obj: Any,
*,
Expand Down
4 changes: 2 additions & 2 deletions sqlmodel/sql/_expression_select_cls.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Comment thread
svlandeg marked this conversation as resolved.

def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `HAVING` clause, joined to the existing clause via `AND`, if any.
"""
return super().having(*having) # type: ignore[arg-type]
return super().having(*having)


class Select(SelectBase[_T]):
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0251f0f
add ty to lint, precommit, pyproject and uv lock
svlandeg Mar 9, 2026
a52380b
add type annotation to kwargs dict
svlandeg Mar 9, 2026
3203f83
remove unnecessary type ignore statements
svlandeg Mar 9, 2026
5d9dd59
🎨 Auto format
pre-commit-ci-lite[bot] Mar 9, 2026
5c9b1d5
avoid using double underscore when any positional-or-keyword paramete…
svlandeg Mar 9, 2026
c1b0465
one more
svlandeg Mar 9, 2026
732847d
remove mypy
svlandeg Mar 9, 2026
2f8efa3
Merge branch 'main' into feat/ty
svlandeg Mar 10, 2026
dc8593b
use same names as upstream class
svlandeg Mar 10, 2026
1dceaa7
fix generator
svlandeg Mar 10, 2026
02cd800
Merge branch 'main' into feat/ty
svlandeg Mar 14, 2026
c49bf7f
Merge branch 'main' into feat/ty
svlandeg Mar 16, 2026
277366f
use / to denote positional-only parameters
svlandeg Mar 18, 2026
b511af0
🎨 Auto format
pre-commit-ci-lite[bot] Mar 18, 2026
7aebcc9
remove class_registry
svlandeg Mar 18, 2026
c6e97e7
revert attr naming and add ty ignore instead
svlandeg Mar 18, 2026
9103510
Merge branch 'main' into feat/ty
svlandeg Mar 18, 2026
807d9d6
ty check tests/test_select_typing.py as well
svlandeg Mar 25, 2026
51b72e1
Merge branch 'main' into feat/ty
svlandeg Mar 25, 2026
fa6df49
ensure that a ty warning creates an error
svlandeg Mar 26, 2026
0f2ea9c
🎨 Auto format
pre-commit-ci-lite[bot] Mar 26, 2026
9cd8a87
Merge branch 'main' into feat/ty
svlandeg Mar 26, 2026
fef2ff7
upgrade lower ty bound to 0.0.25
svlandeg Mar 26, 2026
a1e581a
change mypy codes to ty codes
svlandeg Mar 27, 2026
becee62
Merge branch 'main' into feat/ty
svlandeg Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,9 @@ repos:
language: unsupported
types: [python]

- id: local-mypy
name: mypy check
entry: uv run mypy sqlmodel tests/test_select_typing.py
- id: local-ty
name: ty check
entry: uv run ty check sqlmodel tests/test_select_typing.py
require_serial: true
language: unsupported
pass_filenames: false
Expand Down
15 changes: 4 additions & 11 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,9 @@ tests = [
"fastapi >=0.128.0",
"httpx >=0.28.1",
"jinja2 >=3.1.6",
"mypy >=1.19.1",
"pytest >=7.0.1",
"ruff >=0.15.6",
"ty>=0.0.25",
"typing-extensions >=4.15.0",
]

Expand DownExpand Up@@ -125,16 +125,6 @@ exclude_lines = [
[tool.coverage.html]
show_contexts = true

[tool.mypy]
strict = true
exclude = "sqlmodel.sql._expression_select_gen"

[[tool.mypy.overrides]]
module = "docs_src.*"
disallow_incomplete_defs = false
disallow_untyped_defs = false
disallow_untyped_calls = false

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
Expand All@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.ty.terminal]
error-on-warning = true
2 changes: 1 addition & 1 deletion scripts/generate_select.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ class Arg(BaseModel):
else:
t_type = f"_T{i}"
t_var = f"_TCCA[{t_type}]"
arg = Arg(name=f"__ent{i}", annotation=t_var)
arg = Arg(name=f"ent{i}", annotation=t_var)
ret_type = t_type
args.append(arg)
return_types.append(ret_type)
Expand Down
4 changes: 2 additions & 2 deletions scripts/lint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
set -e
set -x

mypy sqlmodel
mypy tests/test_select_typing.py
ty check sqlmodel
ty check tests/test_select_typing.py
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
29 changes: 13 additions & 16 deletions sqlmodel/main.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import builtins
import ipaddress
import uuid
import weakref
from collections.abc import Callable, Mapping, Sequence, Set
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
Expand DownExpand Up@@ -52,7 +51,7 @@
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import deprecated

from ._compat import ( # type: ignore[attr-defined]
from ._compat import (
PYDANTIC_MINOR_VERSION,
BaseConfig,
ModelMetaclass,
Expand DownExpand Up@@ -101,7 +100,7 @@ def __dataclass_transform__(
return lambda a: a


class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
# mypy - ignore that PydanticFieldInfo is @final
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
primary_key = kwargs.pop("primary_key", False)
Expand DownExpand Up@@ -177,7 +176,7 @@ def __init__(
cascade_delete: bool | None = False,
passive_deletes: bool | Literal["all"] | None = False,
link_model: Any | None = None,
sa_relationship: RelationshipProperty | None = None, # type: ignore
sa_relationship: RelationshipProperty | None = None,
sa_relationship_args: Sequence[Any] | None = None,
sa_relationship_kwargs: Mapping[str, Any] | None = None,
) -> None:
Expand DownExpand Up@@ -398,7 +397,7 @@ def Field(
nullable: bool | UndefinedType = Undefined,
index: bool | UndefinedType = Undefined,
sa_type: type[Any] | UndefinedType = Undefined,
sa_column: Column | UndefinedType = Undefined, # type: ignore
sa_column: Column | UndefinedType = Undefined,
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
schema_extra: dict[str, Any] | None = None,
Expand DownExpand Up@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
model_fields: ClassVar[dict[str, FieldInfo]]

# Replicate SQLAlchemy
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #1806 (comment)

if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
else:
super().__setattr__(name, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, name: str) -> None: # ty: ignore[invalid-method-override]
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
else:
Expand DownExpand Up@@ -609,10 +608,10 @@ def get_config(name: str) -> Any:
# This could be done by reading new_cls.model_config['table'] in FastAPI, but
# that's very specific about SQLModel, so let's have another config that
# other future tools based on Pydantic can use.
new_cls.model_config["read_from_attributes"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_from_attributes"] = True # ty: ignore[invalid-key]
# For compatibility with older versions
# TODO: remove this in the future
new_cls.model_config["read_with_orm_mode"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_with_orm_mode"] = True # ty: ignore[invalid-key]

config_registry = get_config("registry")
if config_registry is not Undefined:
Expand DownExpand Up@@ -649,7 +648,7 @@ def __init__(
# Plain forward references, for models not yet defined, are not
# handled well by SQLAlchemy without Mapped, so, wrap the
# annotations in Mapped here
cls.__annotations__[rel_name] = Mapped[ann] # type: ignore[valid-type]
cls.__annotations__[rel_name] = Mapped[ann]
relationship_to = get_relationship_to(
name=rel_name, rel_info=rel_info, annotation=ann
)
Expand DownExpand Up@@ -738,7 +737,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
raise ValueError(f"{type_} has no matching SQLAlchemy type")


def get_column_from_field(field: Any) -> Column: # type: ignore
def get_column_from_field(field: Any) -> Column:
field_info = field
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
if isinstance(sa_column, Column):
Expand DownExpand Up@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
assert isinstance(foreign_key, str)
assert isinstance(ondelete_value, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
kwargs = {
kwargs: dict[str, Any] = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
Expand All@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
return Column(sa_type, *args, **kwargs)


class_registry = weakref.WeakValueDictionary() # type: ignore

default_registry = registry()

_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
Expand DownExpand Up@@ -852,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
Expand All@@ -872,7 +869,7 @@ def __tablename__(cls) -> str:
return cls.__name__.lower()

@classmethod
def model_validate( # type: ignore[override]
def model_validate( # ty: ignore[invalid-method-override]
cls: type[_TSQLModel],
obj: Any,
*,
Expand Down
4 changes: 2 additions & 2 deletions sqlmodel/sql/_expression_select_cls.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Comment thread
svlandeg marked this conversation as resolved.

def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `HAVING` clause, joined to the existing clause via `AND`, if any.
"""
return super().having(*having) # type: ignore[arg-type]
return super().having(*having)


class Select(SelectBase[_T]):
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0251f0f
add ty to lint, precommit, pyproject and uv lock
svlandeg Mar 9, 2026
a52380b
add type annotation to kwargs dict
svlandeg Mar 9, 2026
3203f83
remove unnecessary type ignore statements
svlandeg Mar 9, 2026
5d9dd59
🎨 Auto format
pre-commit-ci-lite[bot] Mar 9, 2026
5c9b1d5
avoid using double underscore when any positional-or-keyword paramete…
svlandeg Mar 9, 2026
c1b0465
one more
svlandeg Mar 9, 2026
732847d
remove mypy
svlandeg Mar 9, 2026
2f8efa3
Merge branch 'main' into feat/ty
svlandeg Mar 10, 2026
dc8593b
use same names as upstream class
svlandeg Mar 10, 2026
1dceaa7
fix generator
svlandeg Mar 10, 2026
02cd800
Merge branch 'main' into feat/ty
svlandeg Mar 14, 2026
c49bf7f
Merge branch 'main' into feat/ty
svlandeg Mar 16, 2026
277366f
use / to denote positional-only parameters
svlandeg Mar 18, 2026
b511af0
🎨 Auto format
pre-commit-ci-lite[bot] Mar 18, 2026
7aebcc9
remove class_registry
svlandeg Mar 18, 2026
c6e97e7
revert attr naming and add ty ignore instead
svlandeg Mar 18, 2026
9103510
Merge branch 'main' into feat/ty
svlandeg Mar 18, 2026
807d9d6
ty check tests/test_select_typing.py as well
svlandeg Mar 25, 2026
51b72e1
Merge branch 'main' into feat/ty
svlandeg Mar 25, 2026
fa6df49
ensure that a ty warning creates an error
svlandeg Mar 26, 2026
0f2ea9c
🎨 Auto format
pre-commit-ci-lite[bot] Mar 26, 2026
9cd8a87
Merge branch 'main' into feat/ty
svlandeg Mar 26, 2026
fef2ff7
upgrade lower ty bound to 0.0.25
svlandeg Mar 26, 2026
a1e581a
change mypy codes to ty codes
svlandeg Mar 27, 2026
becee62
Merge branch 'main' into feat/ty
svlandeg Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,9 +28,9 @@ repos:
language: unsupported
types: [python]

- id: local-mypy
name: mypy check
entry: uv run mypy sqlmodel tests/test_select_typing.py
- id: local-ty
name: ty check
entry: uv run ty check sqlmodel tests/test_select_typing.py
require_serial: true
language: unsupported
pass_filenames: false
Expand Down
15 changes: 4 additions & 11 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,9 @@ tests = [
"fastapi >=0.128.0",
"httpx >=0.28.1",
"jinja2 >=3.1.6",
"mypy >=1.19.1",
"pytest >=7.0.1",
"ruff >=0.15.6",
"ty>=0.0.25",
"typing-extensions >=4.15.0",
]

Expand DownExpand Up@@ -125,16 +125,6 @@ exclude_lines = [
[tool.coverage.html]
show_contexts = true

[tool.mypy]
strict = true
exclude = "sqlmodel.sql._expression_select_gen"

[[tool.mypy.overrides]]
module = "docs_src.*"
disallow_incomplete_defs = false
disallow_untyped_defs = false
disallow_untyped_calls = false

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
Expand All@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true

[tool.ty.terminal]
error-on-warning = true
2 changes: 1 addition & 1 deletion scripts/generate_select.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ class Arg(BaseModel):
else:
t_type = f"_T{i}"
t_var = f"_TCCA[{t_type}]"
arg = Arg(name=f"__ent{i}", annotation=t_var)
arg = Arg(name=f"ent{i}", annotation=t_var)
ret_type = t_type
args.append(arg)
return_types.append(ret_type)
Expand Down
4 changes: 2 additions & 2 deletions scripts/lint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@
set -e
set -x

mypy sqlmodel
mypy tests/test_select_typing.py
ty check sqlmodel
ty check tests/test_select_typing.py
ruff check sqlmodel tests docs_src scripts
ruff format sqlmodel tests docs_src scripts --check
29 changes: 13 additions & 16 deletions sqlmodel/main.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import builtins
import ipaddress
import uuid
import weakref
from collections.abc import Callable, Mapping, Sequence, Set
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
Expand DownExpand Up@@ -52,7 +51,7 @@
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
from typing_extensions import deprecated

from ._compat import ( # type: ignore[attr-defined]
from ._compat import (
PYDANTIC_MINOR_VERSION,
BaseConfig,
ModelMetaclass,
Expand DownExpand Up@@ -101,7 +100,7 @@ def __dataclass_transform__(
return lambda a: a


class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
# mypy - ignore that PydanticFieldInfo is @final
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
primary_key = kwargs.pop("primary_key", False)
Expand DownExpand Up@@ -177,7 +176,7 @@ def __init__(
cascade_delete: bool | None = False,
passive_deletes: bool | Literal["all"] | None = False,
link_model: Any | None = None,
sa_relationship: RelationshipProperty | None = None, # type: ignore
sa_relationship: RelationshipProperty | None = None,
sa_relationship_args: Sequence[Any] | None = None,
sa_relationship_kwargs: Mapping[str, Any] | None = None,
) -> None:
Expand DownExpand Up@@ -398,7 +397,7 @@ def Field(
nullable: bool | UndefinedType = Undefined,
index: bool | UndefinedType = Undefined,
sa_type: type[Any] | UndefinedType = Undefined,
sa_column: Column | UndefinedType = Undefined, # type: ignore
sa_column: Column | UndefinedType = Undefined,
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
schema_extra: dict[str, Any] | None = None,
Expand DownExpand Up@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
model_fields: ClassVar[dict[str, FieldInfo]]

# Replicate SQLAlchemy
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context: #1806 (comment)

if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
else:
super().__setattr__(name, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, name: str) -> None: # ty: ignore[invalid-method-override]
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
else:
Expand DownExpand Up@@ -609,10 +608,10 @@ def get_config(name: str) -> Any:
# This could be done by reading new_cls.model_config['table'] in FastAPI, but
# that's very specific about SQLModel, so let's have another config that
# other future tools based on Pydantic can use.
new_cls.model_config["read_from_attributes"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_from_attributes"] = True # ty: ignore[invalid-key]
# For compatibility with older versions
# TODO: remove this in the future
new_cls.model_config["read_with_orm_mode"] = True # type: ignore[typeddict-unknown-key]
new_cls.model_config["read_with_orm_mode"] = True # ty: ignore[invalid-key]

config_registry = get_config("registry")
if config_registry is not Undefined:
Expand DownExpand Up@@ -649,7 +648,7 @@ def __init__(
# Plain forward references, for models not yet defined, are not
# handled well by SQLAlchemy without Mapped, so, wrap the
# annotations in Mapped here
cls.__annotations__[rel_name] = Mapped[ann] # type: ignore[valid-type]
cls.__annotations__[rel_name] = Mapped[ann]
relationship_to = get_relationship_to(
name=rel_name, rel_info=rel_info, annotation=ann
)
Expand DownExpand Up@@ -738,7 +737,7 @@ def get_sqlalchemy_type(field: Any) -> Any:
raise ValueError(f"{type_} has no matching SQLAlchemy type")


def get_column_from_field(field: Any) -> Column: # type: ignore
def get_column_from_field(field: Any) -> Column:
field_info = field
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
if isinstance(sa_column, Column):
Expand DownExpand Up@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
assert isinstance(foreign_key, str)
assert isinstance(ondelete_value, (str, type(None))) # for typing
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
kwargs = {
kwargs: dict[str, Any] = {
"primary_key": primary_key,
"nullable": nullable,
"index": index,
Expand All@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
return Column(sa_type, *args, **kwargs)


class_registry = weakref.WeakValueDictionary() # type: ignore

default_registry = registry()

_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
Expand DownExpand Up@@ -852,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return
else:
# Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes, only for
# non relationship values
Expand All@@ -872,7 +869,7 @@ def __tablename__(cls) -> str:
return cls.__name__.lower()

@classmethod
def model_validate( # type: ignore[override]
def model_validate( # ty: ignore[invalid-method-override]
cls: type[_TSQLModel],
obj: Any,
*,
Expand Down
4 changes: 2 additions & 2 deletions sqlmodel/sql/_expression_select_cls.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Comment thread
svlandeg marked this conversation as resolved.

def having(self, *having: _ColumnExpressionArgument[bool] | bool) -> Self:
"""Return a new `Select` construct with the given expression added to
its `HAVING` clause, joined to the existing clause via `AND`, if any.
"""
return super().having(*having) # type: ignore[arg-type]
return super().having(*having)


class Select(SelectBase[_T]):
Expand Down
Loading
Loading