Beispiel #1
0
    def __init__(
        self,
        *,
        title: str = "NinjaAPI",
        version: str = "1.0.0",
        description: str = "",
        openapi_url: Optional[str] = "/openapi.json",
        docs_url: Optional[str] = "/docs",
        urls_namespace: Optional[str] = None,
        csrf: bool = False,
        auth: Union[Sequence[Callable], Callable, object] = NOT_SET,
        renderer: Optional[BaseRenderer] = None,
        parser: Optional[Parser] = None,
    ):
        self.title = title
        self.version = version
        self.description = description
        self.openapi_url = openapi_url
        self.docs_url = docs_url
        self.urls_namespace = urls_namespace or f"api-{self.version}"
        self.csrf = csrf
        self.renderer = renderer or JSONRenderer()
        self.parser = parser or Parser()

        self._exception_handlers: Dict[Exc, ExcHandler] = {}
        self.set_default_exception_handlers()

        self.auth: Optional[Sequence[Callable]] = NOT_SET
        if auth is not None and auth is not NOT_SET:
            self.auth = isinstance(auth, Sequence) and auth or [auth]  # type: ignore

        self._routers: List[Tuple[str, Router]] = []
        self.default_router = Router()
        self.add_router("", self.default_router)
Beispiel #2
0
    def __init__(
        self,
        *,
        title: str = "NinjaAPI",
        version: str = "1.0.0",
        description: str = "",
        openapi_url: Optional[str] = "/openapi.json",
        docs_url: Optional[str] = "/docs",
        urls_namespace: str = None,
        auth: Union[Sequence[Callable], Callable, object] = NOT_SET,
    ):
        self.title = title
        self.version = version
        self.description = description
        self.openapi_url = openapi_url
        self.docs_url = docs_url
        self.urls_namespace = urls_namespace or f"api-{self.version}"
        self.auth: Optional[Sequence[Callable]] = NOT_SET
        if auth is not None and auth is not NOT_SET:
            self.auth = isinstance(auth, Sequence) and auth or [auth]

        self._validate()

        self._routers: List[Tuple[str, Router]] = []
        self.default_router = Router()
        self.add_router("", self.default_router)
Beispiel #3
0
 def add_router(
     self,
     prefix: str,
     router: Router,
     *,
     auth: Any = NOT_SET,
     tags: Optional[List[str]] = None,
 ) -> None:
     if auth != NOT_SET:
         router.auth = auth
     if tags is not None:
         router.tags = tags
     self._routers.extend(router.build_routers(prefix))
     router.set_api_instance(self)
Beispiel #4
0
    def __init__(
        self,
        *,
        title: str = "NinjaAPI",
        version: str = "1.0.0",
        description: str = "",
        openapi_url: Optional[str] = "/openapi.json",
        docs_url: Optional[str] = "/docs",
        docs_decorator: Optional[Callable[[TCallable], TCallable]] = None,
        urls_namespace: Optional[str] = None,
        csrf: bool = False,
        auth: Optional[Union[Sequence[Callable], Callable,
                             NOT_SET_TYPE]] = NOT_SET,
        renderer: Optional[BaseRenderer] = None,
        parser: Optional[Parser] = None,
        default_router: Optional[Router] = None,
    ):
        """
        Args:
            title: A title for the api.
            description: A description for the api.
            version: The API version.
            urls_namespace: The Django URL namespace for the API. If not provided, the namespace will be ``"api-" + self.version``.
            openapi_url: The relative URL to serve the openAPI spec.
            docs_url: The relative URL to serve the API docs.
            csrf: Require a CSRF token for unsafe request types. See <a href="../csrf">CSRF</a> docs.
            auth (Callable | Sequence[Callable] | NOT_SET | None): Authentication class
            renderer: Default response renderer
            parser: Default request parser
        """
        self.title = title
        self.version = version
        self.description = description
        self.openapi_url = openapi_url
        self.docs_url = docs_url
        self.docs_decorator = docs_decorator
        self.urls_namespace = urls_namespace or f"api-{self.version}"
        self.csrf = csrf
        self.renderer = renderer or JSONRenderer()
        self.parser = parser or Parser()

        self._exception_handlers: Dict[Exc, ExcHandler] = {}
        self.set_default_exception_handlers()

        self.auth: Optional[Union[Sequence[Callable], NOT_SET_TYPE]]

        if callable(auth):
            self.auth = [auth]
        else:
            self.auth = auth

        self._routers: List[Tuple[str, Router]] = []
        self.default_router = default_router or Router()
        self.add_router("", self.default_router)
