tortoise / tortoise/tortoise-orm
turtoise pydantic model .from_queryset() NoneType object is not callable
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 5.6k
- Forks
- 516
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 9
Description
Describe the bug
When following the example for integrating tortoise-orm with fastapi, I see an error when trying to
use pydantic_model.from_queryset: TypeError: 'NoneType' is not callable.
To Reproduce
models.py
from tortoise import fields, models
from tortoise.contrib.pydantic import pydantic_model_creator
class Atom(models.Model):
CUI = fields.CharField(max_length=8, description="Unique identifier for concept")
LAT = fields.CharField(max_length=3, description="Language of term")
TS = fields.CharField(max_length=1, description="Term status")
LUI = fields.CharField(max_length=10, description="Unique identifier for term")
STT = fields.CharField(max_length=3, description="String type")
SUI = fields.CharField(max_length=10, description="Unique identifier for string")
ISPREF = fields.CharField(max_length=1, description="Atom status - preferred (Y) or not (N) for this string within this concept")
AUI = fields.CharField(max_length=9, pk=True, description="Unique identifier for atom - variable length field, 8 or 9 characters")
SAUI = fields.CharField(max_length=50, null=True, description="Source asserted atom identifier [optional]")
SCUI = fields.CharField(max_length=100, null=True, description="Source asserted descriptor identifier [optional]")
SDUI = fields.CharField(max_length=100, null=True, description="Source asserted descriptor identifier [optional]")
SAB = fields.CharField(max_length=40, description= """
Abbreviated source name (SAB). Maximum field length is 20 alphanumeric characters. Two source abbreviations are assigned:
Root Source Abbreviation (RSAB) — short form, no version information, for example, AI/RHEUM, 1993, has an RSAB of "AIR"
Versioned Source Abbreviation (VSAB) — includes version information, for example, AI/RHEUM, 1993, has an VSAB of "AIR93"
Official source names, RSABs, and VSABs are included on the UMLS Source Vocabulary Documentation page.
""")
TTY = fields.CharField(max_length=40, description="Abbreviation for term type in source vocabulary, for example PN (Metathesaurus Preferred Name) or CD (Clinical Drug). Possible values are listed on the Abbreviations Used in Data Elements page.")
CODE = fields.CharField(max_length=100, description="Most useful source asserted identifier (if the source vocabulary has more than one identifier), or a Metathesaurus-generated source entry identifier (if the source vocabulary has none)")
STR = fields.TextField(description="String")
SRL = fields.IntField(description="Source restriction level")
SUPPRESS = fields.CharField(max_length=1, description="""
Suppressible flag. Values = O, E, Y, or N
O: All obsolete content, whether they are obsolesced by the source or by NLM. These will include all atoms having obsolete TTYs, and other atoms becoming obsolete that have not acquired an obsolete TTY (e.g. RxNorm SCDs no longer associated with current drugs, LNC atoms derived from obsolete LNC concepts).
E: Non-obsolete content marked suppressible by an editor. These do not have a suppressible SAB/TTY combination.
Y: Non-obsolete content deemed suppressible during inversion. These can be determined by a specific SAB/TTY combination explicitly listed in MRRANK.
N: None of the above
Default suppressibility as determined by NLM (i.e., no changes at the Suppressibility tab in MetamorphoSys) should be used by most users, but may not be suitable in some specialized applications. See the MetamorphoSys Help page for information on how to change the SAB/TTY suppressibility to suit your requirements. NLM strongly recommends that users not alter editor-assigned suppressibility, and MetamorphoSys cannot be used for this purpose.
""")
CVF = fields.IntField(null=True, description="Content View Flag. Bit field used to flag rows included in Content View. This field is a varchar field to maximize the number of bits available for use.")
class Meta:
table = "MRCONSO"
MRCONSO_pydantic = pydantic_model_creator(Atom)
main.py
# pylint: disable=E0611
from typing import List, Optional
from fastapi import FastAPI
from fastapi.params import Query
from pydantic import BaseModel
from tortoise.contrib.fastapi import HTTPNotFoundError, register_tortoise
from omnimasticon.models import MRCONSO_pydantic, Atom, Atom_pydantic
app = FastAPI(title="UMLS Search")
class Status(BaseModel):
message: str
@app.get("/atoms", response_model=List[MRCONSO_pydantic])
async def get_atoms(
cuis: List[str] = Query(..., min_items=1),
sabs: Optional[List[str]] = Query(None),
limit: int = 1000):
qs = Atom.filter(CUI__in=cuis)
if sabs is not None:
qs = qs.filter(SAB__in=sabs)
qs.limit(limit)
return await MRCONSO_pydantic.from_queryset(qs) # The error comes from here
Expected behavior
I expect it to serialize the Atom models and make a response
Additional context
By changing my code I'm able get responses like this:
@app.get("/atoms", response_model=List[MRCONSO_pydantic])
async def get_atoms(
cuis: List[str] = Query(..., min_items=1),
sabs: Optional[List[str]] = Query(None),
limit: int = 1000):
qs = Atom.filter(CUI__in=cuis)
if sabs is not None:
qs = qs.filter(SAB__in=sabs)
qs.limit(limit)
models = await qs
ret = [await MRCONSO_pydantic.from_tortoise_orm(data) for data in models]
return ret
or I can change the .from_queryset method from:
@classmethod
async def from_queryset(cls, queryset: "QuerySet") -> "List[PydanticModel]":
"""
Returns a serializable pydantic model instance that contains a list of models,
from the provided queryset.
This will prefetch all the relations automatically.
:param queryset: a queryset on the model this PydanticModel is based on.
"""
fetch_fields = _get_fetch_fields(cls, getattr(cls.__config__, "orig_model"))
return [cls.from_orm(e) for e in await queryset.prefetch_related(*fetch_fields)]
to :
@classmethod
async def from_queryset(cls, queryset: "QuerySet") -> "List[PydanticModel]":
"""
Returns a serializable pydantic model instance that contains a list of models,
from the provided queryset.
This will prefetch all the relations automatically.
:param queryset: a queryset on the model this PydanticModel is based on.
"""
fetch_fields = _get_fetch_fields(cls, getattr(cls.__config__, "orig_model"))
data = await queryset.prefetch_related(*fetch_fields)
return [cls.from_orm(e) for e in data]
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The reported failure is in MRCONSO_pydantic.from_queryset(qs), with the implementation and two variants shown in the issue. Start by reproducing the FastAPI /atoms request with the Atom queryset, compare the from_queryset behavior with the working from_tortoise_orm path, and confirm serialization succeeds without the NoneType error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fastapi, python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100