Exemple #1
0
    def _args_flatten_map(self,
                          args: List[FuncParam]) -> Dict[str, Tuple[str, ...]]:
        flatten_map = {}
        arg_names: Any = {}
        for arg in args:
            if is_pydantic_model(arg.annotation):
                for name, path in self._model_flatten_map(
                        arg.annotation, arg.alias):
                    if name in flatten_map:
                        raise ConfigError(
                            f"Duplicated name: '{name}' in params: '{arg_names[name]}' & '{arg.name}'"
                        )
                    flatten_map[name] = tuple(path.split(
                        self.FLATTEN_PATH_SEP))
                    arg_names[name] = arg.name
            else:
                name = arg.alias
                if name in flatten_map:
                    raise ConfigError(
                        f"Duplicated name: '{name}' also in '{arg_names[name]}'"
                    )
                flatten_map[name] = (name, )
                arg_names[name] = name

        return flatten_map
Exemple #2
0
    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)"
                                )
Exemple #3
0
    def __new__(
        mcs,
        name: str,
        bases: tuple,
        namespace: dict,
    ):
        cls = super().__new__(mcs, name, bases, namespace)
        for base in reversed(bases):
            if (
                _is_modelschema_class_defined
                and issubclass(base, ModelSchema)
                and base == ModelSchema
            ):
                try:
                    config = namespace["Config"]
                except KeyError:
                    raise ConfigError(
                        f"ModelSchema class '{name}' requires a 'Config' subclass"
                    )

                assert issubclass(config.model, DjangoModel)

                fields = getattr(config, "model_fields", None)
                exclude = getattr(config, "model_exclude", None)

                if not fields and not exclude:
                    raise ConfigError(
                        "Creating a ModelSchema without either the 'model_fields' attribute"
                        " or the 'model_exclude' attribute is prohibited"
                    )

                if fields == "__all__":
                    fields = None
                    # ^ when None is passed to create_schema - all fields are selected

                custom_fields = []
                annotations = namespace.get("__annotations__", {})
                for attr_name, type in annotations.items():
                    if attr_name.startswith("_"):
                        continue
                    default = namespace.get(attr_name, ...)
                    custom_fields.append((attr_name, type, default))

                # cls.__doc__ = namespace.get("__doc__", config.model.__doc__)
                cls.__fields__ = {}  # forcing pydantic recreate

                # print(config.model, name, fields, exclude, "!!")

                model_schema = create_schema(
                    config.model,
                    name=name,
                    fields=fields,
                    exclude=exclude,
                    custom_fields=custom_fields,
                    base_class=cls,
                )
                model_schema.__doc__ = cls.__doc__
                return model_schema

        return cls
Exemple #4
0
    def _selected_model_fields(
        self,
        model: Type[Model],
        fields: Optional[List[str]] = None,
        exclude: Optional[List[str]] = None,
    ) -> Iterator[Field]:
        "Returns iterator for model fields based on `exclude` or `fields` arguments"
        all_fields = {f.name: f for f in self._model_fields(model)}

        if not fields and not exclude:
            for f in all_fields.values():
                yield f

        invalid_fields = (set(fields or [])
                          | set(exclude or [])) - all_fields.keys()
        if invalid_fields:
            raise ConfigError(f"Field(s) {invalid_fields} are not in model.")

        if fields:
            for name in fields:
                yield all_fields[name]
        if exclude:
            for f in all_fields.values():
                if f.name not in exclude:
                    yield f
Exemple #5
0
    def create_schema(
        self,
        model: Type[Model],
        *,
        name: str = "",
        depth: int = 0,
        fields: Optional[List[str]] = None,
        exclude: Optional[List[str]] = None,
    ) -> Type[Schema]:
        name = name or model.__name__

        if fields and exclude:
            raise ConfigError(
                "Only one of 'include' or 'exclude' should be set.")

        key = self.get_key(model, name, depth, fields, exclude)
        if key in self.schemas:
            return self.schemas[key]

        definitions = {}
        for fld in self._selected_model_fields(model, fields, exclude):
            python_type, field_info = get_schema_field(fld, depth=depth)
            definitions[fld.name] = (python_type, field_info)

        schema = cast(
            Type[Schema],
            create_pydantic_model(name, __base__=Schema,
                                  **definitions),  # type: ignore
        )
        self.schemas[key] = schema
        return schema
Exemple #6
0
    def _result_to_response(self, request: HttpRequest, result: Any):
        if isinstance(result, HttpResponse):
            return result
        if self.response_model is None:
            return self.api.create_response(request, result)

        status = 200
        response_model = self.response_model
        if isinstance(result, tuple) and len(result) == 2:
            status = result[0]
            result = result[1]
        if isinstance(response_model, dict):
            if status in response_model:
                response_model = response_model[status]
            elif Ellipsis in response_model:
                response_model = response_model[Ellipsis]
            else:
                raise ConfigError(
                    f"Schema for status {status} is not set in response {response_model.keys()}"
                )

        resp_object = ResponseObject(result)
        # ^ we need object because getter_dict seems work only with from_orm
        result = response_model.from_orm(resp_object).dict()["response"]
        return self.api.create_response(request, result, status=status)
Exemple #7
0
def _inject_pagination(
    func: Callable,
    paginator_class: Type[PaginationBase],
    **paginator_params: DictStrAny,
) -> Callable:
    if not has_kwargs(func):
        raise ConfigError(
            f"function {func.__name__} must have **kwargs argument to be used with pagination"
        )

    paginator: PaginationBase = paginator_class(**paginator_params)

    @wraps(func)
    def view_with_pagination(request: HttpRequest, **kw: DictStrAny) -> Any:
        items = func(request, **kw)
        return paginator.paginate_queryset(items, request, **kw)

    view_with_pagination._ninja_contribute_args = [  # type: ignore
        (
            "pagination",
            paginator.Input,
            paginator.InputSource,
        ),
    ]

    return view_with_pagination
Exemple #8
0
    def __init__(self):
        if not hasattr(self, "openapi_type"):
            raise ConfigError("If you extend AuthBase you need to define openapi_type")

        kwargs = {}
        for attr in dir(self):
            if attr.startswith("openapi_"):
                name = attr.replace("openapi_", "", 1)
                kwargs[name] = getattr(self, attr)
        self.openapi_security_schema = SecuritySchema(**kwargs)
Exemple #9
0
 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)
Exemple #10
0
def _find_collection_response(op: Operation) -> Tuple[int, Any]:
    for code, resp_model in op.response_models.items():
        if resp_model is None or resp_model is NOT_SET:
            continue

        model = resp_model.__annotations__["response"]
        if is_collection_type(model):
            item_schema = get_collection_args(model)[0]
            return code, item_schema

    raise ConfigError(
        f'"{op.view_func}" has no collection response (e.g. response=List[SomeSchema])'
    )
Exemple #11
0
    def _result_to_response(
            self, request: HttpRequest, result: Any,
            temporal_response: HttpResponse) -> HttpResponseBase:
        """
        The protocol for results
         - if HttpResponse - returns as is
         - if tuple with 2 elements - means http_code + body
         - otherwise it's a body
        """
        if isinstance(result, HttpResponseBase):
            return result

        status: int = 200
        if len(self.response_models) == 1:
            status = next(iter(self.response_models))

        if isinstance(result, tuple) and len(result) == 2:
            status = result[0]
            result = result[1]

        if status in self.response_models:
            response_model = self.response_models[status]
        elif Ellipsis in self.response_models:
            response_model = self.response_models[Ellipsis]
        else:
            raise ConfigError(
                f"Schema for status {status} is not set in response {self.response_models.keys()}"
            )

        temporal_response.status_code = status

        if response_model is NOT_SET:
            return self.api.create_response(
                request, result, temporal_response=temporal_response)

        if response_model is None:
            # Empty response.
            return temporal_response

        resp_object = ResponseObject(result)
        # ^ we need object because getter_dict seems work only with from_orm
        result = response_model.from_orm(resp_object).dict(
            by_alias=self.by_alias,
            exclude_unset=self.exclude_unset,
            exclude_defaults=self.exclude_defaults,
            exclude_none=self.exclude_none,
        )["response"]
        return self.api.create_response(request,
                                        result,
                                        temporal_response=temporal_response)
Exemple #12
0
    def build_routers(self, prefix: str) -> List[Tuple[str, "Router"]]:
        if self.api is not None:
            from ninja.main import debug_server_url_reimport

            if not debug_server_url_reimport():
                raise ConfigError(
                    f"Router@'{prefix}' has already been attached to API {self.api.title}:{self.api.version} "
                )
        internal_routes = []
        for inter_prefix, inter_router in self._routers:
            _route = normalize_path("/".join((prefix, inter_prefix))).lstrip("/")
            internal_routes.extend(inter_router.build_routers(_route))

        return [(prefix, self), *internal_routes]
Exemple #13
0
 def check_for_duplicates_on_exception(self, exc: Exception) -> None:
     """check for duplicate named schemas: https://github.com/vitalik/django-ninja/issues/214"""
     exc_args = getattr(exc, "args", None)
     if exc_args and isinstance(exc_args[0], type(Schema)):
         schema = exc_args[0]
         schema_found = tuple(k for k, v in self.schemas.items()
                              if v == schema)
         if schema_found:
             model_name = schema_found[0][1]
             same_name_keys = tuple(key for key in factory.schemas
                                    if key[1] == model_name)
             if len(same_name_keys) > 1:
                 errors = "\n".join(f"  {key}" for key in same_name_keys)
                 msg = f"Looks like you may have created multiple orm schemas with the same name:\n{errors}"
                 raise ConfigError(msg) from exc
Exemple #14
0
    def create_schema(
        self,
        model: Type[Model],
        *,
        name: str = "",
        depth: int = 0,
        fields: Optional[List[str]] = None,
        exclude: Optional[List[str]] = None,
        custom_fields: Optional[List[Tuple[str, Any, Any]]] = None,
        base_class: Type[Schema] = Schema,
    ) -> Type[Schema]:
        name = name or model.__name__

        if fields and exclude:
            raise ConfigError("Only one of 'fields' or 'exclude' should be set.")

        key = self.get_key(model, name, depth, fields, exclude, custom_fields)
        if key in self.schemas:
            return self.schemas[key]

        definitions = {}
        for fld in self._selected_model_fields(model, fields, exclude):
            python_type, field_info = get_schema_field(fld, depth=depth)
            definitions[fld.name] = (python_type, field_info)

        if custom_fields:
            for fld_name, python_type, field_info in custom_fields:
                definitions[fld_name] = (python_type, field_info)

        if name in self.schema_names:
            name = self._get_unique_name(name)

        schema: Type[Schema] = create_pydantic_model(
            name,
            __config__=None,
            __base__=base_class,
            __module__=base_class.__module__,
            __validators__={},
            **definitions,
        )  # type: ignore
        self.schemas[key] = schema
        self.schema_names.add(name)
        return schema