Beispiel #5
0
    def add_router(
        self,
        prefix: str,
        router: Router,
        *,
        auth: Any = NOT_SET,
        tags: Optional[List[str]] = None,
        parent_router: Router = None,
    ) -> None:
        if auth != NOT_SET:
            router.auth = auth
        if tags is not None:
            router.tags = tags

        if parent_router:
            parent_prefix = next(  # pragma: no cover
                (path for path, r in self._routers if r is parent_router), None
            )
            assert parent_prefix is not None
            prefix = normalize_path("/".join((parent_prefix, prefix))).lstrip("/")

        self._routers.extend(router.build_routers(prefix))
        router.set_api_instance(self)
Beispiel #6
0
class NinjaAPI:
    _registry: List[str] = []

    def __init__(
        self,
        *,
        title: str = "NinjaAPI",
        version: str = "1.0.0",
        description: str = "",
        openapi_url: Optional[str] = "/openapi.json",
        docs_url: Optional[str] = "/docs",
        urls_namespace: str = None,
        auth: Union[Sequence[Callable], Callable, object] = NOT_SET,
    ):
        self.title = title
        self.version = version
        self.description = description
        self.openapi_url = openapi_url
        self.docs_url = docs_url
        self.urls_namespace = urls_namespace or f"api-{self.version}"
        self.auth: Optional[Sequence[Callable]] = NOT_SET
        if auth is not None and auth is not NOT_SET:
            self.auth = isinstance(auth, Sequence) and auth or [auth]

        self._validate()

        self._routers: List[Tuple[str, Router]] = []
        self.default_router = Router()
        self.add_router("", self.default_router)

    def get(self, path: str, *, auth=NOT_SET, response=None):
        return self.default_router.get(path,
                                       auth=auth is NOT_SET and self.auth
                                       or auth,
                                       response=response)

    def post(self, path: str, *, auth=NOT_SET, response=None):
        return self.default_router.post(path,
                                        auth=auth is NOT_SET and self.auth
                                        or auth,
                                        response=response)

    def delete(self, path: str, *, auth=NOT_SET, response=None):
        return self.default_router.delete(path,
                                          auth=auth is NOT_SET and self.auth
                                          or auth,
                                          response=response)

    def patch(self, path: str, *, auth=NOT_SET, response=None):
        return self.default_router.patch(path,
                                         auth=auth is NOT_SET and self.auth
                                         or auth,
                                         response=response)

    def put(self, path: str, *, auth=NOT_SET, response=None):
        return self.default_router.put(path,
                                       auth=auth is NOT_SET and self.auth
                                       or auth,
                                       response=response)

    def api_operation(self,
                      methods: List[str],
                      path: str,
                      *,
                      auth=NOT_SET,
                      response=None):
        return self.default_router.api_operation(
            methods,
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response)

    def add_router(self, prefix, router):
        self._routers.append((prefix, router))
        router.set_api_instance(self)

    @property
    def urls(self):
        return (
            self._get_urls(),
            "ninja",
            self.urls_namespace,
        )

    def _get_urls(self):
        result = get_openapi_urls(self)

        for prefix, router in self._routers:
            for path in router.urls_paths(prefix):
                result.append(path)

        result.append(get_root_url(self))
        return result

    @property
    def root_path(self):
        name = f"{self.urls_namespace}:api-root"
        return reverse(name)

    def get_openapi_schema(self, path_prefix=None):
        if path_prefix is None:
            path_prefix = self.root_path
        return get_schema(api=self, path_prefix=path_prefix)

    def _validate(self):
        skip_registry = os.environ.get("NINJA_SKIP_REGISTRY", False)
        if not skip_registry and self.urls_namespace in NinjaAPI._registry:
            msg = [
                "Looks like you created multiple NinjaAPIs",
                "To let ninja distinguish them you need to set either unique version or url_namespace",
                " - NinjaAPI(..., version='2.0.0')",
                " - NinjaAPI(..., urls_namespace='otherapi')",
                f"Already registered: {NinjaAPI._registry}",
            ]
            raise ConfigError("\n".join(msg))
        NinjaAPI._registry.append(self.urls_namespace)
