Skip to content
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

Fix missing Field.default when it's value is None in openapi spec #189

Merged
merged 5 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions flask_openapi3/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,21 +158,26 @@ def _collect_openapi_info(
)

# Set external docs
operation.externalDocs = external_docs
if external_docs:
operation.externalDocs = external_docs

# Unique string used to identify the operation.
operation.operationId = operation_id or self.operation_id_callback(
name=self.name, path=rule, method=method
)

# Only set `deprecated` if True, otherwise leave it as None
operation.deprecated = deprecated
if deprecated is not None:
operation.deprecated = deprecated

# Add security
operation.security = (security or []) + self.abp_security or None
_security = (security or []) + self.abp_security or None
if _security:
operation.security = _security

# Add servers
operation.servers = servers
if servers:
operation.servers = servers

# Store tags
tags = (tags or []) + self.abp_tags
Expand Down
29 changes: 19 additions & 10 deletions flask_openapi3/openapi.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# @Author : llc
# @Time : 2021/4/30 14:25
import json
import os
import re
import sys
Expand Down Expand Up @@ -219,12 +218,18 @@ def api_doc(self) -> Dict:
spec = APISpec(
openapi=self.openapi_version,
info=self.info,
servers=self.severs,
paths=self.paths,
externalDocs=self.external_docs
paths=self.paths
)

if self.severs:
spec.servers = self.severs

if self.external_docs:
spec.externalDocs = self.external_docs

# Set tags
spec.tags = self.tags or None
if self.tags:
spec.tags = self.tags

# Add ValidationErrorModel to components schemas
schema = get_model_schema(self.validation_error_model)
Expand All @@ -241,7 +246,7 @@ def api_doc(self) -> Dict:
spec.components = self.components

# Convert spec to JSON
self.spec_json = json.loads(spec.model_dump_json(by_alias=True, exclude_none=True, warnings=False))
self.spec_json = spec.model_dump(mode="json", by_alias=True, exclude_unset=True, warnings=False)

# Update with OpenAPI extensions
self.spec_json.update(**self.openapi_extensions)
Expand Down Expand Up @@ -385,21 +390,25 @@ def _collect_openapi_info(
openapi_extensions=openapi_extensions
)
# Set external docs
operation.externalDocs = external_docs
if external_docs:
operation.externalDocs = external_docs

# Unique string used to identify the operation.
operation.operationId = operation_id or self.operation_id_callback(
name=func.__name__, path=rule, method=method
)

# Only set `deprecated` if True, otherwise leave it as None
operation.deprecated = deprecated
if deprecated is not None:
operation.deprecated = deprecated

# Add security
operation.security = security
if security:
operation.security = security

# Add servers
operation.servers = servers
if servers:
operation.servers = servers

