sqlmodel-mypy-plugin 0.18.0__tar.gz → 0.19.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/.gitignore +2 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/LICENSE +1 -1
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/PKG-INFO +4 -6
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/README.md +0 -2
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/pyproject.toml +3 -3
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/src/sqlmodel_mypy/__init__.py +1 -1
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/src/sqlmodel_mypy/plugin.py +97 -12
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/src/sqlmodel_mypy/transform.py +22 -4
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/session_exec.py +9 -2
- sqlmodel_mypy_plugin-0.19.1/tests/mypy/modules/table_dunders.py +24 -0
- sqlmodel_mypy_plugin-0.19.1/tests/mypy/outputs/mypy-plugin-strict_ini/session_exec.py +43 -0
- sqlmodel_mypy_plugin-0.19.1/tests/mypy/outputs/mypy-plugin_ini/session_exec.py +43 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/test_plugin_unit.py +25 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/test_transform_unit.py +52 -1
- sqlmodel_mypy_plugin-0.18.0/ROADMAP.md +0 -349
- sqlmodel_mypy_plugin-0.18.0/tests/mypy/modules/table_dunders.py +0 -12
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/.pre-commit-config.yaml +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/AGENTS.md +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/src/sqlmodel_mypy/persisted.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/src/sqlmodel_mypy/py.typed +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/conftest.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/configs/mypy-plugin-no-untyped.ini +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/configs/mypy-plugin-pydantic-first.ini +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/configs/mypy-plugin-pydantic-last.ini +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/configs/mypy-plugin-strict-no-explicit-any.ini +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/configs/mypy-plugin-strict-typed-execute.ini +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/configs/mypy-plugin-strict.ini +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/configs/mypy-plugin.ini +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/configs/pyproject-plugin.toml +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/aliased_joins.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/aliases.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/annotated_field_metadata.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/constructor.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/docs_parity_many_to_many_create_data.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/docs_parity_read_connected_data.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/docs_parity_relationship_attributes.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/docs_parity_update_patterns.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/execution_options.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/expression_typing_strict.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/expressions.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/field_sa_type.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/field_validation_alias.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/generics.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/getattr_column_property.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/inheritance.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/joins.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/label.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/many_to_many_relationships.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/mapped_fields.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/model_config.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/model_construct.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/nullable_defaults.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/persisted_helpers.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/relationship_comparators.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/relationship_expressions.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/relationships.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/select_varargs.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/selectinload.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/session_execute_typed.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/session_get.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/table_model_awareness.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/table_via_model_config.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/tuple_expression.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/untyped_fields.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-pydantic-first_ini/relationships.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-pydantic-last_ini/relationships.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict-typed-execute_ini/session_execute_typed.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/aliased_joins.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/aliases.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/annotated_field_metadata.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/constructor.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/docs_parity_many_to_many_create_data.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/docs_parity_read_connected_data.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/docs_parity_relationship_attributes.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/docs_parity_update_patterns.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/execution_options.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/generics.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/joins.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/label.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/many_to_many_relationships.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/mapped_fields.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/model_construct.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/nullable_defaults.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/persisted_helpers.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/relationship_comparators.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/relationships.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/select_varargs.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/table_model_awareness.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/table_via_model_config.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin-strict_ini/tuple_expression.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/aliased_joins.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/aliases.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/constructor.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/execution_options.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/expressions.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/inheritance.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/joins.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/label.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/many_to_many_relationships.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/model_construct.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/nullable_defaults.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/relationship_comparators.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/relationship_expressions.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/relationships.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/select_varargs.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/table_model_awareness.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/mypy-plugin_ini/untyped_fields.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/outputs/pyproject-plugin_toml/constructor.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/test_mypy.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/smoke_test.py +0 -0
- {sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/test_persisted_helpers.py +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlmodel-mypy-plugin
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.1
|
|
4
4
|
Summary: Mypy plugin for SQLModel (better constructor/Field typing).
|
|
5
5
|
Project-URL: Homepage, https://github.com/sertione/sqlmodel-mypy-plugin
|
|
6
6
|
Project-URL: Repository, https://github.com/sertione/sqlmodel-mypy-plugin
|
|
7
7
|
Project-URL: Issues, https://github.com/sertione/sqlmodel-mypy-plugin/issues
|
|
8
8
|
Project-URL: Changelog, https://github.com/sertione/sqlmodel-mypy-plugin/releases
|
|
9
|
-
Author:
|
|
9
|
+
Author: sertione
|
|
10
10
|
License: MIT License
|
|
11
11
|
|
|
12
|
-
Copyright (c) 2026
|
|
12
|
+
Copyright (c) 2026 sertione
|
|
13
13
|
|
|
14
14
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
15
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -30,7 +30,7 @@ License: MIT License
|
|
|
30
30
|
SOFTWARE.
|
|
31
31
|
License-File: LICENSE
|
|
32
32
|
Keywords: mypy,plugin,pydantic,sqlalchemy,sqlmodel,typing
|
|
33
|
-
Classifier: Development Status ::
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
34
|
Classifier: Intended Audience :: Developers
|
|
35
35
|
Classifier: License :: OSI Approved :: MIT License
|
|
36
36
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -52,8 +52,6 @@ Mypy plugin that improves type checking for [`SQLModel`](https://github.com/fast
|
|
|
52
52
|
|
|
53
53
|
Early/experimental. Implemented scope (supported today):
|
|
54
54
|
|
|
55
|
-
Planned work is tracked in [`ROADMAP.md`](ROADMAP.md).
|
|
56
|
-
|
|
57
55
|
- Generate correct `__init__` / `model_construct` signatures for SQLModel models:
|
|
58
56
|
- treat `sqlmodel.Field(...)` required/optional correctly
|
|
59
57
|
- accept `sqlmodel.Relationship(...)` kwargs for `table=True` models
|
|
@@ -6,8 +6,6 @@ Mypy plugin that improves type checking for [`SQLModel`](https://github.com/fast
|
|
|
6
6
|
|
|
7
7
|
Early/experimental. Implemented scope (supported today):
|
|
8
8
|
|
|
9
|
-
Planned work is tracked in [`ROADMAP.md`](ROADMAP.md).
|
|
10
|
-
|
|
11
9
|
- Generate correct `__init__` / `model_construct` signatures for SQLModel models:
|
|
12
10
|
- treat `sqlmodel.Field(...)` required/optional correctly
|
|
13
11
|
- accept `sqlmodel.Relationship(...)` kwargs for `table=True` models
|
|
@@ -4,15 +4,15 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sqlmodel-mypy-plugin"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.19.1"
|
|
8
8
|
description = "Mypy plugin for SQLModel (better constructor/Field typing)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
12
|
-
authors = [{ name = "
|
|
12
|
+
authors = [{ name = "sertione" }]
|
|
13
13
|
keywords = ["mypy", "plugin", "sqlmodel", "pydantic", "sqlalchemy", "typing"]
|
|
14
14
|
classifiers = [
|
|
15
|
-
"Development Status ::
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
17
|
"License :: OSI Approved :: MIT License",
|
|
18
18
|
"Programming Language :: Python :: 3 :: Only",
|
|
@@ -163,8 +163,15 @@ SQLMODEL_ASYNC_SESSION_EXECUTE_FULLNAME = "sqlmodel.ext.asyncio.session.AsyncSes
|
|
|
163
163
|
|
|
164
164
|
SQLMODEL_SELECT_OF_SCALAR_CLS_FULLNAME = "sqlmodel.sql._expression_select_cls.SelectOfScalar"
|
|
165
165
|
|
|
166
|
+
# `inspect` is re-exported from `sqlalchemy` and `sqlmodel`.
|
|
167
|
+
SQLALCHEMY_INSPECT_FULLNAMES = {
|
|
168
|
+
"sqlalchemy.inspect",
|
|
169
|
+
"sqlalchemy.inspection.inspect",
|
|
170
|
+
"sqlmodel.inspect",
|
|
171
|
+
}
|
|
172
|
+
|
|
166
173
|
# Increment when plugin changes should invalidate mypy cache.
|
|
167
|
-
__version__ =
|
|
174
|
+
__version__ = 26
|
|
168
175
|
|
|
169
176
|
|
|
170
177
|
class _CollectedField(NamedTuple):
|
|
@@ -554,6 +561,8 @@ class SQLModelMypyPlugin(Plugin):
|
|
|
554
561
|
return self._sqlmodel_select_return_type_callback
|
|
555
562
|
if fullname == SQLMODEL_COL_FULLNAME:
|
|
556
563
|
return self._sqlmodel_col_return_type_callback
|
|
564
|
+
if fullname in SQLALCHEMY_INSPECT_FULLNAMES:
|
|
565
|
+
return self._sqlalchemy_inspect_return_type_callback
|
|
557
566
|
if fullname in SQLMODEL_TUPLE_FULLNAMES:
|
|
558
567
|
return self._sqlmodel_tuple_return_type_callback
|
|
559
568
|
if fullname == BUILTINS_GETATTR_FULLNAME:
|
|
@@ -1751,6 +1760,48 @@ class SQLModelMypyPlugin(Plugin):
|
|
|
1751
1760
|
|
|
1752
1761
|
return self._sqlalchemy_tuple_expr_type(ctx.api)
|
|
1753
1762
|
|
|
1763
|
+
def _sqlalchemy_inspect_return_type_callback(self, ctx: FunctionContext) -> Type:
|
|
1764
|
+
"""Type `inspect(Model)` / `inspect(model)` for SQLModel table models.
|
|
1765
|
+
|
|
1766
|
+
SQLAlchemy's overloads for `inspect()` rely on the inspected object implementing typing-only
|
|
1767
|
+
protocols (`_sa_inspect_type` / `_sa_inspect_instance`). SQLModel models are valid ORM entities
|
|
1768
|
+
at runtime, but the required typing surface is not always present for strict type checkers.
|
|
1769
|
+
|
|
1770
|
+
Here we conservatively support the two common ORM introspection patterns:
|
|
1771
|
+
- `inspect(User)` -> `Mapper[User]`
|
|
1772
|
+
- `inspect(user)` -> `InstanceState[User]`
|
|
1773
|
+
"""
|
|
1774
|
+
if not ctx.arg_types or not ctx.arg_types[0]:
|
|
1775
|
+
return ctx.default_return_type
|
|
1776
|
+
|
|
1777
|
+
arg0 = get_proper_type(ctx.arg_types[0][0])
|
|
1778
|
+
|
|
1779
|
+
model_inst: Instance | None = None
|
|
1780
|
+
is_type: bool = False
|
|
1781
|
+
if isinstance(arg0, TypeType):
|
|
1782
|
+
item = get_proper_type(arg0.item)
|
|
1783
|
+
if isinstance(item, Instance):
|
|
1784
|
+
model_inst = item
|
|
1785
|
+
is_type = True
|
|
1786
|
+
elif isinstance(arg0, Instance):
|
|
1787
|
+
model_inst = arg0
|
|
1788
|
+
is_type = False
|
|
1789
|
+
|
|
1790
|
+
if model_inst is None:
|
|
1791
|
+
return ctx.default_return_type
|
|
1792
|
+
|
|
1793
|
+
info = model_inst.type
|
|
1794
|
+
if info.fullname == SQLMODEL_BASEMODEL_FULLNAME:
|
|
1795
|
+
return ctx.default_return_type
|
|
1796
|
+
if not info.has_base(SQLMODEL_BASEMODEL_FULLNAME):
|
|
1797
|
+
return ctx.default_return_type
|
|
1798
|
+
if not _is_table_model(info):
|
|
1799
|
+
return ctx.default_return_type
|
|
1800
|
+
|
|
1801
|
+
if is_type:
|
|
1802
|
+
return self._sqlalchemy_mapper_type(ctx.api, model_inst)
|
|
1803
|
+
return self._sqlalchemy_instance_state_type(ctx.api, model_inst)
|
|
1804
|
+
|
|
1754
1805
|
@staticmethod
|
|
1755
1806
|
def _lookup_var_in_mro(info: TypeInfo, name: str) -> Var | None:
|
|
1756
1807
|
"""Find a `Var` named `name` in `info` or its MRO (best-effort)."""
|
|
@@ -1992,17 +2043,25 @@ class SQLModelMypyPlugin(Plugin):
|
|
|
1992
2043
|
if not _is_table_model(info):
|
|
1993
2044
|
return
|
|
1994
2045
|
|
|
1995
|
-
if "__table__" in info.names:
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2046
|
+
if "__table__" not in info.names:
|
|
2047
|
+
table_t = self._sqlalchemy_table_type(ctx.api)
|
|
2048
|
+
v = Var("__table__", table_t)
|
|
2049
|
+
v.info = info
|
|
2050
|
+
v._fullname = f"{info.fullname}.__table__"
|
|
2051
|
+
v.is_classvar = True
|
|
2052
|
+
sym = SymbolTableNode(MDEF, v)
|
|
2053
|
+
sym.plugin_generated = True
|
|
2054
|
+
info.names["__table__"] = sym
|
|
2055
|
+
|
|
2056
|
+
if "__mapper__" not in info.names:
|
|
2057
|
+
mapper_t = self._sqlalchemy_mapper_type(ctx.api, fill_typevars(info))
|
|
2058
|
+
v = Var("__mapper__", mapper_t)
|
|
2059
|
+
v.info = info
|
|
2060
|
+
v._fullname = f"{info.fullname}.__mapper__"
|
|
2061
|
+
v.is_classvar = True
|
|
2062
|
+
sym = SymbolTableNode(MDEF, v)
|
|
2063
|
+
sym.plugin_generated = True
|
|
2064
|
+
info.names["__mapper__"] = sym
|
|
2006
2065
|
|
|
2007
2066
|
def _sqlalchemy_table_type(self, api: Any | None = None) -> Type:
|
|
2008
2067
|
"""Return the best available `sqlalchemy` Table-ish type.
|
|
@@ -2024,6 +2083,32 @@ class SQLModelMypyPlugin(Plugin):
|
|
|
2024
2083
|
return Instance(info, [])
|
|
2025
2084
|
return _plugin_any()
|
|
2026
2085
|
|
|
2086
|
+
def _sqlalchemy_mapper_type(self, api: Any | None, model_type: Type) -> Type:
|
|
2087
|
+
"""Return `sqlalchemy.orm.Mapper[model_type]` (best-effort)."""
|
|
2088
|
+
for fullname in ("sqlalchemy.orm.Mapper", "sqlalchemy.orm.mapper.Mapper"):
|
|
2089
|
+
if api is not None:
|
|
2090
|
+
inst = _named_generic_type_or_none(api, fullname, [model_type])
|
|
2091
|
+
if inst is not None:
|
|
2092
|
+
return inst
|
|
2093
|
+
info = _lookup_typeinfo(self, fullname)
|
|
2094
|
+
if info is not None:
|
|
2095
|
+
args = [model_type] * len(info.defn.type_vars)
|
|
2096
|
+
return Instance(info, args)
|
|
2097
|
+
return _plugin_any()
|
|
2098
|
+
|
|
2099
|
+
def _sqlalchemy_instance_state_type(self, api: Any | None, model_type: Type) -> Type:
|
|
2100
|
+
"""Return `sqlalchemy.orm.InstanceState[model_type]` (best-effort)."""
|
|
2101
|
+
for fullname in ("sqlalchemy.orm.InstanceState", "sqlalchemy.orm.state.InstanceState"):
|
|
2102
|
+
if api is not None:
|
|
2103
|
+
inst = _named_generic_type_or_none(api, fullname, [model_type])
|
|
2104
|
+
if inst is not None:
|
|
2105
|
+
return inst
|
|
2106
|
+
info = _lookup_typeinfo(self, fullname)
|
|
2107
|
+
if info is not None:
|
|
2108
|
+
args = [model_type] * len(info.defn.type_vars)
|
|
2109
|
+
return Instance(info, args)
|
|
2110
|
+
return _plugin_any()
|
|
2111
|
+
|
|
2027
2112
|
def _widen_model_config_type(self, ctx: ClassDefContext) -> None:
|
|
2028
2113
|
mapping_str_any = ctx.api.named_type(
|
|
2029
2114
|
"typing.Mapping",
|
|
@@ -1198,11 +1198,25 @@ def add_method(
|
|
|
1198
1198
|
"""
|
|
1199
1199
|
info = cls.info
|
|
1200
1200
|
|
|
1201
|
-
|
|
1201
|
+
def _remove_stmt(stmt: Statement) -> None:
|
|
1202
|
+
# `list.remove(...)` raises ValueError if not present; ignore.
|
|
1203
|
+
try:
|
|
1204
|
+
cls.defs.body.remove(stmt)
|
|
1205
|
+
except ValueError:
|
|
1206
|
+
return
|
|
1207
|
+
|
|
1208
|
+
# Remove previously generated methods (handle both FuncDef and Decorator nodes).
|
|
1202
1209
|
if name in info.names:
|
|
1203
1210
|
sym = info.names[name]
|
|
1204
|
-
if sym.plugin_generated
|
|
1205
|
-
|
|
1211
|
+
if sym.plugin_generated:
|
|
1212
|
+
node = sym.node
|
|
1213
|
+
if isinstance(node, Decorator):
|
|
1214
|
+
# NOTE: older buggy versions appended the underlying FuncDef instead of the
|
|
1215
|
+
# Decorator; remove both to be safe.
|
|
1216
|
+
_remove_stmt(node)
|
|
1217
|
+
_remove_stmt(node.func)
|
|
1218
|
+
elif isinstance(node, FuncDef):
|
|
1219
|
+
_remove_stmt(node)
|
|
1206
1220
|
|
|
1207
1221
|
# Keep existing definition for semantic analysis by renaming it.
|
|
1208
1222
|
if name in info.names:
|
|
@@ -1248,9 +1262,13 @@ def add_method(
|
|
|
1248
1262
|
dec = Decorator(func, [NameExpr("classmethod")], v)
|
|
1249
1263
|
dec.line = info.line
|
|
1250
1264
|
sym = SymbolTableNode(MDEF, dec)
|
|
1265
|
+
# NOTE: keep the class body node as a plain FuncDef (this matches the
|
|
1266
|
+
# previous plugin behavior and avoids override-check churn).
|
|
1267
|
+
body_node: Statement = func
|
|
1251
1268
|
else:
|
|
1252
1269
|
sym = SymbolTableNode(MDEF, func)
|
|
1270
|
+
body_node = func
|
|
1253
1271
|
|
|
1254
1272
|
sym.plugin_generated = True
|
|
1255
1273
|
info.names[name] = sym
|
|
1256
|
-
info.defn.defs.body.append(
|
|
1274
|
+
info.defn.defs.body.append(body_node)
|
{sqlmodel_mypy_plugin-0.18.0 → sqlmodel_mypy_plugin-0.19.1}/tests/mypy/modules/session_exec.py
RENAMED
|
@@ -2,9 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from sqlalchemy import text
|
|
5
|
+
from sqlalchemy import select, text
|
|
6
6
|
from sqlalchemy.engine.cursor import CursorResult
|
|
7
|
-
from sqlalchemy.engine.result import Result
|
|
7
|
+
from sqlalchemy.engine.result import Result, TupleResult
|
|
8
8
|
from sqlmodel import Field, Session, SQLModel, create_engine, delete
|
|
9
9
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
10
10
|
|
|
@@ -21,6 +21,10 @@ with Session(engine) as session:
|
|
|
21
21
|
result = session.exec(text("SELECT 1"))
|
|
22
22
|
ok_result: Result[Any] = result
|
|
23
23
|
|
|
24
|
+
# accept SQLAlchemy `select(...)` statements (commonly used with SQLModel)
|
|
25
|
+
select_result = session.exec(select(Hero))
|
|
26
|
+
_ok_select_result: TupleResult[tuple[Hero]] = select_result
|
|
27
|
+
|
|
24
28
|
# Keep update/delete statements working (select correct overload).
|
|
25
29
|
delete_result = session.exec(delete(Hero))
|
|
26
30
|
ok_delete_result: CursorResult[Any] = delete_result
|
|
@@ -30,5 +34,8 @@ async def async_exec(session: AsyncSession) -> None:
|
|
|
30
34
|
result2 = await session.exec(text("SELECT 1"))
|
|
31
35
|
_ok_result2: Result[Any] = result2
|
|
32
36
|
|
|
37
|
+
select_result2 = await session.exec(select(Hero))
|
|
38
|
+
_ok_select_result2: TupleResult[tuple[Hero]] = select_result2
|
|
39
|
+
|
|
33
40
|
delete_result2 = await session.exec(delete(Hero))
|
|
34
41
|
_ok_delete_result2: CursorResult[Any] = delete_result2
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import inspect as sa_inspect
|
|
4
|
+
from sqlalchemy.orm import InstanceState, Mapper
|
|
5
|
+
from sqlalchemy.sql.schema import Table
|
|
6
|
+
from sqlmodel import Field, SQLModel
|
|
7
|
+
from sqlmodel import inspect as sm_inspect
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class User(SQLModel, table=True):
|
|
11
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
12
|
+
name: str = Field()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ok_table: Table = User.__table__
|
|
16
|
+
ok_mapper: Mapper[User] = User.__mapper__
|
|
17
|
+
|
|
18
|
+
# `inspect(...)` should be typed for SQLModel table models (SQLAlchemy + SQLModel re-export).
|
|
19
|
+
ok_mapper2: Mapper[User] = sa_inspect(User)
|
|
20
|
+
ok_mapper3: Mapper[User] = sm_inspect(User)
|
|
21
|
+
|
|
22
|
+
user = User(name="x")
|
|
23
|
+
ok_state: InstanceState[User] = sa_inspect(user)
|
|
24
|
+
ok_state2: InstanceState[User] = sm_inspect(user)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import select, text
|
|
6
|
+
from sqlalchemy.engine.cursor import CursorResult
|
|
7
|
+
from sqlalchemy.engine.result import Result, TupleResult
|
|
8
|
+
from sqlmodel import Field, Session, SQLModel, create_engine, delete
|
|
9
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Hero(SQLModel, table=True):
|
|
13
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
14
|
+
name: str = Field()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
engine = create_engine("sqlite://")
|
|
18
|
+
|
|
19
|
+
with Session(engine) as session:
|
|
20
|
+
# accept SQLAlchemy (non-SQLModel) Executable statements, e.g. text(...)
|
|
21
|
+
result = session.exec(text("SELECT 1"))
|
|
22
|
+
ok_result: Result[Any] = result
|
|
23
|
+
|
|
24
|
+
# accept SQLAlchemy `select(...)` statements (commonly used with SQLModel)
|
|
25
|
+
select_result = session.exec(select(Hero))
|
|
26
|
+
_ok_select_result: TupleResult[tuple[Hero]] = select_result
|
|
27
|
+
# MYPY: error: Incompatible types in assignment (expression has type "CursorResult[Any]", variable has type "TupleResult[tuple[Hero]]") [assignment]
|
|
28
|
+
|
|
29
|
+
# Keep update/delete statements working (select correct overload).
|
|
30
|
+
delete_result = session.exec(delete(Hero))
|
|
31
|
+
ok_delete_result: CursorResult[Any] = delete_result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def async_exec(session: AsyncSession) -> None:
|
|
35
|
+
result2 = await session.exec(text("SELECT 1"))
|
|
36
|
+
_ok_result2: Result[Any] = result2
|
|
37
|
+
|
|
38
|
+
select_result2 = await session.exec(select(Hero))
|
|
39
|
+
_ok_select_result2: TupleResult[tuple[Hero]] = select_result2
|
|
40
|
+
# MYPY: error: Incompatible types in assignment (expression has type "CursorResult[Any]", variable has type "TupleResult[tuple[Hero]]") [assignment]
|
|
41
|
+
|
|
42
|
+
delete_result2 = await session.exec(delete(Hero))
|
|
43
|
+
_ok_delete_result2: CursorResult[Any] = delete_result2
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import select, text
|
|
6
|
+
from sqlalchemy.engine.cursor import CursorResult
|
|
7
|
+
from sqlalchemy.engine.result import Result, TupleResult
|
|
8
|
+
from sqlmodel import Field, Session, SQLModel, create_engine, delete
|
|
9
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Hero(SQLModel, table=True):
|
|
13
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
14
|
+
name: str = Field()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
engine = create_engine("sqlite://")
|
|
18
|
+
|
|
19
|
+
with Session(engine) as session:
|
|
20
|
+
# accept SQLAlchemy (non-SQLModel) Executable statements, e.g. text(...)
|
|
21
|
+
result = session.exec(text("SELECT 1"))
|
|
22
|
+
ok_result: Result[Any] = result
|
|
23
|
+
|
|
24
|
+
# accept SQLAlchemy `select(...)` statements (commonly used with SQLModel)
|
|
25
|
+
select_result = session.exec(select(Hero))
|
|
26
|
+
_ok_select_result: TupleResult[tuple[Hero]] = select_result
|
|
27
|
+
# MYPY: error: Incompatible types in assignment (expression has type "CursorResult[Any]", variable has type "TupleResult[tuple[Hero]]") [assignment]
|
|
28
|
+
|
|
29
|
+
# Keep update/delete statements working (select correct overload).
|
|
30
|
+
delete_result = session.exec(delete(Hero))
|
|
31
|
+
ok_delete_result: CursorResult[Any] = delete_result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def async_exec(session: AsyncSession) -> None:
|
|
35
|
+
result2 = await session.exec(text("SELECT 1"))
|
|
36
|
+
_ok_result2: Result[Any] = result2
|
|
37
|
+
|
|
38
|
+
select_result2 = await session.exec(select(Hero))
|
|
39
|
+
_ok_select_result2: TupleResult[tuple[Hero]] = select_result2
|
|
40
|
+
# MYPY: error: Incompatible types in assignment (expression has type "CursorResult[Any]", variable has type "TupleResult[tuple[Hero]]") [assignment]
|
|
41
|
+
|
|
42
|
+
delete_result2 = await session.exec(delete(Hero))
|
|
43
|
+
_ok_delete_result2: CursorResult[Any] = delete_result2
|
|
@@ -2941,3 +2941,28 @@ def test_select_signature_hook_is_idempotent_for_star_overload() -> None:
|
|
|
2941
2941
|
hook(FunctionSigContext(args=[], default_signature=default_sig, context=call, api=api))
|
|
2942
2942
|
is default_sig
|
|
2943
2943
|
)
|
|
2944
|
+
|
|
2945
|
+
|
|
2946
|
+
def test_sqlalchemy_typing_helpers_fallback_to_any(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
2947
|
+
"""Cover SQLAlchemy typing helper fallbacks (best-effort robustness)."""
|
|
2948
|
+
p = plugin_mod.SQLModelMypyPlugin(Options())
|
|
2949
|
+
any_t = AnyType(TypeOfAny.explicit)
|
|
2950
|
+
|
|
2951
|
+
def _no_lookup(_plugin: object, _fullname: str) -> None:
|
|
2952
|
+
return None
|
|
2953
|
+
|
|
2954
|
+
monkeypatch.setattr(plugin_mod, "_lookup_typeinfo", _no_lookup)
|
|
2955
|
+
|
|
2956
|
+
assert isinstance(get_proper_type(p._sqlalchemy_table_type(None)), AnyType)
|
|
2957
|
+
assert isinstance(get_proper_type(p._sqlalchemy_mapper_type(None, any_t)), AnyType)
|
|
2958
|
+
assert isinstance(get_proper_type(p._sqlalchemy_instance_state_type(None, any_t)), AnyType)
|
|
2959
|
+
|
|
2960
|
+
|
|
2961
|
+
def test_sqlalchemy_instance_state_type_uses_named_generic_type() -> None:
|
|
2962
|
+
"""Cover the fast-path branch for InstanceState typing."""
|
|
2963
|
+
p = plugin_mod.SQLModelMypyPlugin(Options())
|
|
2964
|
+
api = DummyCheckerAPI()
|
|
2965
|
+
any_t = AnyType(TypeOfAny.explicit)
|
|
2966
|
+
|
|
2967
|
+
out = p._sqlalchemy_instance_state_type(api, any_t)
|
|
2968
|
+
assert isinstance(get_proper_type(out), Instance)
|
|
@@ -657,7 +657,58 @@ def test_add_method_generates_and_replaces_methods() -> None:
|
|
|
657
657
|
api, cls, "cm", args=[arg], return_type=AnyType(TypeOfAny.explicit), is_classmethod=True
|
|
658
658
|
)
|
|
659
659
|
assert "cm" in cls.info.names
|
|
660
|
-
|
|
660
|
+
cm_node = cls.info.names["cm"].node
|
|
661
|
+
assert isinstance(cm_node, Decorator)
|
|
662
|
+
assert cm_node.func.is_class is True
|
|
663
|
+
assert cm_node not in cls.defs.body
|
|
664
|
+
assert cm_node.func in cls.defs.body
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def test_add_method_classmethod_reentry_is_idempotent_and_handles_legacy_body() -> None:
|
|
668
|
+
api = DummyAPI()
|
|
669
|
+
cls = make_sqlmodel_class()
|
|
670
|
+
|
|
671
|
+
any_t = AnyType(TypeOfAny.explicit)
|
|
672
|
+
arg = Argument(Var("x", any_t), any_t, None, ARG_NAMED)
|
|
673
|
+
|
|
674
|
+
# First generation
|
|
675
|
+
add_method(
|
|
676
|
+
api, cls, "cm", args=[arg], return_type=AnyType(TypeOfAny.explicit), is_classmethod=True
|
|
677
|
+
)
|
|
678
|
+
node1 = cls.info.names["cm"].node
|
|
679
|
+
assert isinstance(node1, Decorator)
|
|
680
|
+
assert node1 not in cls.defs.body
|
|
681
|
+
assert node1.func in cls.defs.body
|
|
682
|
+
assert node1.func.is_class is True
|
|
683
|
+
|
|
684
|
+
# Re-entry: previously generated Decorator should be removed, no duplicates.
|
|
685
|
+
add_method(
|
|
686
|
+
api, cls, "cm", args=[arg], return_type=AnyType(TypeOfAny.explicit), is_classmethod=True
|
|
687
|
+
)
|
|
688
|
+
node2 = cls.info.names["cm"].node
|
|
689
|
+
assert isinstance(node2, Decorator)
|
|
690
|
+
assert node2 not in cls.defs.body
|
|
691
|
+
assert node2.func in cls.defs.body
|
|
692
|
+
assert node1.func not in cls.defs.body
|
|
693
|
+
assert node2.func.is_class is True
|
|
694
|
+
assert sum(1 for s in cls.defs.body if isinstance(s, FuncDef) and s.name == "cm") == 1
|
|
695
|
+
|
|
696
|
+
# Simulate legacy buggy state: body contains both Decorator and FuncDef.
|
|
697
|
+
cls.defs.body.append(node2)
|
|
698
|
+
assert node2 in cls.defs.body
|
|
699
|
+
assert node2.func in cls.defs.body
|
|
700
|
+
|
|
701
|
+
add_method(
|
|
702
|
+
api, cls, "cm", args=[arg], return_type=AnyType(TypeOfAny.explicit), is_classmethod=True
|
|
703
|
+
)
|
|
704
|
+
node3 = cls.info.names["cm"].node
|
|
705
|
+
assert isinstance(node3, Decorator)
|
|
706
|
+
assert node3 not in cls.defs.body
|
|
707
|
+
assert node3.func in cls.defs.body
|
|
708
|
+
assert node2 not in cls.defs.body
|
|
709
|
+
assert node2.func not in cls.defs.body
|
|
710
|
+
assert node3.func.is_class is True
|
|
711
|
+
assert sum(1 for s in cls.defs.body if isinstance(s, FuncDef) and s.name == "cm") == 1
|
|
661
712
|
|
|
662
713
|
|
|
663
714
|
def test_transform_defer_paths() -> None:
|