Sqlalchemyクエリが返すオブジェクトの種類がわかりません。
entries = session.query(Foo.id, Foo.date).all()
エントリ内の各オブジェクトのタイプはsqlalchemy.util._collections.result
のようですが、pythonインタプリタのクイックfrom sqlalchemy.util._collections import result
はImportErrorを発生させます。
私が最終的にやろうとしているのは、この関数のヒントを入力することです:
def my_super_function(session: Session) -> ???:
entries = session.query(Foo.id, Foo.date).all()
return entries
???
の代わりに何を入れればよいですか? mypy(この場合)はList[Tuple[int, str]]
で問題ないようです。確かに、エントリにタプルであるかのようにアクセスできますが、たとえばentry.date
でもアクセスできます。
クラスもインポートできないことに不思議に思いました。答えはかなり長く、私がそれをどのように解決したかを説明してきたので、我慢してください。
Query.all()
Query
オブジェクト自体でlist()
を呼び出します:
_def all(self):
"""Return the results represented by this ``Query`` as a list.
This results in an execution of the underlying query.
"""
return list(self)
_
...リストはオブジェクトを反復処理するため、 Query.__iter__()
:
_def __iter__(self):
context = self._compile_context()
context.statement.use_labels = True
if self._autoflush and not self._populate_existing:
self.session._autoflush()
return self._execute_and_instances(context)
_
... Query._execute_and_instances()
メソッドの結果を返します:
_def _execute_and_instances(self, querycontext):
conn = self._get_bind_args(
querycontext, self._connection_from_session, close_with_result=True
)
result = conn.execute(querycontext.statement, self._params)
return loading.instances(querycontext.query, result, querycontext)
_
これはクエリを実行し、 sqlalchemy.loading.instances()
関数の結果を返します。その関数には this line があり、単一エンティティ以外のクエリに適用されます。
_keyed_Tuple = util.lightweight_named_Tuple("result", labels)
_
...そして、その行の後にprint(keyed_Tuple)
を挿入すると、上記で述べた型である_<class 'sqlalchemy.util._collections.result'>
_が出力されます。つまり、そのオブジェクトが何であれ、それは sqlalchemy.util._collections.lightweight_named_Tuple()
関数からのものです。
_def lightweight_named_Tuple(name, fields):
hash_ = (name,) + Tuple(fields)
tp_cls = _lw_tuples.get(hash_)
if tp_cls:
return tp_cls
tp_cls = type(
name,
(_LW,),
dict(
[
(field, _property_getters[idx])
for idx, field in enumerate(fields)
if field is not None
]
+ [("__slots__", ())]
),
)
tp_cls._real_fields = fields
tp_cls._fields = Tuple([f for f in fields if f is not None])
_lw_tuples[hash_] = tp_cls
return tp_cls
_
したがって、重要な部分は このステートメント です。
_tp_cls = type(
name,
(_LW,),
dict(
[
(field, _property_getters[idx])
for idx, field in enumerate(fields)
if field is not None
]
+ [("__slots__", ())]
),
)
_
...組み込みのtype()
クラスを呼び出します。
3つの引数で、新しい型オブジェクトを返します。これは本質的にクラスステートメントの動的形式です。
クラス_sqlalchemy.util._collections.result
_をインポートできないのはこのためです。クラスはクエリ時にのみ作成されるためです。この理由は、列名(つまり、名前付きタプル属性)は、クエリが実行されるまでわからないためです。
From python docstype
の署名は次のとおりです:type(name, bases, dict)
ここで:
名前文字列はクラス名であり、_
__name__
_属性になります。 basesタプルは基本クラスを項目化し、___bases__
_属性になります。 dict辞書は、クラス本体の定義を含む名前空間であり、___dict__
_属性になるように標準辞書にコピーされます。
ご覧のとおり、type()
のlightweight_named_Tuple()
に渡されるbases
引数は_(_LW,)
_です。したがって、動的に作成された名前付きタプル型は、インポート可能なクラスである _sqlalchemy.util._collections._LW
_ から継承されます。
_from sqlalchemy.util._collections import _LW
entries = session.query(Foo.id, Foo.date).all()
for entry in entries:
assert isinstance(entry, _LW) # True
_
...したがって、関数をアンダースコアで始まる内部クラスに入力するのが適切な形式かどうかはわかりませんが、__LW
_は、Tuple
から継承される_sqlalchemy.util._collections.AbstractKeyedTuple
_から継承します。タプルのリストはなので、_List[Tuple[int, str]]
_の現在の型指定が機能するのはこのためです。だからあなたの選択を取り、__LW
_、AbstractKeyedTuple
、Tuple
はすべて、関数が返すものの正しい表現になります。