# Store tags
parse_and_store_tags(tags or [], self.tags, self.tag_names, operation)
Expand Down
142 changes: 86 additions & 56 deletions flask_openapi3/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,25 @@ def get_operation(
doc = inspect.getdoc(func) or ""
doc = doc.strip()
lines = doc.split("\n")
doc_summary = lines[0] or None
doc_summary = lines[0]

# Determine the summary and description based on provided arguments or docstring
if summary is None:
doc_description = lines[0] if len(lines) == 0 else "</br>".join(lines[1:]) or None
doc_description = lines[0] if len(lines) == 0 else "</br>".join(lines[1:])
else:
doc_description = "</br>".join(lines) or None
doc_description = "</br>".join(lines)

summary = summary or doc_summary
description = description or doc_description

# Create the operation dictionary with summary and description
operation_dict = dict(
summary=summary or doc_summary,
description=description or doc_description
)
operation_dict = {"deprecated": False}
ddorian marked this conversation as resolved.
Show resolved Hide resolved

if summary:
operation_dict["summary"] = summary # type: ignore

if description:
operation_dict["description"] = description # type: ignore

# Add any additional openapi_extensions to the operation dictionary
operation_dict.update(openapi_extensions or {})
Expand Down Expand Up @@ -136,16 +142,18 @@ def parse_header(header: Type[BaseModel]) -> Tuple[List[Parameter], dict]:
data = {
"name": name,
"in": ParameterInType.HEADER,
"description": value.get("description"),
"required": name in schema.get("required", []),
"schema": Schema(**value)
}
# Parse extra values
data.update({
"deprecated": value.get("deprecated"),
"example": value.get("example"),
"examples": value.get("examples"),
})
if "description" in value.keys():
data["description"] = value.get("description")
if "deprecated" in value.keys():
data["deprecated"] = value.get("deprecated")
if "example" in value.keys():
data["example"] = value.get("example")
if "examples" in value.keys():
data["examples"] = value.get("examples")
parameters.append(Parameter(**data))

# Parse definitions
Expand All @@ -167,16 +175,18 @@ def parse_cookie(cookie: Type[BaseModel]) -> Tuple[List[Parameter], dict]:
data = {
"name": name,
"in": ParameterInType.COOKIE,
"description": value.get("description"),
"required": name in schema.get("required", []),
"schema": Schema(**value)
}
# Parse extra values
data.update({
"deprecated": value.get("deprecated"),
"example": value.get("example"),
"examples": value.get("examples"),
})
if "description" in value.keys():
data["description"] = value.get("description")
if "deprecated" in value.keys():
data["deprecated"] = value.get("deprecated")
if "example" in value.keys():
data["example"] = value.get("example")
if "examples" in value.keys():
data["examples"] = value.get("examples")
parameters.append(Parameter(**data))

# Parse definitions
Expand All @@ -198,16 +208,18 @@ def parse_path(path: Type[BaseModel]) -> Tuple[List[Parameter], dict]:
data = {
"name": name,
"in": ParameterInType.PATH,
"description": value.get("description"),
"required": True,
"schema": Schema(**value)
}
# Parse extra values
data.update({
"deprecated": value.get("deprecated"),
"example": value.get("example"),
"examples": value.get("examples"),
})
if "description" in value.keys():
data["description"] = value.get("description")
if "deprecated" in value.keys():
data["deprecated"] = value.get("deprecated")
if "example" in value.keys():
data["example"] = value.get("example")
if "examples" in value.keys():
data["examples"] = value.get("examples")
parameters.append(Parameter(**data))

# Parse definitions
Expand All @@ -229,16 +241,18 @@ def parse_query(query: Type[BaseModel]) -> Tuple[List[Parameter], dict]:
data = {
"name": name,
"in": ParameterInType.QUERY,
"description": value.get("description"),
"required": name in schema.get("required", []),
"schema": Schema(**value)
}
# Parse extra values
data.update({
"deprecated": value.get("deprecated"),
"example": value.get("example"),
"examples": value.get("examples"),
})
if "description" in value.keys():
data["description"] = value.get("description")
if "deprecated" in value.keys():
data["deprecated"] = value.get("deprecated")
if "example" in value.keys():
data["example"] = value.get("example")
if "examples" in value.keys():
data["examples"] = value.get("examples")
parameters.append(Parameter(**data))

# Parse definitions
Expand Down Expand Up @@ -269,9 +283,10 @@ def parse_form(
content = {
"multipart/form-data": MediaType(
schema=Schema(**{"$ref": f"{OPENAPI3_REF_PREFIX}/{title}"}),
encoding=encoding or None
)
}
if encoding:
content["multipart/form-data"].encoding = encoding

# Parse definitions
definitions = schema.get("$defs", {})
Expand Down Expand Up @@ -332,18 +347,24 @@ def get_responses(
)})

model_config: DefaultDict[str, Any] = response.model_config # type: ignore
openapi_extra = model_config.get("openapi_extra")
openapi_extra = model_config.get("openapi_extra", {})
if openapi_extra:
openapi_extra_keys = openapi_extra.keys()
# Add additional information from model_config to the response
_responses[key].description = openapi_extra.get("description")
_responses[key].headers = openapi_extra.get("headers")
_responses[key].links = openapi_extra.get("links")
if "description" in openapi_extra_keys:
_responses[key].description = openapi_extra.get("description")
if "headers" in openapi_extra_keys:
_responses[key].headers = openapi_extra.get("headers")
if "links" in openapi_extra_keys:
_responses[key].links = openapi_extra.get("links")
_content = _responses[key].content
if _content is not None:
_content["application/json"].example = openapi_extra.get("example")
_content["application/json"].examples = openapi_extra.get("examples")
_content["application/json"].encoding = openapi_extra.get("encoding")
_content.update(openapi_extra.get("content", {}))
if "example" in openapi_extra_keys:
_content["application/json"].example = openapi_extra.get("example") # type: ignore
if "examples" in openapi_extra_keys:
_content["application/json"].examples = openapi_extra.get("examples") # type: ignore
if "encoding" in openapi_extra_keys:
_content["application/json"].encoding = openapi_extra.get("encoding") # type: ignore
_content.update(openapi_extra.get("content", {})) # type: ignore

