def test(self, resolver, modifiers, expected_result_from_name, expected_result_from_schema): schema = SimpleSchema(only=['id'], exclude=['name']) with patch(f'{module_path}.MODIFIERS', new=modifiers): with patch(f'{module_path}.resolver', side_effect=resolver) as mock_resolver: result_from_name = create_schema_name( name_schema=SimpleSchema.__name__) result_from_schema = create_schema_name(schema=schema) assert result_from_name == expected_result_from_name assert result_from_schema == expected_result_from_schema
def process_query_params_spec(self, schema_ref_name: str): if schema_not_in_registry(schema_ref_name): # probably schema name was updated from e.g. `UserSchema` to `User` by `_ref_to_spec` # try to recover its name if first option was not found schema_ref_name = f"{schema_ref_name}Schema" new_parameters = [] name_schema = create_schema_name(name_schema=schema_ref_name) if name_schema in self.spec.components.schemas: for i_name, i_value in self.spec.components.schemas[name_schema][ "properties"].items(): new_parameter = { "name": i_name, "in": "query", "type": i_value.get("type"), "description": i_value.get("description", ""), } if "example" in i_value: new_parameter["example"] = i_value["example"] if "items" in i_value: new_items = { "type": i_value["items"].get("type"), } if "enum" in i_value["items"]: new_items["enum"] = i_value["items"]["enum"] new_parameter.update({"items": new_items}) new_parameters.append(new_parameter) return new_parameters
def resolve_nested_schema(self, schema): """Return the Open API representation of a marshmallow Schema. Adds the schema to the spec if it isn't already present. Typically will return a dictionary with the reference to the schema's path in the spec unless the `schema_name_resolver` returns `None`, in which case the returned dictoinary will contain a JSON Schema Object representation of the schema. :param schema: schema to add to the spec """ schema_instance = resolve_schema_instance(schema) schema_key = make_schema_key(schema_instance) if schema_key not in self.refs: schema_cls = self.resolve_schema_class(schema) name = self.schema_name_resolver(schema_cls) if not name: try: json_schema = self.schema2jsonschema(schema) except RuntimeError: raise APISpecError( "Name resolver returned None for schema {schema} which is " "part of a chain of circular referencing schemas. Please" " ensure that the schema_name_resolver passed to" " MarshmallowPlugin returns a string for all circular" " referencing schemas.".format(schema=schema) ) if getattr(schema, "many", False): return {"type": "array", "items": json_schema} return json_schema name = create_schema_name(schema=schema_instance) if name not in self.spec.components._schemas: self.spec.components.schema(name, schema=schema) return self.get_ref_dict(schema_instance)
def test__ref_to_spec__with_ref_data(self): mock_self = Mock() mock_self.spec.components._schemas = [] schema = SomeSchema schema_name = create_schema_name(schema) data = {'$ref': f'#/definitions/{schema.__name__}'} RestfulPlugin._ref_to_spec(mock_self, data) mock_self.spec.components.schema.assert_called_once_with(schema_name, schema=schema) assert data['$ref'] == f'#/definitions/{schema_name}'
def test__ref_to_spec__with_ref_data(self): mock_self = Mock() mock_self.spec.components.schemas = {} schema = SomeSchema schema_name = create_schema_name(schema) # todo: update later to use spec.get_ref, check for openapi v3 data = {'$ref': f'#/definitions/{schema.__name__}'} RestfulPlugin._ref_to_spec(mock_self, data) mock_self.spec.components.schema.assert_called_once_with(schema_name, schema=schema) assert data['$ref'] == f'#/definitions/{schema_name}'
def __get_list_resource_fields_filters(self, resource) -> Generator[dict, None, None]: schema_name = create_schema_name(schema=resource.schema) for i_field_name, i_field in resource.schema._declared_fields.items(): i_field_spec = self.spec.components._schemas[schema_name]["properties"][i_field_name] if not isinstance(i_field, fields.Nested): if i_field_spec.get("type") == "object": # Skip filtering by dicts continue yield self.__get_parameter_for_not_nested(i_field_name, i_field_spec) elif getattr(i_field.schema.Meta, "filtering", False): yield from self.__get_parameters_for_nested_with_filtering(i_field, i_field_name)
def test_resolve_nested_schema__schema_name_in_spec(): schema = SomeSchema() name = create_schema_name(schema) mock_self = Mock() mock_self.refs = [] mock_self.spec.components._schemas = [name] result = resolve_nested_schema(mock_self, schema) assert result is mock_self.get_ref_dict.return_value mock_self.get_ref_dict.assert_called_once_with(schema) mock_self.spec.components.schema.assert_not_called()
def _add_definitions_in_spec(self, schema) -> None: """ Add schema in spec :param schema: schema marshmallow :return: """ name_schema = create_schema_name(schema) if name_schema not in self.spec_schemas and name_schema not in self.spec.components._schemas: self.spec_schemas[name_schema] = schema if APISPEC_VERSION_MAJOR < 1: self.spec.definition(name_schema, schema=schema) else: self.spec.components.schema(name_schema, schema=schema)
def test_resolve_nested_schema(): mock_self = Mock() mock_self.refs = [] mock_self.spec.components.schemas = {} schema = SomeSchema() result = resolve_nested_schema(mock_self, schema) assert result is mock_self.get_ref_dict.return_value mock_self.get_ref_dict.assert_called_once_with(schema) mock_self.spec.components.schema.assert_called_once_with( create_schema_name(schema), schema=schema)
def __get_parameters_for_nested_with_filtering(self, field, field_name) -> Generator[dict, None, None]: # Allow JSONB filtering field_schema_name = create_schema_name(schema=field.schema) component_schema = self.spec.components._schemas[field_schema_name] for i_field_jsonb_name, i_field_jsonb in field.schema._declared_fields.items(): i_field_jsonb_spec = component_schema["properties"][i_field_jsonb_name] if i_field_jsonb_spec.get("type") == "object": # Пропускаем создание фильтров для dict. Просто не понятно как фильтровать по таким # полям continue new_parameter = self.__get_parameter_for_nested_with_filtering( field_name, i_field_jsonb_name, i_field_jsonb_spec, ) yield new_parameter
def __get_parameters_for_declared_fields(self, resource, description) -> Generator[dict, None, None]: type_schemas = {resource.schema.Meta.type_} for i_field_name, i_field in resource.schema._declared_fields.items(): if not (isinstance(i_field, Relationship) and i_field.schema.Meta.type_ not in type_schemas): continue schema_name = create_schema_name(schema=i_field.schema) new_parameter = { "name": f"fields[{i_field.schema.Meta.type_}]", "in": "query", "type": "array", "required": False, "description": description.format(i_field.schema.Meta.type_), "items": { "type": "string", "enum": list(self.spec.components._schemas[schema_name]["properties"].keys()), }, } type_schemas.add(i_field.schema.Meta.type_) yield new_parameter
def test_after_route(self, mock_add_paths_in_spec, plugin): tag = 'SomeName' url = '/some_resource/<int:id>/' plugin.after_route(SomeResourceDetail, 'some_view_name', (url, ), tag=tag) schema_name = create_schema_name(SomeResourceDetail.schema) assert plugin.spec_schemas[schema_name] == SomeResourceDetail.schema assert plugin.spec.components._schemas[schema_name] assert plugin.spec_tag[tag] mock_add_paths_in_spec.assert_called_once_with( plugin, path=url, resource=SomeResourceDetail, default_parameters=None, default_schema=None, tag_name=tag)
def operation_helper(self, path=None, operations=None, **kwargs): """Если для query параметров указали схему marshmallow, то раскрываем её и вытаскиваем параметры первого уровня, без Nested""" resource = kwargs.get("resource", None) for m in getattr(resource, "methods", []): m = m.lower() f = getattr(resource, m) m_ops = load_yaml_from_docstring(f.__doc__) if m_ops: operations.update({m: m_ops}) self._ref_to_spec(m_ops) for method, val in operations.items(): for index, parametr in enumerate( val["parameters"] if "parameters" in val else []): if "in" in parametr and parametr[ "in"] == "query" and "schema" in parametr: name_schema = parametr["schema"]["$ref"].split("/")[-1] new_parameters = [] name_schema = create_schema_name(name_schema=name_schema) if name_schema in self.spec.components._schemas: for i_name, i_value in self.spec.components._schemas[ name_schema]["properties"].items(): new_parameter = { "name": i_name, "in": "query", "type": i_value.get("type"), "description": i_value.get("description", ""), } if "items" in i_value: new_items = { "type": i_value["items"].get("type"), } if "enum" in i_value["items"]: new_items["enum"] = i_value["items"][ "enum"] new_parameter.update({"items": new_items}) new_parameters.append(new_parameter) del val["parameters"][index] val["parameters"].extend(new_parameters)
def _ref_to_spec(self, data): """ Вытаскиваем из описания :param data: :return: """ if isinstance(data, list): for i_v in data: self._ref_to_spec(i_v) if isinstance(data, dict): for k, v in data.items(): if isinstance(v, list): for i_v in v: self._ref_to_spec(i_v) if isinstance(v, dict): self._ref_to_spec(v) elif k == "$ref": name_schema = v.split("/")[-1] schema = class_registry.get_class(name_schema) name_schema = create_schema_name(schema=schema) if name_schema not in self.spec.components._schemas: self.spec.components.schema(name_schema, schema=schema) data[k] = "/".join(v.split("/")[:-1] + [name_schema])
def test_invalid_schema_type(self): with pytest.raises(TypeError) as e: create_schema_name(schema=int) assert e.value.args[0] == INVALID_SCHEMA_TYPE
def test_no_schema_name_in_class_registry(self): schema_name = "FakeSchema" with pytest.raises(ValueError) as e: create_schema_name(name_schema=schema_name) assert e.value.args[0] == NO_SCHEMA_FOUND_ERROR.format( schema_name=schema_name)
def _add_paths_in_spec( self, path: str = "", resource: Any = None, tag_name: str = "", default_parameters: List = None, default_schema: Schema = None, **kwargs, ) -> None: operations = {} methods: Set[str] = {i_method.lower() for i_method in resource.methods} attributes = {} if resource.schema: attributes = self.spec.get_ref("schema", create_schema_name(resource.schema)) schema = (default_schema if default_schema else { "type": "object", "properties": { "data": { "type": "object", "properties": { "type": { "type": "string", }, "id": { "type": "string", }, "attributes": attributes, "relationships": { "type": "object", }, }, "required": [ "type", ], }, }, }) if "get" in methods: operations["get"] = self._get_operations_for_get( resource, tag_name, default_parameters) if "post" in methods: operations["post"] = self._get_operations_for_post( schema, tag_name, default_parameters) if "patch" in methods: operations["patch"] = self._get_operations_for_patch( schema, tag_name, default_parameters) if "delete" in methods: operations["delete"] = self._get_operations_for_delete( tag_name, default_parameters) rule = None for i_rule in self.app.url_map._rules: if i_rule.rule == path: rule = i_rule break if APISPEC_VERSION_MAJOR < 1: self.spec.add_path(path=path, operations=operations, rule=rule, resource=resource, **kwargs) else: self.spec.path(path=path, operations=operations, rule=rule, resource=resource, **kwargs)
def test_no_args(self): with pytest.raises(ValueError) as e: create_schema_name() assert e.value.args[0] == NO_ARGS_ERROR