Skip to content

👷 Replace mypy with ty in precommit#1806

Open
svlandeg wants to merge 12 commits intofastapi:mainfrom
svlandeg:feat/ty
Open

👷 Replace mypy with ty in precommit#1806
svlandeg wants to merge 12 commits intofastapi:mainfrom
svlandeg:feat/ty

Conversation

@svlandeg
Copy link
Member

@svlandeg svlandeg commented Mar 9, 2026

  • Replace mypy with ty in precommit, lint.sh & pyproject.toml.
  • Type fixes to make ty happy

I originally set out to have mypy and ty run together in precommit, but for sqlmodel I would argue that perhaps we want to remove mypy alltogether already now, as it allows us to remove a lot of type: ignore statements that ty thinks are unnecessary anyway.

⚠️ This requires thorough review, I'm not super sure on all decisions taken here, cf 2 separate review comments below 👇

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

📝 Docs preview

Last commit c49bf7f at: https://4d2e1793.sqlmodel.pages.dev

@svlandeg svlandeg self-assigned this Mar 9, 2026
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)
Copy link
Member Author

Choose a reason for hiding this comment

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

ty complaints with:

warning[invalid-legacy-positional-parameter]: Invalid use of the legacy convention for positional-only parameters
--> sqlmodel\sql_expression_select_gen.py:132:5
|
130 | @overload
131 | def select(
132 | entity_0: _TScalar_0,
| -------- Prior parameter here was positional-or-keyword
133 | __ent1: _TCCA[_T1],
| ^^^^^^ Parameter name begins with __ but will not be treated as positional-only
134 | ) -> Select[tuple[_TScalar_0, _T1]]: ...
|
info: A parameter can only be positional-only if it precedes all positional-or-keyword parameters
info: rule invalid-legacy-positional-parameter is enabled by default

So basically we can't have __var if there is a scalar parameter in front of this one.

As a quick fix, I changed all double underscores to singles, but it feels a bit like a hack. We could also suppress the ty warning, but that also feels wrong...

Copy link
Member

Choose a reason for hiding this comment

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

I think just renaming it to single-underscored would be incorrect.
As I understand, double-underscored parameter names was a convention for position-only parameters before , / syntax was added in 3.8.

I suggest we update this to use names without leading underscores, and update template to add , / to each signature of select:

@overload
-def select(__ent0: _TCCA[_T0]) -> SelectOfScalar[_T0]: ...
+def select(__ent0: _TCCA[_T0], /) -> SelectOfScalar[_T0]: ...


@overload
-def select(__ent0: _TScalar_0) -> SelectOfScalar[_TScalar_0]: ...
+def select(__ent0: _TScalar_0, /) -> SelectOfScalar[_TScalar_0]: ...

# Generated overloads start

{% for signature in signatures %}

@overload
def select(
-    {% for arg in signature[0] %}{{ arg.name }}: {{ arg.annotation }}, {% endfor %}
+    {% for arg in signature[0] %}{{ arg.name }}: {{ arg.annotation }}, {% endfor %} /,
    ) -> Select[tuple[{%for ret in signature[1] %}{{ ret }} {% if not loop.last %}, {% endif %}{% endfor %}]]: ...

{% endfor %}

# Generated overloads end

Alternatively we can leave __ent and rename entity_.. parameters to __entity_..

Comment on lines -528 to +538
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, key: str, value: Any) -> None:
if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
DeclarativeMeta.__setattr__(cls, key, value)
else:
super().__setattr__(name, value)
super().__setattr__(key, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, key: str) -> None:
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
DeclarativeMeta.__delattr__(cls, key)
else:
super().__delattr__(name)
super().__delattr__(key)
Copy link
Member Author

Choose a reason for hiding this comment

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

ty said the original code violates the Liskov Substitution Principle.

Copy link
Member

Choose a reason for hiding this comment

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

ty is right about Liskov Substitution Principle violation, but I don't think we need to fix it this way.
object declares these methods with name parameter. So, using key here will not be correct.
This is actually minor issue as this is unlikely that these methods will be called with parameters passed by name.
So, I would revert changes and just ignore ty warning

