#load many to many relations

23 messages · Page 1 of 1 (latest)

raven ferry
#

I'm using fastapi with django, but I'm having problems with async with many to many relationships...

#

I made this code to transform django models into pydantic models, but I'm getting an async error

#

def to_schema(schema: type[Schema], model: type[models.Model]):
    async def load_field(obj, field):
        print("LOADING FIELD", field.name)

        await sync_to_async(lambda: getattr(obj, field.name))()

    async def load_many(obj, field):
        print("LOADING MANY", field.name)

        res = await sync_to_async(list)(getattr(obj, field.name).all())
        getattr(obj, field.name)
        return res

    def get_field_type(field_info):
        type = field_info.annotation

        if typing.get_origin(type) is list:
            type = type.__args__[0]

        return getattr(type, "model_fields", type)

#
    async def load(obj, model_, fields):
        print(fields)
        print(type(fields))

        if not isinstance(fields, dict):
            return

        fields_to_prefetch = [
            field for field in model_._meta.get_fields() if field.is_relation
        ]

        for field in fields_to_prefetch:
            if field.name in fields:
                if field.one_to_one or field.many_to_one:
                    await load_field(obj, field)
                    await load(
                        getattr(obj, field.name),
                        model_=field.related_model,
                        fields=get_field_type(fields[field.name]),
                    )
                else:
                    for obj_ in await load_many(obj, field):
                        await load(
                            obj_,
                            field.related_model,
                            get_field_type(fields[field.name]),
                        )


#
    def inner(func: Callable[..., Any]):
        @wraps(func)
        async def wrapper(*args: Any, **kwargs: Any):
            res = await func(*args, **kwargs)

            fields_to_prefetch = [
                field for field in model._meta.get_fields() if field.is_relation
            ]

            for field in fields_to_prefetch:
                if field.name in schema.model_fields:
                    if isinstance(res, list):
                        for obj in res:
                            await load_field(obj, field)
                            await load(
                                getattr(obj, field.name),
                                field.related_model,
                                get_field_type(schema.model_fields[field.name]),
                            )
                    else:
                        await load_field(res, field)
                        await load(
                            getattr(res, field.name),
                            field.related_model,
                            get_field_type(schema.model_fields[field.name]),
                        )

            return res

        return wrapper

    return inner

#

but I am getting this error

  {'type': 'get_attribute_error', 'loc': ('response', 'pack', 'config', 'exclude_collections'), 'msg': 'Error extracting attribute: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.', 'input': <DjangoGetter: <FutPackConfig: a Config>>, 'ctx': {'error': 'SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.'}}
  {'type': 'get_attribute_error', 'loc': ('response', 'pack', 'config', 'only_cards'), 'msg': 'Error extracting attribute: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.', 'input': <DjangoGetter: <FutPackConfig: a Config>>, 'ctx': {'error': 'SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.'}}
  {'type': 'get_attribute_error', 'loc': ('response', 'pack', 'config', 'exclude_cards'), 'msg': 'Error extracting attribute: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.', 'input': <DjangoGetter: <FutPackConfig: a Config>>, 'ctx': {'error': 'SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.'}}
  {'type': 'get_attribute_error', 'loc': ('response', 'pack', 'config', 'only_teams'), 'msg': 'Error extracting attribute: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.', 'input': <DjangoGetter: <FutPackConfig: a Config>>, 'ctx': {'error': 'SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.'}}

#

They are all many to many relationships

#

however it uses .all() to load, should I use something else?

hasty fjord
#

That's a whole lot of code. I suggest you reduce it to the bare minimum where the error happens.

#

supposedly all should be usable in an async context, but you could probably move it in sync_to_async.

I don't get the sync_to_async(list) though

#
res = await sync_to_async(getattr(obj, field.name).all)()
#

P.S. I've never truly used async beyond experiments, so take this with a grain of salt.

raven ferry
#

the error occurs in many to many relationships,

for example user.houses

it gives an async error because it is a many to many relationship, even though I have already done an .all() before...

#

The error occurs when trying to transform django's models.Model to pydantic.BaseModel, because for some reason it is not saving .all() that I did previously...

#

I tried to find something about this in django ninja, but I didn't find anything

#

:v

hasty fjord
#

Sorry, can't be of more help, I truly hope you solve your problem!

raven ferry
#

.

halcyon sparrow
#

Hi @raven ferry , I am just curious what's the reason you paired fastApi with Django for ?

raven ferry
upbeat plinth
#

(but you are aware that drf and django-ninja exists?)