_schemas[name] = Schema(**schema)
definitions = schema.get("$defs")
Expand Down Expand Up @@ -382,8 +403,8 @@ def parse_and_store_tags(
old_tags.append(tag)

# Set the tags attribute of the operation object to a list of unique tag names from new_tags
# If the resulting list is empty, set it to None
operation.tags = list(set([tag.name for tag in new_tags])) or None
# If the resulting list is empty, set it to ["default"]
operation.tags = list(set([tag.name for tag in new_tags])) or ["default"]


def parse_parameters(
Expand Down Expand Up @@ -415,7 +436,7 @@ def parse_parameters(

# If operation is None, initialize it as an Operation object
if operation is None:
operation = Operation()
operation = Operation(deprecated=False)

# Get the type hints from the function
annotations = get_type_hints(func)
Expand Down Expand Up @@ -458,29 +479,38 @@ def parse_parameters(
if form:
_content, _components_schemas = parse_form(form)
components_schemas.update(**_components_schemas)
request_body = RequestBody(content=_content)
request_body = RequestBody(content=_content, required=True)
model_config: DefaultDict[str, Any] = form.model_config # type: ignore
openapi_extra = model_config.get("openapi_extra")
openapi_extra = model_config.get("openapi_extra", {})
if openapi_extra:
request_body.description = openapi_extra.get("description")
request_body.content["multipart/form-data"].example = openapi_extra.get("example")
request_body.content["multipart/form-data"].examples = openapi_extra.get("examples")
if openapi_extra.get("encoding"):
openapi_extra_keys = openapi_extra.keys()
if "description" in openapi_extra_keys:
request_body.description = openapi_extra.get("description")
if "example" in openapi_extra_keys:
request_body.content["multipart/form-data"].example = openapi_extra.get("example")
if "examples" in openapi_extra_keys:
request_body.content["multipart/form-data"].examples = openapi_extra.get("examples")
if "encoding" in openapi_extra_keys:
request_body.content["multipart/form-data"].encoding = openapi_extra.get("encoding")
operation.requestBody = request_body

if body:
_content, _components_schemas = parse_body(body)
components_schemas.update(**_components_schemas)
request_body = RequestBody(content=_content)
request_body = RequestBody(content=_content, required=True)
model_config: DefaultDict[str, Any] = body.model_config # type: ignore
openapi_extra = model_config.get("openapi_extra")
openapi_extra = model_config.get("openapi_extra", {})
if openapi_extra:
request_body.description = openapi_extra.get("description")
openapi_extra_keys = openapi_extra.keys()
if "description" in openapi_extra_keys:
request_body.description = openapi_extra.get("description")
request_body.required = openapi_extra.get("required", True)
request_body.content["application/json"].example = openapi_extra.get("example")
request_body.content["application/json"].examples = openapi_extra.get("examples")
request_body.content["application/json"].encoding = openapi_extra.get("encoding")
if "example" in openapi_extra_keys:
request_body.content["application/json"].example = openapi_extra.get("example")
if "examples" in openapi_extra_keys:
request_body.content["application/json"].examples = openapi_extra.get("examples")
if "encoding" in openapi_extra_keys:
request_body.content["application/json"].encoding = openapi_extra.get("encoding")
operation.requestBody = request_body

if raw:
Expand Down
16 changes: 11 additions & 5 deletions flask_openapi3/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,25 @@ def decorator(func):
)

# Set external docs
operation.externalDocs = external_docs
if external_docs:
operation.externalDocs = external_docs

# Unique string used to identify the operation.
operation.operationId = operation_id
if operation_id:
operation.operationId = operation_id

# Only set `deprecated` if True, otherwise leave it as None
operation.deprecated = deprecated
if deprecated is not None:
operation.deprecated = deprecated

# Add security
operation.security = security + self.view_security or None
_security = (security or []) + self.view_security or None
if _security:
operation.security = _security

# Add servers
operation.servers = servers
if servers:
operation.servers = servers

# Store tags
parse_and_store_tags(tags, self.tags, self.tag_names, operation)
Expand Down
Loading