Skip to content

Commit f63626e

Browse files
committed
Merge remote-tracking branch 'upstream/main' into pin-actions-by-sha
2 parents 8000a0e + c113f5a commit f63626e

15 files changed

+498
-430
lines changed

.pre-commit-config.yaml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ repos:
2828
language: unsupported
2929
types: [python]
3030

31-
- id: local-mypy
32-
name: mypy check
33-
entry: uv run mypy sqlmodel tests/test_select_typing.py
31+
- id: local-ty
32+
name: ty check
33+
entry: uv run ty check sqlmodel tests/test_select_typing.py
3434
require_serial: true
3535
language: unsupported
3636
pass_filenames: false
@@ -41,6 +41,13 @@ repos:
4141
entry: uv run ./scripts/generate_select.py
4242
files: ^scripts/generate_select\.py|sqlmodel/sql/_expression_select_gen\.py\.jinja2$
4343

44+
- id: add-release-date
45+
language: unsupported
46+
name: add date to latest release header
47+
entry: uv run python scripts/add_latest_release_date.py
48+
files: ^docs/release-notes\.md$
49+
pass_filenames: false
50+
4451
- id: generate-readme
4552
language: unsupported
4653
name: generate README.md from index.md

docs/contributing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ That way, you can edit the documentation/source files and see the changes live.
9292

9393
/// tip
9494

95-
Alternatively, you can perform the same steps that scripts does manually.
95+
Alternatively, you can perform the same steps that the script does manually.
9696

97-
Go into the docs director at `docs/`:
97+
Go into the docs directory at `docs/`:
9898

