summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2023-02-27 06:36:41 +0000
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>2023-02-27 06:36:41 +0000
commit8d16aac95d99c708ff4eecc9f8676776c27cfd58 (patch)
tree80bba068953ec78eb082e912b508a47014bccd51
parent1737585241ef8e3f5ed0bdb39b71a8526b41d8b3 (diff)
parent18db042ea74a0ff4e6f4bdecb66c9321a999c1a8 (diff)
downloadsqlalchemy-8d16aac95d99c708ff4eecc9f8676776c27cfd58.tar.gz
Merge "Create public QueryPropertyDescriptor type for query_property" into main
-rw-r--r--doc/build/changelog/unreleased_20/9338.rst7
-rw-r--r--doc/build/orm/contextual.rst4
-rw-r--r--lib/sqlalchemy/orm/__init__.py1
-rw-r--r--lib/sqlalchemy/orm/scoping.py27
-rw-r--r--test/ext/mypy/plain_files/sessionmakers.py32
-rw-r--r--test/ext/mypy/plugin_files/issue_9156.py3
6 files changed, 65 insertions, 9 deletions
diff --git a/doc/build/changelog/unreleased_20/9338.rst b/doc/build/changelog/unreleased_20/9338.rst
new file mode 100644
index 000000000..426fa85ac
--- /dev/null
+++ b/doc/build/changelog/unreleased_20/9338.rst
@@ -0,0 +1,7 @@
+.. change::
+ :tags: typing, usecase
+ :tickets: 9338
+
+ Exported the type returned by
+ :meth:`_orm.scoped_session.query_property` using a new public type
+ :class:`.orm.QueryPropertyDescriptor`.
diff --git a/doc/build/orm/contextual.rst b/doc/build/orm/contextual.rst
index 1fc64c965..3e03e9316 100644
--- a/doc/build/orm/contextual.rst
+++ b/doc/build/orm/contextual.rst
@@ -271,7 +271,7 @@ otherwise self-managed.
Contextual Session API
----------------------
-.. autoclass:: sqlalchemy.orm.scoping.scoped_session
+.. autoclass:: sqlalchemy.orm.scoped_session
:members:
:inherited-members:
@@ -279,3 +279,5 @@ Contextual Session API
:members:
.. autoclass:: sqlalchemy.util.ThreadLocalRegistry
+
+.. autoclass:: sqlalchemy.orm.QueryPropertyDescriptor
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index d54e1ccb9..69cd7f598 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -120,6 +120,7 @@ from .relationships import foreign as foreign
from .relationships import Relationship as Relationship
from .relationships import RelationshipProperty as RelationshipProperty
from .relationships import remote as remote
+from .scoping import QueryPropertyDescriptor as QueryPropertyDescriptor
from .scoping import scoped_session as scoped_session
from .session import close_all_sessions as close_all_sessions
from .session import make_transient as make_transient
diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py
index b46d26d0b..f5f894583 100644
--- a/lib/sqlalchemy/orm/scoping.py
+++ b/lib/sqlalchemy/orm/scoping.py
@@ -76,7 +76,14 @@ if TYPE_CHECKING:
_T = TypeVar("_T", bound=Any)
-class _QueryDescriptorType(Protocol):
+class QueryPropertyDescriptor(Protocol):
+ """Describes the type applied to a class-level
+ :meth:`_orm.scoped_session.query_property` attribute.
+
+ .. versionadded:: 2.0.5
+
+ """
+
def __get__(self, instance: Any, owner: Type[_T]) -> Query[_T]:
...
@@ -254,17 +261,25 @@ class scoped_session(Generic[_S]):
def query_property(
self, query_cls: Optional[Type[Query[_T]]] = None
- ) -> _QueryDescriptorType:
- """return a class property which produces a :class:`_query.Query`
- object
- against the class and the current :class:`.Session` when called.
+ ) -> QueryPropertyDescriptor:
+ """return a class property which produces a legacy
+ :class:`_query.Query` object against the class and the current
+ :class:`.Session` when called.
+
+ .. legacy:: The :meth:`_orm.scoped_session.query_property` accessor
+ is specific to the legacy :class:`.Query` object and is not
+ considered to be part of :term:`2.0-style` ORM use.
e.g.::
+ from sqlalchemy.orm import QueryPropertyDescriptor
+ from sqlalchemy.orm import scoped_session
+ from sqlalchemy.orm import sessionmaker
+
Session = scoped_session(sessionmaker())
class MyClass:
- query = Session.query_property()
+ query: QueryPropertyDescriptor = Session.query_property()
# after mappers are defined
result = MyClass.query.filter(MyClass.name=='foo').all()
diff --git a/test/ext/mypy/plain_files/sessionmakers.py b/test/ext/mypy/plain_files/sessionmakers.py
index 2d02f2a3f..2897606cf 100644
--- a/test/ext/mypy/plain_files/sessionmakers.py
+++ b/test/ext/mypy/plain_files/sessionmakers.py
@@ -1,4 +1,6 @@
-"""test #7656"""
+"""test sessionmaker, originally for #7656"""
+
+from typing import reveal_type
from sqlalchemy import create_engine
from sqlalchemy import Engine
@@ -7,6 +9,7 @@ from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.ext.asyncio import AsyncEngine
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine
+from sqlalchemy.orm import QueryPropertyDescriptor
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import Session
from sqlalchemy.orm import sessionmaker
@@ -110,3 +113,30 @@ def test_8837_async() -> None:
# EXPECTED_TYPE: AsyncSession
reveal_type(async_session)
+
+
+# test #9338
+ss_9338 = scoped_session_factory(engine)
+
+# EXPECTED_TYPE: QueryPropertyDescriptor
+reveal_type(ss_9338.query_property())
+qp: QueryPropertyDescriptor = ss_9338.query_property()
+
+
+class Foo:
+ query = qp
+
+
+# EXPECTED_TYPE: Query[Foo]
+reveal_type(Foo.query)
+
+# EXPECTED_TYPE: list[Foo]
+reveal_type(Foo.query.all())
+
+
+class Bar:
+ query: QueryPropertyDescriptor = ss_9338.query_property()
+
+
+# EXPECTED_TYPE: Query[Bar]
+reveal_type(Bar.query)
diff --git a/test/ext/mypy/plugin_files/issue_9156.py b/test/ext/mypy/plugin_files/issue_9156.py
index 46e5e9570..e67f64442 100644
--- a/test/ext/mypy/plugin_files/issue_9156.py
+++ b/test/ext/mypy/plugin_files/issue_9156.py
@@ -1,4 +1,5 @@
-from typing import Any, Type
+from typing import Any
+from typing import Type
from sqlalchemy.sql.elements import ColumnElement
from sqlalchemy.sql.type_api import TypeEngine