โ† baseline_full_v2

fastapi_14448

failed NO_PATCH NO_PATCH UNSUBMITTED empty_patch(None) ยท None tool calls ยท 0 s ยท fastapi/fastapi

Task input

๐Ÿ› Fix support for functools wraps and partial combined, for async and regular functions and classes in path operations and dependencies

๐Ÿ› Fix support for functools wraps and partial combined, for async and regular functions and classes in path operations and dependencies

This also uses and extends the tests created by @YuriiMotov :raised_hands: 

Should fix / related to: https://github.com/fastapi/fastapi/issues/14444

The new tests simulate the multiple combinations of how decorators could affect dependencies and path operation functions (endpoints).

The logic to analyze if a function is an async, generator, async generator,  callable instance, etc. now also checks and unwraps and extracts partials in all those levels.

The main case is when the callable is an _async_ something (function, generator, etc) because then it needs to be awaited.

But the original function could be async or not, the wrapper could be async or not. if either is async, then it has to be awaited, no matter if the other is not async. So, analyzing only the last point in the chain is not enough, it's necessary to analyze the function but also the wrapper.

This PR implements and tests all that, all those combinations.

Tool calls (0)

#ToolArgumentsResult
No trace captured.

Test output

show
urn encoder_instance(obj)
        if include is not None and not isinstance(include, (set, dict)):
            include = set(include)
        if exclude is not None and not isinstance(exclude, (set, dict)):
            exclude = set(exclude)
        if isinstance(obj, (BaseModel, may_v1.BaseModel)):
            # TODO: remove when deprecating Pydantic v1
            encoders: Dict[Any, Any] = {}
            if isinstance(obj, may_v1.BaseModel):
                encoders = getattr(obj.__config__, "json_encoders", {})  # type: ignore[attr-defined]
                if custom_encoder:
                    encoders = {**encoders, **custom_encoder}
            obj_dict = _model_dump(
                obj,
                mode="json",
                include=include,
                exclude=exclude,
                by_alias=by_alias,
                exclude_unset=exclude_unset,
                exclude_none=exclude_none,
                exclude_defaults=exclude_defaults,
            )
            if "__root__" in obj_dict:
                obj_dict = obj_dict["__root__"]
            return jsonable_encoder(
                obj_dict,
                exclude_none=exclude_none,
                exclude_defaults=exclude_defaults,
                # TODO: remove when deprecating Pydantic v1
                custom_encoder=encoders,
                sqlalchemy_safe=sqlalchemy_safe,
            )
        if dataclasses.is_dataclass(obj):
            assert not isinstance(obj, type)
            obj_dict = dataclasses.asdict(obj)
            return jsonable_encoder(
                obj_dict,
                include=include,
                exclude=exclude,
                by_alias=by_alias,
                exclude_unset=exclude_unset,
                exclude_defaults=exclude_defaults,
                exclude_none=exclude_none,
                custom_encoder=custom_encoder,
                sqlalchemy_safe=sqlalchemy_safe,
            )
        if isinstance(obj, Enum):
            return obj.value
        if isinstance(obj, PurePath):
            return str(obj)
        if isinstance(obj, (str, int, float, type(None))):
            return obj
        if _is_undefined(obj):
            return None
        if isinstance(obj, dict):
            encoded_dict = {}
            allowed_keys = set(obj.keys())
            if include is not None:
                allowed_keys &= set(include)
            if exclude is not None:
                allowed_keys -= set(exclude)
            for key, value in obj.items():
                if (
                    (
                        not sqlalchemy_safe
                        or (not isinstance(key, str))
                        or (not key.startswith("_sa"))
                    )
                    and (value is not None or not exclude_none)
                    and key in allowed_keys
                ):
                    encoded_key = jsonable_encoder(
                        key,
                        by_alias=by_alias,
                        exclude_unset=exclude_unset,
                        exclude_none=exclude_none,
                        custom_encoder=custom_encoder,
                        sqlalchemy_safe=sqlalchemy_safe,
                    )
                    encoded_value = jsonable_encoder(
                        value,
                        by_alias=by_alias,
                        exclude_unset=exclude_unset,
                        exclude_none=exclude_none,
                        custom_encoder=custom_encoder,
                        sqlalchemy_safe=sqlalchemy_safe,
                    )
                    encoded_dict[encoded_key] = encoded_value
            return encoded_dict
        if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
            encoded_list = []
            for item in obj:
                encoded_list.append(
                    jsonable_encoder(
                        item,
                        include=include,
                        exclude=exclude,
                        by_alias=by_alias,
                        exclude_unset=exclude_unset,
                        exclude_defaults=exclude_defaults,
                        exclude_none=exclude_none,
                        custom_encoder=custom_encoder,
                        sqlalchemy_safe=sqlalchemy_safe,
                    )
                )
            return encoded_list
    
        if type(obj) in ENCODERS_BY_TYPE:
            return ENCODERS_BY_TYPE[type(obj)](obj)
        for encoder, classes_tuple in encoders_by_class_tuples.items():
            if isinstance(obj, classes_tuple):
                return encoder(obj)
    
        try:
            data = dict(obj)
        except Exception as e:
            errors: List[Exception] = []
            errors.append(e)
            try:
                data = vars(obj)
            except Exception as e:
                errors.append(e)
>               raise ValueError(errors) from e
E               ValueError: [TypeError("'coroutine' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]

fastapi/encoders.py:345: ValueError
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 10 passed, 1 warning in 0.84s
<sys>:0: RuntimeWarning: coroutine 'ClassInstanceAsyncWrappedDep.__call__' was never awaited