Beispiel #7
0
class NinjaAPI:
    _registry: List[str] = []

    def __init__(
        self,
        *,
        title: str = "NinjaAPI",
        version: str = "1.0.0",
        description: str = "",
        openapi_url: Optional[str] = "/openapi.json",
        docs_url: Optional[str] = "/docs",
        urls_namespace: Optional[str] = None,
        csrf: bool = False,
        auth: Union[Sequence[Callable], Callable, object] = NOT_SET,
        renderer: Optional[BaseRenderer] = None,
        parser: Optional[Parser] = None,
    ):
        self.title = title
        self.version = version
        self.description = description
        self.openapi_url = openapi_url
        self.docs_url = docs_url
        self.urls_namespace = urls_namespace or f"api-{self.version}"
        self.csrf = csrf
        self.renderer = renderer or JSONRenderer()
        self.parser = parser or Parser()

        self._exception_handlers: Dict[Exc, ExcHandler] = {}
        self.set_default_exception_handlers()

        self.auth: Optional[Sequence[Callable]] = NOT_SET
        if auth is not None and auth is not NOT_SET:
            self.auth = isinstance(auth, Sequence) and auth or [
                auth
            ]  # type: ignore

        self._routers: List[Tuple[str, Router]] = []
        self.default_router = Router()
        self.add_router("", self.default_router)

    def get(
        self,
        path: str,
        *,
        auth: Any = NOT_SET,
        response: Any = NOT_SET,
        operation_id: Optional[str] = None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        url_name: Optional[str] = None,
        include_in_schema: bool = True,
    ) -> Decorator:
        return self.default_router.get(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            operation_id=operation_id,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            url_name=url_name,
            include_in_schema=include_in_schema,
        )

    def post(
        self,
        path: str,
        *,
        auth: Any = NOT_SET,
        response: Any = NOT_SET,
        operation_id: Optional[str] = None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        url_name: Optional[str] = None,
        include_in_schema: bool = True,
    ) -> Decorator:
        return self.default_router.post(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            operation_id=operation_id,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            url_name=url_name,
            include_in_schema=include_in_schema,
        )

    def delete(
        self,
        path: str,
        *,
        auth: Any = NOT_SET,
        response: Any = NOT_SET,
        operation_id: Optional[str] = None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        url_name: Optional[str] = None,
        include_in_schema: bool = True,
    ) -> Decorator:
        return self.default_router.delete(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            operation_id=operation_id,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            url_name=url_name,
            include_in_schema=include_in_schema,
        )

    def patch(
        self,
        path: str,
        *,
        auth: Any = NOT_SET,
        response: Any = NOT_SET,
        operation_id: Optional[str] = None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        url_name: Optional[str] = None,
        include_in_schema: bool = True,
    ) -> Decorator:
        return self.default_router.patch(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            operation_id=operation_id,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            url_name=url_name,
            include_in_schema=include_in_schema,
        )

    def put(
        self,
        path: str,
        *,
        auth: Any = NOT_SET,
        response: Any = NOT_SET,
        operation_id: Optional[str] = None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        url_name: Optional[str] = None,
        include_in_schema: bool = True,
    ) -> Decorator:
        return self.default_router.put(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            operation_id=operation_id,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            url_name=url_name,
            include_in_schema=include_in_schema,
        )

    def api_operation(
        self,
        methods: List[str],
        path: str,
        *,
        auth: Any = NOT_SET,
        response: Any = NOT_SET,
        operation_id: Optional[str] = None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
        by_alias: bool = False,
        exclude_unset: bool = False,
        exclude_defaults: bool = False,
        exclude_none: bool = False,
        url_name: Optional[str] = None,
        include_in_schema: bool = True,
    ) -> Decorator:
        return self.default_router.api_operation(
            methods,
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            operation_id=operation_id,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
            by_alias=by_alias,
            exclude_unset=exclude_unset,
            exclude_defaults=exclude_defaults,
            exclude_none=exclude_none,
            url_name=url_name,
            include_in_schema=include_in_schema,
        )

    def add_router(
        self,
        prefix: str,
        router: Router,
        *,
        auth: Any = NOT_SET,
        tags: Optional[List[str]] = None,
    ) -> None:
        if auth != NOT_SET:
            router.auth = auth
        if tags is not None:
            router.tags = tags
        self._routers.extend(router.build_routers(prefix))
        router.set_api_instance(self)

    @property
    def urls(self) -> Tuple[Any, ...]:
        self._validate()
        return (
            self._get_urls(),
            "ninja",
            self.urls_namespace.split(":")[-1],
            # ^ if api included into nested urls, we only care about last bit here
        )

    def _get_urls(self) -> List[URLPattern]:
        result = get_openapi_urls(self)

        for prefix, router in self._routers:
            for path in router.urls_paths(prefix):
                result.append(path)

        result.append(get_root_url(self))
        return result

    @property
    def root_path(self) -> str:
        name = f"{self.urls_namespace}:api-root"
        return reverse(name)

    def create_response(self,
                        request: HttpRequest,
                        data: Any,
                        *,
                        status: int = 200) -> HttpResponse:
        content = self.renderer.render(request, data, response_status=status)
        content_type = "{}; charset={}".format(self.renderer.media_type,
                                               self.renderer.charset)
        return HttpResponse(content, status=status, content_type=content_type)

    def get_openapi_schema(self,
                           path_prefix: Optional[str] = None) -> OpenAPISchema:
        if path_prefix is None:
            path_prefix = self.root_path
        return get_schema(api=self, path_prefix=path_prefix)

    def get_openapi_operation_id(self, operation: "Operation") -> str:
        name = operation.view_func.__name__
        module = operation.view_func.__module__
        return (module + "_" + name).replace(".", "_")

    def add_exception_handler(self, exc_class: Type[Exception],
                              handler: ExcHandler) -> None:
        assert issubclass(exc_class, Exception)
        self._exception_handlers[exc_class] = handler

    def exception_handler(self, exc_class: Type[Exception]) -> Callable:
        def decorator(func: Callable) -> Callable:
            self.add_exception_handler(exc_class, func)
            return func

        return decorator

    def set_default_exception_handlers(self) -> None:
        set_default_exc_handlers(self)

    def on_exception(self, request: HttpRequest, exc: Exc) -> HttpResponse:
        handler = self._lookup_exception_handler(exc)
        if handler is None:
            raise exc
        return handler(request, exc)

    def _lookup_exception_handler(self, exc: Exc) -> Optional[ExcHandler]:
        for cls in type(exc).__mro__:
            if cls in self._exception_handlers:
                return self._exception_handlers[cls]

        return None

    def _validate(self) -> None:
        from ninja.security import APIKeyCookie

        # 1) urls namespacing validation
        skip_registry = os.environ.get("NINJA_SKIP_REGISTRY", False)
        if not skip_registry and self.urls_namespace in NinjaAPI._registry:
            msg = [
                "Looks like you created multiple NinjaAPIs",
                "To let ninja distinguish them you need to set either unique version or url_namespace",
                " - NinjaAPI(..., version='2.0.0')",
                " - NinjaAPI(..., urls_namespace='otherapi')",
                f"Already registered: {NinjaAPI._registry}",
            ]
            raise ConfigError("\n".join(msg))
        NinjaAPI._registry.append(self.urls_namespace)

        # 2) csrf
        if self.csrf is False:
            for _prefix, router in self._routers:
                for path_operation in router.path_operations.values():
                    for operation in path_operation.operations:
                        for auth in operation.auth_callbacks:
                            if isinstance(auth, APIKeyCookie):
                                raise ConfigError(
                                    "Cookie Authentication must be used with CSRF. Please use NinjaAPI(csrf=True)"
                                )