9999
```console
100100
$ cd docs/

docs/release-notes.md

Lines changed: 50 additions & 37 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ tests = [
8282
"fastapi >=0.128.0",
8383
"httpx >=0.28.1",
8484
"jinja2 >=3.1.6",
85-
"mypy >=1.19.1",
8685
"pytest >=7.0.1",
8786
"ruff >=0.15.6",
87+
"ty>=0.0.25",
8888
"typing-extensions >=4.15.0",
8989
]
9090

@@ -125,16 +125,6 @@ exclude_lines = [
125125
[tool.coverage.html]
126126
show_contexts = true
127127

128-
[tool.mypy]
129-
strict = true
130-
exclude = "sqlmodel.sql._expression_select_gen"
131-
132-
[[tool.mypy.overrides]]
133-
module = "docs_src.*"
134-
disallow_incomplete_defs = false
135-
disallow_untyped_defs = false
136-
disallow_untyped_calls = false
137-
138128
[tool.ruff.lint]
139129
select = [
140130
"E", # pycodestyle errors
@@ -161,3 +151,6 @@ known-third-party = ["sqlmodel", "sqlalchemy", "pydantic", "fastapi"]
161151
[tool.ruff.lint.pyupgrade]
162152
# Preserve types, even if a file imports `from __future__ import annotations`.
163153
keep-runtime-typing = true
154+
155+
[tool.ty.terminal]
156+
error-on-warning = true

scripts/add_latest_release_date.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Check release-notes.md and add today's date to the latest release header if missing."""
2+
3+
import re
4+
import sys
5+
from datetime import date
6+
7+
RELEASE_NOTES_FILE = "docs/release-notes.md"
8+
RELEASE_HEADER_PATTERN = re.compile(r"^## (\d+\.\d+\.\d+)\s*(\(.*\))?\s*$")
9+
10+
11+
def main() -> None:
12+
with open(RELEASE_NOTES_FILE) as f:
13+
lines = f.readlines()
14+
15+
for i, line in enumerate(lines):
16+
match = RELEASE_HEADER_PATTERN.match(line)
17+
if not match:
18+
continue
19+
20+
version = match.group(1)
21+
date_part = match.group(2)
22+
23+
if date_part:
24+
print(f"Latest release {version} already has a date: {date_part}")
25+
sys.exit(0)
26+
27+
today = date.today().isoformat()
28+
lines[i] = f"## {version} ({today})\n"
29+
print(f"Added date: {version} ({today})")
30+
31+
with open(RELEASE_NOTES_FILE, "w") as f:
32+
f.writelines(lines)
33+
sys.exit(0)
34+
35+
print("No release header found")
36+
sys.exit(1)
37+
38+
39+
if __name__ == "__main__":
40+
main()

scripts/generate_select.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Arg(BaseModel):
3737
else:
3838
t_type = f"_T{i}"
3939
t_var = f"_TCCA[{t_type}]"
40-
arg = Arg(name=f"__ent{i}", annotation=t_var)
40+
arg = Arg(name=f"ent{i}", annotation=t_var)
4141
ret_type = t_type
4242
args.append(arg)
4343
return_types.append(ret_type)

scripts/lint.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
set -e
44
set -x
55

6-
mypy sqlmodel
7-
mypy tests/test_select_typing.py
6+
ty check sqlmodel
7+
ty check tests/test_select_typing.py
88
ruff check sqlmodel tests docs_src scripts
99
ruff format sqlmodel tests docs_src scripts --check

sqlmodel/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.0.37"
1+
__version__ = "0.0.38"
22

33
# Re-export from SQLAlchemy
44
from sqlalchemy.engine import create_engine as create_engine

sqlmodel/main.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import builtins
44
import ipaddress
55
import uuid
6-
import weakref
76
from collections.abc import Callable, Mapping, Sequence, Set
87
from dataclasses import dataclass
98
from datetime import date, datetime, time, timedelta
@@ -52,7 +51,7 @@
5251
from sqlalchemy.sql.sqltypes import LargeBinary, Time, Uuid
5352
from typing_extensions import deprecated
5453

55-
from ._compat import ( # type: ignore[attr-defined]
54+
from ._compat import (
5655
PYDANTIC_MINOR_VERSION,
5756
BaseConfig,
5857
ModelMetaclass,
@@ -101,7 +100,7 @@ def __dataclass_transform__(
101100
return lambda a: a
102101

103102

104-
class FieldInfo(PydanticFieldInfo): # type: ignore[misc]
103+
class FieldInfo(PydanticFieldInfo): # ty: ignore[subclass-of-final-class]
105104
# mypy - ignore that PydanticFieldInfo is @final
106105
def __init__(self, default: Any = Undefined, **kwargs: Any) -> None:
107106
primary_key = kwargs.pop("primary_key", False)
@@ -177,7 +176,7 @@ def __init__(
177176
cascade_delete: bool | None = False,
178177
passive_deletes: bool | Literal["all"] | None = False,
179178
link_model: Any | None = None,
180-
sa_relationship: RelationshipProperty | None = None, # type: ignore
179+
sa_relationship: RelationshipProperty | None = None,
181180
sa_relationship_args: Sequence[Any] | None = None,
182181
sa_relationship_kwargs: Mapping[str, Any] | None = None,
183182
) -> None:
@@ -398,7 +397,7 @@ def Field(
398397
nullable: bool | UndefinedType = Undefined,
399398
index: bool | UndefinedType = Undefined,
400399
sa_type: type[Any] | UndefinedType = Undefined,
401-
sa_column: Column | UndefinedType = Undefined, # type: ignore
400+
sa_column: Column | UndefinedType = Undefined,
402401
sa_column_args: Sequence[Any] | UndefinedType = Undefined,
403402
sa_column_kwargs: Mapping[str, Any] | UndefinedType = Undefined,
404403
schema_extra: dict[str, Any] | None = None,
@@ -525,13 +524,13 @@ class SQLModelMetaclass(ModelMetaclass, DeclarativeMeta):
525524
model_fields: ClassVar[dict[str, FieldInfo]]
526525

527526
# Replicate SQLAlchemy
528-
def __setattr__(cls, name: str, value: Any) -> None:
527+
def __setattr__(cls, name: str, value: Any) -> None: # ty: ignore[invalid-method-override]
529528
if is_table_model_class(cls):
530529
DeclarativeMeta.__setattr__(cls, name, value)
531530
else:
532531
super().__setattr__(name, value)
533532

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

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

740739

741-
def get_column_from_field(field: Any) -> Column: # type: ignore
740+
def get_column_from_field(field: Any) -> Column:
742741
field_info = field
743742
sa_column = _get_sqlmodel_field_value(field_info, "sa_column", Undefined)
744743
if isinstance(sa_column, Column):
@@ -773,7 +772,7 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
773772
assert isinstance(foreign_key, str)
774773
assert isinstance(ondelete_value, (str, type(None))) # for typing
775774
args.append(ForeignKey(foreign_key, ondelete=ondelete_value))
776-
kwargs = {
775+
kwargs: dict[str, Any] = {
777776
"primary_key": primary_key,
778777
"nullable": nullable,
779778
"index": index,
@@ -797,8 +796,6 @@ def get_column_from_field(field: Any) -> Column: # type: ignore
797796
return Column(sa_type, *args, **kwargs)
798797

799798

800-
class_registry = weakref.WeakValueDictionary() # type: ignore
801-
802799
default_registry = registry()
803800

804801
_TSQLModel = TypeVar("_TSQLModel", bound="SQLModel")
@@ -814,7 +811,9 @@ class SQLModel(BaseModel, metaclass=SQLModelMetaclass, registry=default_registry
814811
__allow_unmapped__ = True # https://docs.sqlalchemy.org/en/20/changelog/migration_20.html#migration-20-step-six
815812
model_config = SQLModelConfig(from_attributes=True)
816813

817-
def __new__(cls, *args: Any, **kwargs: Any) -> Any:
814+
# Typing spec says `__new__` returning `Any` overrides normal constructor
815+
# behavior, but a missing annotation does not:
816+
def __new__(cls, *args: Any, **kwargs: Any): # type: ignore[no-untyped-def]
818817
new_object = super().__new__(cls)
819818
# SQLAlchemy doesn't call __init__ on the base class when querying from DB
820819
# Ref: https://docs.sqlalchemy.org/en/14/orm/constructors.html
@@ -850,7 +849,7 @@ def __setattr__(self, name: str, value: Any) -> None:
850849
return
851850
else:
852851
# Set in SQLAlchemy, before Pydantic to trigger events and updates
853-
if is_table_model_class(self.__class__) and is_instrumented(self, name): # type: ignore[no-untyped-call]
852+
if is_table_model_class(self.__class__) and is_instrumented(self, name):
854853
set_attribute(self, name, value)
855854
# Set in Pydantic model to trigger possible validation changes, only for
856855
# non relationship values
@@ -870,7 +869,7 @@ def __tablename__(cls) -> str:
870869
return cls.__name__.lower()
871870

872871
@classmethod
873-
def model_validate( # type: ignore[override]
872+
def model_validate( # ty: ignore[invalid-method-override]
874873
cls: type[_TSQLModel],
875874
obj: Any,
876875
*,

sqlmodel/sql/_expression_select_cls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ def where(self, *whereclause: _ColumnExpressionArgument[bool] | bool) -> Self:
2020
"""Return a new `Select` construct with the given expression added to
2121
its `WHERE` clause, joined to the existing clause via `AND`, if any.
2222
"""
23-
return super().where(*whereclause) # type: ignore[arg-type]
23+
return super().where(*whereclause)
2424

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

3131

3232
class Select(SelectBase[_T]):

0 commit comments

Comments
 (0)