@svlandeg svlandeg changed the title 👷 Add ty to precommit 👷 Replace mypy with ty in precommit Mar 10, 2026
@svlandeg svlandeg marked this pull request as ready for review March 10, 2026 10:16
@svlandeg svlandeg removed their assignment Mar 10, 2026
@github-actions github-actions bot added the conflicts Automatically generated when a PR has a merge conflict label Mar 14, 2026
@github-actions

This comment was marked as resolved.

# Conflicts:
#	pyproject.toml
#	uv.lock
@github-actions github-actions bot removed the conflicts Automatically generated when a PR has a merge conflict label Mar 14, 2026
@github-actions

This comment was marked as resolved.

@github-actions github-actions bot added the conflicts Automatically generated when a PR has a merge conflict label Mar 15, 2026
@svlandeg svlandeg self-assigned this Mar 15, 2026
# Conflicts:
#	pyproject.toml
#	uv.lock
@github-actions github-actions bot removed the conflicts Automatically generated when a PR has a merge conflict label Mar 16, 2026
@svlandeg svlandeg removed their assignment Mar 16, 2026
@svlandeg svlandeg requested a review from YuriiMotov March 16, 2026 09:15
Copy link
Member

@YuriiMotov YuriiMotov left a comment

Choose a reason for hiding this comment

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

Added a few suggestions in the comments. Please, take a look)

Comment on lines -528 to +538
def __setattr__(cls, name: str, value: Any) -> None:
def __setattr__(cls, key: str, value: Any) -> None:
if is_table_model_class(cls):
DeclarativeMeta.__setattr__(cls, name, value)
DeclarativeMeta.__setattr__(cls, key, value)
else:
super().__setattr__(name, value)
super().__setattr__(key, value)

def __delattr__(cls, name: str) -> None:
def __delattr__(cls, key: str) -> None:
if is_table_model_class(cls):
DeclarativeMeta.__delattr__(cls, name)
DeclarativeMeta.__delattr__(cls, key)
else:
super().__delattr__(name)
super().__delattr__(key)
Copy link
Member

Choose a reason for hiding this comment

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

ty is right about Liskov Substitution Principle violation, but I don't think we need to fix it this way.
object declares these methods with name parameter. So, using key here will not be correct.
This is actually minor issue as this is unlikely that these methods will be called with parameters passed by name.
So, I would revert changes and just ignore ty warning

its `WHERE` clause, joined to the existing clause via `AND`, if any.
"""
return super().where(*whereclause) # type: ignore[arg-type]
return super().where(*whereclause)
Copy link
Member

Choose a reason for hiding this comment

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

It's ty's mistake that it doesn't notice type mismatch here. But I checked - passing bool argument works in runtime

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)
Copy link
Member

Choose a reason for hiding this comment

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

I think just renaming it to single-underscored would be incorrect.
As I understand, double-underscored parameter names was a convention for position-only parameters before , / syntax was added in 3.8.

I suggest we update this to use names without leading underscores, and update template to add , / to each signature of select:

@overload
-def select(__ent0: _TCCA[_T0]) -> SelectOfScalar[_T0]: ...
+def select(__ent0: _TCCA[_T0], /) -> SelectOfScalar[_T0]: ...


@overload
-def select(__ent0: _TScalar_0) -> SelectOfScalar[_TScalar_0]: ...
+def select(__ent0: _TScalar_0, /) -> SelectOfScalar[_TScalar_0]: ...

# Generated overloads start

{% for signature in signatures %}

@overload
def select(
-    {% for arg in signature[0] %}{{ arg.name }}: {{ arg.annotation }}, {% endfor %}
+    {% for arg in signature[0] %}{{ arg.name }}: {{ arg.annotation }}, {% endfor %} /,
    ) -> Select[tuple[{%for ret in signature[1] %}{{ ret }} {% if not loop.last %}, {% endif %}{% endfor %}]]: ...

{% endfor %}

# Generated overloads end

Alternatively we can leave __ent and rename entity_.. parameters to __entity_..



class_registry = weakref.WeakValueDictionary() # type: ignore
class_registry = weakref.WeakValueDictionary()
Copy link
Member

Choose a reason for hiding this comment

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

Do we even need this class_registry?
It's not used and after removing it all tests pass

@svlandeg svlandeg self-assigned this Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants