<gh_stars>10-100
"""Validate that the Python code can be generated according to the schema."""
from typing import (  # pylint: disable=unused-import
    Dict, List, Mapping, MutableMapping)

import mapry
import mapry.py.naming
import mapry.validation

_KEYWORDS = {
    'False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class',
    'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for',
    'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not',
    'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'
}


class _Attribute:
    """Represent a Python attribute of a composite."""

    def __init__(self, name: str, ref: str) -> None:
        """
        Initialize the Python attribute of a composite with the given values.

        :param name: name of the composite attribute
        :param ref:
            reference path in the mapry schema corresponding to this attribute
        """
        self.name = name
        self.ref = ref


def _properties_to_attributes(properties: Mapping[str, mapry.Property]
                              ) -> List[_Attribute]:
    """
    Convert properties of a mapry composite into Python attributes.

    :param properties: of a composite
    :return: list of attributes.
    """
    # yapf: disable
    return [
        _Attribute(
            name=mapry.py.naming.as_attribute(identifier=prop.name),
            ref=prop.ref)
        for prop in properties.values()
    ]
    # yapf: enable


def _validate_attributes(attributes: List[_Attribute]
                         ) -> List[mapry.validation.SchemaError]:
    """
    Validate the attributes of a composite.

    Notably, check that there are no duplicates and no reserved words.

    :param attributes: Python attributes corresponding to the composite
    :return: list of errors, or an empty list if no errors
    """
    errs = []  # type: List[mapry.validation.SchemaError]

    name_to_attributes = dict()  # type: Dict[str, _Attribute]

    for attribute in attributes:
        if attribute.name in _KEYWORDS:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "is reserved in Python").format(attribute.name),
                    ref=attribute.ref))

        if attribute.name in name_to_attributes:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "conflicts another attribute ({})").format(
                            attribute.name,
                            name_to_attributes[attribute.name].ref),
                    ref=attribute.ref))
        else:
            name_to_attributes[attribute.name] = attribute

    return errs


def _validate_class_attributes(a_class: mapry.Class
                               ) -> List[mapry.validation.SchemaError]:
    """
    Check that the properties can be translated to Python attributes.

    Notably, we check for conflicts with keywords and
    reserved attribute names (such as "id").

    :param a_class: definition of a mapry class
    :return: list of errors, or an empty list if no errors
    """
    errs = []  # type: List[mapry.validation.SchemaError]

    attributes = _properties_to_attributes(properties=a_class.properties)
    errs.extend(_validate_attributes(attributes=attributes))

    for attribute in attributes:
        if attribute.name == 'id':
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "is reserved for class identifiers and "
                        "used by the autogenerated code").format(
                            attribute.name),
                    ref=attribute.ref))
    return errs


def _validate_graph_attributes(graph: mapry.Graph
                               ) -> List[mapry.validation.SchemaError]:
    """
    Validate that the object graph can be translated into a Python class.

    :param graph: mapry definition of the object graph
    :return: list of errors, or an empty list if no errors
    """
    errs = []  # type: List[mapry.validation.SchemaError]

    attributes = _properties_to_attributes(properties=graph.properties)
    errs.extend(_validate_attributes(attributes=attributes))

    # yapf: disable
    property_attribute_map = {
        attribute.name: attribute
        for attribute in attributes
    }
    # yapf: enable

    registry_attribute_map = dict()  # type: MutableMapping[str, mapry.Class]

    for cls in graph.classes.values():
        plural_attribute = mapry.py.naming.as_attribute(identifier=cls.plural)

        if plural_attribute in _KEYWORDS:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "corresponding to the registry of the class {!r} "
                        "in the object graph is reserved in Python").format(
                            plural_attribute, cls.name),
                    ref=cls.ref))

        if plural_attribute in property_attribute_map:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "corresponding to the registry of the class {!r} "
                        "in the object graph conflicts with "
                        "another Python attribute corresponding "
                        "to a property of the object graph ({})").format(
                            plural_attribute, cls.name,
                            property_attribute_map[plural_attribute].ref),
                    ref=cls.ref))

        if plural_attribute in registry_attribute_map:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "corresponding to the registry of the class {!r} "
                        "in the object graph conflicts "
                        "with the Python attribute corresponding to "
                        "another registry of the class {!r} ({})").format(
                            plural_attribute, cls.name,
                            registry_attribute_map[plural_attribute].name,
                            registry_attribute_map[plural_attribute].ref),
                    ref=cls.ref))
        else:
            registry_attribute_map[plural_attribute] = cls

    return errs


def validate_schema(schema: mapry.Schema) -> List[mapry.validation.SchemaError]:
    """
    Validate that we can generate the Python code for the given mapry schema.

    :param schema: mapry schema
    :return: list of errors, or an empty list if no errors
    """
    name_to_composite = dict()  # type: Dict[str, mapry.Composite]

    errs = []  # type: List[mapry.validation.SchemaError]

    graph_name = mapry.py.naming.as_composite(identifier=schema.graph.name)
    if graph_name in _KEYWORDS:
        errs.append(
            mapry.validation.SchemaError(
                message=(
                    "Python identifier ({!r}) "
                    "corresponding to the name of the object graph ({!r}) "
                    "is reserved in Python").format(
                        graph_name, schema.graph.name),
                ref=schema.graph.ref))

        name_to_composite[graph_name] = schema.graph

    for cls in schema.graph.classes.values():
        cls_name = mapry.py.naming.as_composite(identifier=cls.name)

        # Check that the name is not a keyword
        if cls_name in _KEYWORDS:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "Python identifier ({!r}) corresponding to "
                        "the name of the class ({!r}) "
                        "is reserved in Python").format(cls_name, cls.name),
                    ref=cls.ref))

        # Check that the name is not a duplicate
        if cls_name in name_to_composite:
            conflict_composite = name_to_composite[cls_name]
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "Class {!r} and the composite {!r} ({}) "
                        "have conflicting Python identifiers: {!r}".format(
                            cls.name, conflict_composite.name,
                            conflict_composite.ref, cls_name)),
                    ref=cls.ref))
        else:
            name_to_composite[cls_name] = cls

        # Check that the attributes are valid
        errs.extend(_validate_class_attributes(a_class=cls))

    for embed in schema.graph.embeds.values():
        embed_name = mapry.py.naming.as_composite(identifier=embed.name)
        if embed_name in _KEYWORDS:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "Python identifier ({!r}) corresponding to "
                        "the name of the embeddable structure ({!r}) "
                        "is reserved in Python").format(embed_name, embed.name),
                    ref=embed.ref))

        if embed_name in name_to_composite:
            conflict_composite = name_to_composite[embed_name]
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The embeddable structure {!r} and "
                        "the composite {!r} ({}) "
                        "have conflicting Python identifiers: {!r}".format(
                            embed.name, conflict_composite.name,
                            conflict_composite.ref, embed_name)),
                    ref=embed.ref))
        else:
            name_to_composite[embed_name] = embed

        # Check that the attributes are valid
        errs.extend(
            _validate_attributes(
                _properties_to_attributes(properties=embed.properties)))

    # Check that the attributes of the object graph are valid
    # (including the class registries)
    errs.extend(_validate_graph_attributes(graph=schema.graph))

    return errs

"""Validate that the Python code can be generated according to the schema."""
from typing import (  # pylint: disable=unused-import
    Dict, List, Mapping, MutableMapping)

import mapry
import mapry.py.naming
import mapry.validation

_KEYWORDS = {
    'False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class',
    'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for',
    'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not',
    'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'
}


class _Attribute:
    """Represent a Python attribute of a composite."""

    def __init__(self, name: str, ref: str) -> None:
        """
        Initialize the Python attribute of a composite with the given values.

        :param name: name of the composite attribute
        :param ref:
            reference path in the mapry schema corresponding to this attribute
        """
        self.name = name
        self.ref = ref


def _properties_to_attributes(properties: Mapping[str, mapry.Property]
                              ) -> List[_Attribute]:
    """
    Convert properties of a mapry composite into Python attributes.

    :param properties: of a composite
    :return: list of attributes.
    """
    # yapf: disable
    return [
        _Attribute(
            name=mapry.py.naming.as_attribute(identifier=prop.name),
            ref=prop.ref)
        for prop in properties.values()
    ]
    # yapf: enable


def _validate_attributes(attributes: List[_Attribute]
                         ) -> List[mapry.validation.SchemaError]:
    """
    Validate the attributes of a composite.

    Notably, check that there are no duplicates and no reserved words.

    :param attributes: Python attributes corresponding to the composite
    :return: list of errors, or an empty list if no errors
    """
    errs = []  # type: List[mapry.validation.SchemaError]

    name_to_attributes = dict()  # type: Dict[str, _Attribute]

    for attribute in attributes:
        if attribute.name in _KEYWORDS:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "is reserved in Python").format(attribute.name),
                    ref=attribute.ref))

        if attribute.name in name_to_attributes:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "conflicts another attribute ({})").format(
                            attribute.name,
                            name_to_attributes[attribute.name].ref),
                    ref=attribute.ref))
        else:
            name_to_attributes[attribute.name] = attribute

    return errs


def _validate_class_attributes(a_class: mapry.Class
                               ) -> List[mapry.validation.SchemaError]:
    """
    Check that the properties can be translated to Python attributes.

    Notably, we check for conflicts with keywords and
    reserved attribute names (such as "id").

    :param a_class: definition of a mapry class
    :return: list of errors, or an empty list if no errors
    """
    errs = []  # type: List[mapry.validation.SchemaError]

    attributes = _properties_to_attributes(properties=a_class.properties)
    errs.extend(_validate_attributes(attributes=attributes))

    for attribute in attributes:
        if attribute.name == 'id':
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "is reserved for class identifiers and "
                        "used by the autogenerated code").format(
                            attribute.name),
                    ref=attribute.ref))
    return errs


def _validate_graph_attributes(graph: mapry.Graph
                               ) -> List[mapry.validation.SchemaError]:
    """
    Validate that the object graph can be translated into a Python class.

    :param graph: mapry definition of the object graph
    :return: list of errors, or an empty list if no errors
    """
    errs = []  # type: List[mapry.validation.SchemaError]

    attributes = _properties_to_attributes(properties=graph.properties)
    errs.extend(_validate_attributes(attributes=attributes))

    # yapf: disable
    property_attribute_map = {
        attribute.name: attribute
        for attribute in attributes
    }
    # yapf: enable

    registry_attribute_map = dict()  # type: MutableMapping[str, mapry.Class]

    for cls in graph.classes.values():
        plural_attribute = mapry.py.naming.as_attribute(identifier=cls.plural)

        if plural_attribute in _KEYWORDS:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "corresponding to the registry of the class {!r} "
                        "in the object graph is reserved in Python").format(
                            plural_attribute, cls.name),
                    ref=cls.ref))

        if plural_attribute in property_attribute_map:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "corresponding to the registry of the class {!r} "
                        "in the object graph conflicts with "
                        "another Python attribute corresponding "
                        "to a property of the object graph ({})").format(
                            plural_attribute, cls.name,
                            property_attribute_map[plural_attribute].ref),
                    ref=cls.ref))

        if plural_attribute in registry_attribute_map:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The Python attribute identifier {!r} "
                        "corresponding to the registry of the class {!r} "
                        "in the object graph conflicts "
                        "with the Python attribute corresponding to "
                        "another registry of the class {!r} ({})").format(
                            plural_attribute, cls.name,
                            registry_attribute_map[plural_attribute].name,
                            registry_attribute_map[plural_attribute].ref),
                    ref=cls.ref))
        else:
            registry_attribute_map[plural_attribute] = cls

    return errs


