Fix gate following strange PyYAML 4.1 behavior
This patchset adds a dict() cast as a workaround the fact that PyYAML 4.1 recently changed yaml.dump to yaml.safe_dump, compelling developers to use yaml.danger_dump to achieve the previous behavior of yaml.dump [0]. However, yaml.danger_dump should not be used and this technically corrects antecedent use of yaml.dump by introducing a recursive function that ensures the dictionary prior to being dumped is compatible with yaml.safe_dump. Such a function is needed because yaml.safe_dump rejects serialization of Deckhand's DocumentDict dictionary wrapper helper -- even though it is a subclass of a dict. Thus, the recursive function simply casts each instance of DocumentDict into a dictionary. [0] https://stackoverflow.com/questions/51053903/new-pyyaml-version-breaks-on-most-custom-python-objects-representererror Change-Id: I67966b45e0865864bd5e6bb4578548769fc13eeb
This commit is contained in:
parent
41b889c4f9
commit
807990a099
@ -240,14 +240,18 @@ class DataSchemaValidator(GenericValidator):
|
|||||||
parent_path_to_error_in_document = '.'.join(
|
parent_path_to_error_in_document = '.'.join(
|
||||||
path_to_error_in_document.split('.')[:-1]) or '.'
|
path_to_error_in_document.split('.')[:-1]) or '.'
|
||||||
try:
|
try:
|
||||||
# NOTE(fmontei): Because validation is performed on fully rendered
|
# NOTE(felipemonteiro): Because validation is performed on fully
|
||||||
# documents, it is necessary to omit the parts of the data section
|
# rendered documents, it is necessary to omit the parts of the data
|
||||||
# where substitution may have occurred to avoid exposing any
|
# section where substitution may have occurred to avoid exposing
|
||||||
# secrets. While this may make debugging a few validation failures
|
# any secrets. While this may make debugging a few validation
|
||||||
# more difficult, it is a necessary evil.
|
# failures more difficult, it is a necessary evil.
|
||||||
sanitized_document = (
|
sanitized_document = (
|
||||||
SecretsSubstitution.sanitize_potential_secrets(
|
SecretsSubstitution.sanitize_potential_secrets(
|
||||||
error, document))
|
error, document))
|
||||||
|
# This incurs some degree of overhead as caching here won't make
|
||||||
|
# a big difference as we are not parsing commonly referenced
|
||||||
|
# JSON paths -- but this branch is only hit during error handling
|
||||||
|
# so this should be OK.
|
||||||
parent_error_section = utils.jsonpath_parse(
|
parent_error_section = utils.jsonpath_parse(
|
||||||
sanitized_document, parent_path_to_error_in_document)
|
sanitized_document, parent_path_to_error_in_document)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -12,11 +12,12 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import yaml
|
import collections
|
||||||
|
|
||||||
import falcon
|
import falcon
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import six
|
import six
|
||||||
|
import yaml
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -29,6 +30,34 @@ def get_version_from_request(req):
|
|||||||
return 'N/A'
|
return 'N/A'
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_yaml_dump(error_response):
|
||||||
|
"""Cast every instance of ``DocumentDict`` into a dictionary for
|
||||||
|
compatibility with ``yaml.safe_dump``.
|
||||||
|
|
||||||
|
This should only be called for error formatting.
|
||||||
|
"""
|
||||||
|
is_dict_sublcass = (
|
||||||
|
lambda v: type(v) is not dict and issubclass(v.__class__, dict)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _to_dict(value, parent):
|
||||||
|
if isinstance(value, (list, tuple, set)):
|
||||||
|
for v in value:
|
||||||
|
_to_dict(v, value)
|
||||||
|
elif isinstance(value, collections.Mapping):
|
||||||
|
for v in value.values():
|
||||||
|
_to_dict(v, value)
|
||||||
|
else:
|
||||||
|
if isinstance(parent, (list, tuple, set)):
|
||||||
|
parent[parent.index(value)] = (
|
||||||
|
dict(value) if is_dict_sublcass(value) else value)
|
||||||
|
elif isinstance(parent, dict):
|
||||||
|
for k, v in parent.items():
|
||||||
|
parent[k] = dict(v) if is_dict_sublcass(v) else v
|
||||||
|
_to_dict(error_response, None)
|
||||||
|
return yaml.safe_dump(error_response)
|
||||||
|
|
||||||
|
|
||||||
def format_error_resp(req,
|
def format_error_resp(req,
|
||||||
resp,
|
resp,
|
||||||
status_code=falcon.HTTP_500,
|
status_code=falcon.HTTP_500,
|
||||||
@ -102,8 +131,7 @@ def format_error_resp(req,
|
|||||||
'retry': True if status_code is falcon.HTTP_500 else False
|
'retry': True if status_code is falcon.HTTP_500 else False
|
||||||
}
|
}
|
||||||
|
|
||||||
# Don't use yaml.safe_dump to handle unicode correctly.
|
resp.body = _safe_yaml_dump(error_response)
|
||||||
resp.body = yaml.dump(error_response)
|
|
||||||
resp.status = status_code
|
resp.status = status_code
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user