Beispiel #8
0
class NinjaAPI:
    _registry: List[str] = []

    def __init__(
        self,
        *,
        title: str = "NinjaAPI",
        version: str = "1.0.0",
        description: str = "",
        openapi_url: Optional[str] = "/openapi.json",
        docs_url: Optional[str] = "/docs",
        urls_namespace: str = None,
        csrf: bool = False,
        auth: Union[Sequence[Callable], Callable, object] = NOT_SET,
    ):
        self.title = title
        self.version = version
        self.description = description
        self.openapi_url = openapi_url
        self.docs_url = docs_url
        self.urls_namespace = urls_namespace or f"api-{self.version}"
        self.csrf = csrf

        self.auth: Optional[Sequence[Callable]] = NOT_SET
        if auth is not None and auth is not NOT_SET:
            self.auth = isinstance(auth, Sequence) and auth or [auth]

        self._routers: List[Tuple[str, Router]] = []
        self.default_router = Router()
        self.add_router("", self.default_router)

    def get(
        self,
        path: str,
        *,
        auth=NOT_SET,
        response=None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
    ):
        return self.default_router.get(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
        )

    def post(
        self,
        path: str,
        *,
        auth=NOT_SET,
        response=None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
    ):
        return self.default_router.post(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
        )

    def delete(
        self,
        path: str,
        *,
        auth=NOT_SET,
        response=None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
    ):
        return self.default_router.delete(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
        )

    def patch(
        self,
        path: str,
        *,
        auth=NOT_SET,
        response=None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
    ):
        return self.default_router.patch(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
        )

    def put(
        self,
        path: str,
        *,
        auth=NOT_SET,
        response=None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
    ):
        return self.default_router.put(
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
        )

    def api_operation(
        self,
        methods: List[str],
        path: str,
        *,
        auth=NOT_SET,
        response=None,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        deprecated: Optional[bool] = None,
    ):
        return self.default_router.api_operation(
            methods,
            path,
            auth=auth is NOT_SET and self.auth or auth,
            response=response,
            summary=summary,
            description=description,
            tags=tags,
            deprecated=deprecated,
        )

    def add_router(self, prefix, router):
        self._routers.extend(router.build_routers(prefix))
        router.set_api_instance(self)

    @property
    def urls(self):
        self._validate()
        return (
            self._get_urls(),
            "ninja",
            self.urls_namespace,
        )

    def _get_urls(self):
        result = get_openapi_urls(self)

        for prefix, router in self._routers:
            for path in router.urls_paths(prefix):
                result.append(path)

        result.append(get_root_url(self))
        return result

    @property
    def root_path(self):
        name = f"{self.urls_namespace}:api-root"
        return reverse(name)

    def get_openapi_schema(self, path_prefix=None):
        if path_prefix is None:
            path_prefix = self.root_path
        return get_schema(api=self, path_prefix=path_prefix)

    def _validate(self):
        from ninja.security import APIKeyCookie

        # 1) urls namespacing validation
        skip_registry = os.environ.get("NINJA_SKIP_REGISTRY", False)
        if not skip_registry and self.urls_namespace in NinjaAPI._registry:
            msg = [
                "Looks like you created multiple NinjaAPIs",
                "To let ninja distinguish them you need to set either unique version or url_namespace",
                " - NinjaAPI(..., version='2.0.0')",
                " - NinjaAPI(..., urls_namespace='otherapi')",
                f"Already registered: {NinjaAPI._registry}",
            ]
            raise ConfigError("\n".join(msg))
        NinjaAPI._registry.append(self.urls_namespace)

        # 2) csrf
        if self.csrf is False:
            for _prefix, router in self._routers:
                for path_operation in router.operations.values():
                    for operation in path_operation.operations:
                        for auth in operation.auth_callbacks:
                            if isinstance(auth, APIKeyCookie):
                                raise ConfigError(
                                    "Cookie Authentication must be used with CSRF. Please use NinjaAPI(csrf=True)"
                                )
 def add_router(self, prefix: str, router: Router) -> None:
     self._routers.extend(router.build_routers(prefix))
     router.set_api_instance(self)