def validate_schema(schema: mapry.Schema) -> List[mapry.validation.SchemaError]:
    """
    Validate that we can generate the Python code for the given mapry schema.

    :param schema: mapry schema
    :return: list of errors, or an empty list if no errors
    """
    name_to_composite = dict()  # type: Dict[str, mapry.Composite]

    errs = []  # type: List[mapry.validation.SchemaError]

    graph_name = mapry.py.naming.as_composite(identifier=schema.graph.name)
    if graph_name in _KEYWORDS:
        errs.append(
            mapry.validation.SchemaError(
                message=(
                    "Python identifier ({!r}) "
                    "corresponding to the name of the object graph ({!r}) "
                    "is reserved in Python").format(
                        graph_name, schema.graph.name),
                ref=schema.graph.ref))

        name_to_composite[graph_name] = schema.graph

    for cls in schema.graph.classes.values():
        cls_name = mapry.py.naming.as_composite(identifier=cls.name)

        # Check that the name is not a keyword
        if cls_name in _KEYWORDS:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "Python identifier ({!r}) corresponding to "
                        "the name of the class ({!r}) "
                        "is reserved in Python").format(cls_name, cls.name),
                    ref=cls.ref))

        # Check that the name is not a duplicate
        if cls_name in name_to_composite:
            conflict_composite = name_to_composite[cls_name]
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "Class {!r} and the composite {!r} ({}) "
                        "have conflicting Python identifiers: {!r}".format(
                            cls.name, conflict_composite.name,
                            conflict_composite.ref, cls_name)),
                    ref=cls.ref))
        else:
            name_to_composite[cls_name] = cls

        # Check that the attributes are valid
        errs.extend(_validate_class_attributes(a_class=cls))

    for embed in schema.graph.embeds.values():
        embed_name = mapry.py.naming.as_composite(identifier=embed.name)
        if embed_name in _KEYWORDS:
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "Python identifier ({!r}) corresponding to "
                        "the name of the embeddable structure ({!r}) "
                        "is reserved in Python").format(embed_name, embed.name),
                    ref=embed.ref))

        if embed_name in name_to_composite:
            conflict_composite = name_to_composite[embed_name]
            errs.append(
                mapry.validation.SchemaError(
                    message=(
                        "The embeddable structure {!r} and "
                        "the composite {!r} ({}) "
                        "have conflicting Python identifiers: {!r}".format(
                            embed.name, conflict_composite.name,
                            conflict_composite.ref, embed_name)),
                    ref=embed.ref))
        else:
            name_to_composite[embed_name] = embed

        # Check that the attributes are valid
        errs.extend(
            _validate_attributes(
                _properties_to_attributes(properties=embed.properties)))

    # Check that the attributes of the object graph are valid
    # (including the class registries)
    errs.extend(_validate_graph_attributes(graph=schema.graph))

    return errs
Validate that the Python code can be generated according to the schema. # pylint: disable=unused-import Represent a Python attribute of a composite. Initialize the Python attribute of a composite with the given values.          :param name: name of the composite attribute         :param ref:             reference path in the mapry schema corresponding to this attribute Convert properties of a mapry composite into Python attributes.      :param properties: of a composite     :return: list of attributes. # yapf: disable # yapf: enable Validate the attributes of a composite.      Notably, check that there are no duplicates and no reserved words.      :param attributes: Python attributes corresponding to the composite     :return: list of errors, or an empty list if no errors # type: List[mapry.validation.SchemaError] # type: Dict[str, _Attribute] Check that the properties can be translated to Python attributes.      Notably, we check for conflicts with keywords and     reserved attribute names (such as "id").      :param a_class: definition of a mapry class     :return: list of errors, or an empty list if no errors # type: List[mapry.validation.SchemaError] Validate that the object graph can be translated into a Python class.      :param graph: mapry definition of the object graph     :return: list of errors, or an empty list if no errors # type: List[mapry.validation.SchemaError] # yapf: disable # yapf: enable # type: MutableMapping[str, mapry.Class] Validate that we can generate the Python code for the given mapry schema.      :param schema: mapry schema     :return: list of errors, or an empty list if no errors # type: Dict[str, mapry.Composite] # type: List[mapry.validation.SchemaError] # Check that the name is not a keyword # Check that the name is not a duplicate # Check that the attributes are valid # Check that the attributes are valid # Check that the attributes of the object graph are valid # (including the class registries)