Skip to content

[BUG] Endpoint with query schema crashes when paginated and from __future__ import annotations import included #1424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jjnesbitt opened this issue Mar 14, 2025 · 4 comments

Comments

@jjnesbitt
Copy link

jjnesbitt commented Mar 14, 2025

Apologies for the lengthy title.

Describe the bug
I get the following error on application start:

NameError: name 'MySchema' is not defined

If the following conditions are met:

  1. An endpoint function is defined with the paginate decorator
  2. There is an argument to the function that is a user defined query param schema (Query[MySchema])
  3. The from __future__ import annotations import is present in the file

If any of these three things are excluded, the bug does not occur.

Versions (please complete the following information):

  • Python versions tested: 3.11, 3.12, 3.13
  • Django version: 5.1.6
  • Django-Ninja version: 1.3.0
  • Pydantic version: 2.10.6

Here is an MRE:

from __future__ import annotations

from ninja import NinjaAPI, Query, Schema
from ninja.pagination import PageNumberPagination, paginate


class MySchema(Schema):
    foo: str


api = NinjaAPI()


@api.get("/endpoint_path", response=list[int])
@paginate(PageNumberPagination)
def atpath(
    request,
    params: Query[MySchema],
):
    return []

@jjnesbitt
Copy link
Author

This seems similar to #1357, but I don't think these are duplicate issues.

@vitalik
Copy link
Owner

vitalik commented Mar 14, 2025

Hi @jjnesbitt

there is some dark magic with evaulate forward refs in pydantic that I do not fully understand - but for now you can try this branch #1425 (1424-evaluate-forward-ref)

@jjnesbitt
Copy link
Author

Hi @vitalik, has this been fixed via #1425?

@benf710
Copy link

benf710 commented May 7, 2025

I'm still able to reproduce this error with Django Ninja 1.4.1. I was able to work around this issue by overriding globals using a custom @paginate decorator:

def repair_wrapped_globalns(
    f: Callable[..., Any],
    globalns: dict[str, Any] | None = None,
    module: str | None = None,
):
    """Use this to generate a new function with an updated global namespace.

    Credit: https://stackoverflow.com/a/49077211
    """

    if globalns is None:
        globalns = f.__globals__

    g = types.FunctionType(
        f.__code__,
        globalns,
        name=f.__name__,
        argdefs=f.__defaults__,
        closure=f.__closure__,
    )

    g = functools.update_wrapper(g, f)

    if module is not None:
        g.__module__ = module

    g.__kwdefaults__ = copy.copy(f.__kwdefaults__)  # pyright: ignore[reportAttributeAccessIssue]

    return g


def paginate(func_or_pgn_class: Any = NOT_SET, **paginator_params: DictStrAny) -> Callable[..., Any]:
    """Custom `@paginate` decorator because, in brief, the current version of Django Ninja doesn't handle
    globals correctly when resolving type signatures.

    NOTE: The ninja.conf import has to be within this function for `import_string(settings.PAGINATION_CLASS)`
    to work correctly.
    """

    from ninja.conf import settings

    isfunction = inspect.isfunction(func_or_pgn_class)
    isnotset = func_or_pgn_class == NOT_SET

    pagination_class: type[PaginationBase] = import_string(settings.PAGINATION_CLASS)

    # ! This condition is where the source code has been modified.
    if isfunction:
        ret = _inject_pagination(func_or_pgn_class, pagination_class)
        ret = repair_wrapped_globalns(ret, func_or_pgn_class.__globals__, func_or_pgn_class.__name__)
        return ret

    if not isnotset:
        pagination_class = func_or_pgn_class

    def wrapper(func: Callable[..., Any]) -> Any:
        return _inject_pagination(func, pagination_class, **paginator_params)

    return wrapper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants