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
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)" )
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
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
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
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)
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
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)
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)
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])' )
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)
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]
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
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