apidoc/factory/source/rootDto.py
class Hydrator():

    def __init__(self, version, versions, types):
        self.version_name = version.name
        self.versions = versions
        self.types = types

    def hydrate_method(self, root_dto, root_source, method):
        categories = dict((category.name, category) for category in root_dto.method_categories)
        if method.category not in categories.keys():
            if method.category in root_source.categories.keys():
                category = MethodCategory(root_source.categories[method.category])
            else:
                category = MethodCategory(Category(method.category))
            root_dto.method_categories.append(category)
        else:
            category = categories[method.category]

        methods = dict((method.name, method) for method in category.methods)
        if method.name in methods.keys():
            method_dto = methods[method.name]
            method_dto.changes_status[self.version_name] = MultiVersion.Changes.none
        else:
            method_dto = Method(method)
            category.methods.append(method_dto)
            method_dto.changes_status[self.version_name] = MultiVersion.Changes.new

        method_dto.versions.append(self.version_name)

        for parameter in method.request_parameters.values():
            parameter.position = method.full_uri.find("{%s}" % parameter.name)
        request_parameters = [RequestParameter(parameter) for parameter in method.request_parameters.values()]
        request_headers = [Parameter(parameter) for parameter in method.request_headers.values()]
        response_codes = [ResponseCode(parameter) for parameter in method.response_codes]

        changes = 0
        changes += self.hydrate_value(method_dto.description, method.description)
        changes += self.hydrate_value(method_dto.full_uri, method.full_uri)
        changes += self.hydrate_value(method_dto.absolute_uri, method.absolute_uri)
        changes += self.hydrate_value(method_dto.code, method.code)

        changes += self.hydrate_list(method_dto.request_headers, sorted(request_headers))
        changes += self.hydrate_list(method_dto.request_parameters, sorted(request_parameters))
        changes += self.hydrate_list(method_dto.response_codes, sorted(response_codes))

        changes += self.hydrate_object(method_dto.request_body, method.request_body)
        changes += self.hydrate_object(method_dto.response_body, method.response_body)

        if changes > 0 and method_dto.changes_status[self.version_name] is MultiVersion.Changes.none:
            method_dto.changes_status[self.version_name] = MultiVersion.Changes.updated

        method_dto.samples[self.version_name] = MethodSample(method)

    def hydrate_type(self, root_dto, root, type):
        categories = dict((category.name, category) for category in root_dto.type_categories)
        if type.category not in categories.keys():
            if type.category in root.categories.keys():
                category = TypeCategory(root.categories[type.category])
            else:
                category = TypeCategory(Category(type.category))
            root_dto.type_categories.append(category)
        else:
            category = categories[type.category]

        types = dict((type.name, type) for type in category.types)
        if type.name in types.keys():
            type_dto = types[type.name]
            type_dto.changes_status[self.version_name] = MultiVersion.Changes.none
        else:
            type_dto = Type(type)
            category.types.append(type_dto)
            type_dto.changes_status[self.version_name] = MultiVersion.Changes.new

        type_dto.versions.append(self.version_name)

        changes = 0
        changes += self.hydrate_value(type_dto.description, type.description)
        changes += self.hydrate_value(type_dto.format.pretty, type.format.pretty)
        changes += self.hydrate_value(type_dto.format.advanced, type.format.advanced)
        changes += self.hydrate_object(type_dto.item, type.item)

        if changes > 0 and type_dto.changes_status[self.version_name] is MultiVersion.Changes.none:
            type_dto.changes_status[self.version_name] = MultiVersion.Changes.updated

        type_dto.samples[self.version_name] = TypeSample(type)

    def hydrate_value(self, dto_value, source_value):
        if source_value is None:
            return 0

        changes = 0
        find = False
        for versioned_value in dto_value:
            if versioned_value.value == source_value:
                versioned_value.versions.append(self.version_name)
                find = True
                changes += 1 if self.has_changed(versioned_value) else 0

        if not find:
            dto_value.append(MultiVersion(source_value, self.version_name))
            return 1

        return changes

    def hydrate_list(self, dto_list, source_list):
        changes = 0
        for source_value in source_list:
            find = False
            for versioned_value in dto_list:
                if versioned_value.value == source_value:
                    versioned_value.versions.append(self.version_name)
                    find = True
                    changes += 1 if self.has_changed(versioned_value) else 0

            if not find:
                dto_list.append(MultiVersion(source_value, self.version_name))
                changes += 1

        return changes

    def hydrate_object(self, dto_object, source_object):
        if source_object is None:
            return 0

        source_dto = Object.factory(source_object)

        changes = 0
        find = None
        for versioned_value in dto_object:
            if versioned_value.value == source_dto:
                versioned_value.versions.append(self.version_name)
                find = versioned_value
                changes += 1 if self.has_changed(versioned_value) else 0

        if find is None:
            changes += 1
            if source_dto.type is ObjectRaw.Types.object:
                for (property_name, property_value) in source_object.properties.items():
                    source_dto.properties[property_name] = []
                    changes += self.hydrate_object(source_dto.properties[property_name], property_value)
                for (property_name, property_value) in source_object.pattern_properties.items():
                    source_dto.pattern_properties[property_name] = []
                    changes += self.hydrate_object(source_dto.pattern_properties[property_name], property_value)
                changes += self.hydrate_object(source_dto.additional_properties, source_object.additional_properties)
            elif source_dto.type is ObjectRaw.Types.array:
                source_dto.items = []
                changes += self.hydrate_object(source_dto.items, source_object.items)
            elif source_dto.type is ObjectRaw.Types.dynamic:
                source_dto.items = []
                changes += self.hydrate_object(source_dto.items, source_object.items)
            elif source_dto.type is ObjectRaw.Types.enum:
                source_dto.values = []
                source_dto.descriptions = []
                self.hydrate_list(source_dto.values, sorted(source_object.values))
                self.hydrate_list(source_dto.descriptions, sorted(source_object.descriptions))
            elif source_dto.type is ObjectRaw.Types.type:
                source_dto.type_object = source_object.type_object

            dto_object.append(MultiVersion(source_dto, self.version_name))
        else:
            if source_dto.type is ObjectRaw.Types.object:
                for (property_name, property_value) in source_object.properties.items():
                    if find.value.type is ObjectRaw.Types.object and property_name in find.value.properties.keys():
                        changes += self.hydrate_object(find.value.properties[property_name], property_value)
                    else:
                        find.value.properties[property_name] = []
                        changes += self.hydrate_object(find.value.properties[property_name], property_value)
                for (property_name, property_value) in source_object.pattern_properties.items():
                    if find.value.type is ObjectRaw.Types.object and property_name in find.value.pattern_properties.keys():
                        changes += self.hydrate_object(find.value.pattern_properties[property_name], property_value)
                    else:
                        find.value.pattern_properties[property_name] = []
                        changes += self.hydrate_object(find.value.pattern_properties[property_name], property_value)
                changes += self.hydrate_object(find.value.additional_properties, source_object.additional_properties)
            elif source_dto.type is ObjectRaw.Types.array:
                changes += self.hydrate_object(find.value.items, source_object.items)
            elif source_dto.type is ObjectRaw.Types.dynamic:
                changes += self.hydrate_object(find.value.items, source_object.items)
            elif source_dto.type is ObjectRaw.Types.enum:
                changes += self.hydrate_list(find.value.values, source_object.values)
                changes += self.hydrate_list(find.value.descriptions, source_object.descriptions)

        return changes

    def has_changed(self, multi_version):
        previous_version = self.get_previous_version()
        if previous_version is None:
            return False

        return previous_version.name not in multi_version.versions

    def get_previous_version(self):
        previous = None
        for version in sorted(self.versions.values()):
            if version.name == self.version_name:
                return previous
            previous = version

        raise ValueError("Unable to find current version in Version list")
apidoc/object/source_raw.py
class ObjectDynamic(Object, Constraintable):

    """Element ObjectDynamic
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.type = Object.Types("dynamic")
        self.items = None

    def get_default_sample(self):
        """Return default value for the element
        """
        return {
            "key1": "my_%s" % self.name,
            "key2": "sample"
        }
apidoc/object/source_dto.py
class TypeCategory(Category):

    """Element TypeCategory
    """

    def __init__(self, category):
        """Class instantiation
        """
        super().__init__(category)
        self.types = []
apidoc/object/source_raw.py
class ObjectString(Object, Constraintable):

    """Element ObjectString
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.type = Object.Types("string")
apidoc/object/source_raw.py
class ObjectArray(Object, Constraintable):

    """Element ObjectArray
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.type = Object.Types("array")
        self.items = None
        self.sample_count = 2
apidoc/object/source_raw.py
class ObjectObject(Object, Constraintable):

    """Element ObjectObject
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.type = Object.Types("object")
        self.properties = {}
        self.pattern_properties = {}
        self.additional_properties = None
apidoc/object/source_dto.py
class MultiVersion(Comparable):

    class Changes(Enum):
        """List of availables Change for this element
        """
        none = 1
        new = 2
        updated = 3
        deleted = 4

    def __init__(self, value, version):
        self.versions = [version]
        self.value = value

    def get_comparable_values(self):
        """Return a tupple of values representing the unicity of the object
        """
        return (self.value, sorted(self.versions))
apidoc/object/source_raw.py
class ObjectConst(Object, Constraintable):

    """Element ObjectConst
    """

    class Types(Enum):

        """List of availables Primaries for this element
        """
        string = 1
        boolean = 2
        number = 3
        integer = 4

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.type = Object.Types("const")
        self.const_type = ObjectConst.Types.string
        self.value = None

    def get_default_sample(self):
        """Return default value for the element
        """
        return self.value
apidoc/object/source_raw.py
class Parameter(Element, Sampleable):

    """Element Parameter
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.type = None
        self.optional = False
        self.generic = False
        self.type_object = None
        self.position = 0

    def get_object(self):
        object = Object.factory(self.type, None)
        object.name = self.name
        return object

    def get_default_sample(self):
        """Return default value for the element
        """
        if self.type not in Object.Types or self.type is Object.Types.type:
            return self.type_object.get_sample()
        else:
            return self.get_object().get_sample()
apidoc/object/source_raw.py
class Method(Element, Displayable, Comparable):

    """Element Method
    """

    class Methods(Enum):

        """List of availables Methods for this element
        """
        get = 1
        post = 2
        put = 3
        delete = 4
        head = 5
        option = 6
        patch = 7

    @property
    def message(self):
        """Return default message for this element
        """
        if self.code != 200:
            for code in self.response_codes:
                if code.code == self.code:
                    return code.message

            raise ValueError("Unknown response code \"%s\" in \"%s\"." % (self.code, self.name))

        return "OK"

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.code = 200
        self.uri = None
        self.absolute_uri = None
        self.full_uri = None
        self.category = None
        self.method = Method.Methods("get")
        self.request_headers = {}
        self.request_parameters = {}
        self.request_body = None
        self.response_codes = []
        self.response_body = None

    def get_comparable_values(self):
        """Return a tupple of values representing the unicity of the object
        """
        return (str(self.name))
apidoc/object/source_dto.py
class Root():

    """Root object of sources elements for templates
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.configuration = None
        self.versions = []
        self.method_categories = []
        self.type_categories = []
apidoc/factory/source/rootDto.py
class RootDto():
    """ Root Factory
    """

    def create_from_root(self, root_source):
        """Return a populated Object Root from dictionnary datas
        """

        root_dto = ObjectRoot()

        root_dto.configuration = root_source.configuration
        root_dto.versions = [Version(x) for x in root_source.versions.values()]

        for version in sorted(root_source.versions.values()):
            hydrator = Hydrator(version, root_source.versions, root_source.versions[version.name].types)
            for method in version.methods.values():
                hydrator.hydrate_method(root_dto, root_source, method)
            for type in version.types.values():
                hydrator.hydrate_type(root_dto, root_source, type)

        self.define_changes_status(root_dto)

        return root_dto

    def define_changes_status(self, root_dto):
        sorted_version = sorted(root_dto.versions)

        items = []
        for category in root_dto.method_categories:
            items = items + category.methods
        for category in root_dto.type_categories:
            items = items + category.types

        for item in items:
            new = False
            for version in sorted_version:
                if version.name not in item.changes_status.keys():
                    if new:
                        item.changes_status[version.name] = MultiVersion.Changes.deleted
                        new = False
                    else:
                        item.changes_status[version.name] = MultiVersion.Changes.none
                else:
                    if not new:
                        item.changes_status[version.name] = MultiVersion.Changes.new
                        new = True
apidoc/object/source_raw.py
class ObjectType(Object, Constraintable):

    """Element ObjectType
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.type = Object.Types("type")
        self.type_name = None
        self.type_object = None

    def get_default_sample(self):
        """Return default value for the element
        """
        if self.type_object is None:
            return super().get_default_sample()
        return self.type_object.get_sample()
apidoc/object/source_dto.py
class MethodCategory(Category):

    """Element MethodCategory
    """

    def __init__(self, category):
        """Class instantiation
        """
        super().__init__(category)
        self.methods = []
apidoc/object/source_dto.py
class Version(Element, Comparable):

    """Element Version
    """

    def __init__(self, version):
        """Class instantiation
        """
        super().__init__(version)
        self.label = version.label
        self.uri = version.uri
        self.major = version.major
        self.minor = version.minor
        self.status = version.status

    def get_comparable_values(self):
        """Return a tupple of values representing the unicity of the object
        """
        return (int(self.major), int(self.minor), str(self.label), str(self.name))
apidoc/object/source_raw.py
class Type(Element, Comparable, Sampleable):

    """Element Type
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.format = TypeFormat()
        self.category = None
        self.item = None

    def get_sample(self):
        """Return the a sample for the element
        """
        if self.item is not None:
            return self.item.get_sample()
        else:
            return super().get_sample()

    def get_comparable_values(self):
        """Return a tupple of values representing the unicity of the object
        """
        return (str(self.name))
apidoc/object/source_raw.py
class Root():

    """Root object of sources elements
    """

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.configuration = Configuration()
        self.versions = {}
        self.categories = {}
        self.methods = {}
        self.types = {}
        self.references = {}
apidoc/object/source_raw.py
class ObjectEnum(Object, Constraintable):

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.type = Object.Types("enum")
        self.values = []
        self.descriptions = []

    def get_default_sample(self):
        """Return default value for the element
        """
        if not self.values:
            return super().get_default_sample()
        return self.values[0]
apidoc/object/source_dto.py
class RequestParameter(Parameter):

    def __init__(self, parameter):
        """Class instantiation
        """
        super().__init__(parameter)
        self.position = parameter.position

    @property
    def is_query_string(self):
        return self.position < 0

    def get_comparable_values(self):
        """Return a tupple of values representing the unicity of the object
        """
        return (0 if self.position >= 0 else 1, not self.generic, str(self.name), str(self.description))

    def get_comparable_values_for_ordering(self):
        """Return a tupple of values representing the unicity of the object
        """

        return (0 if self.position >= 0 else 1, int(self.position), str(self.name), str(self.description))
apidoc/object/source_raw.py
class Version(Element, Displayable, Comparable):

    """Element Version
    """

    class Status(Enum):

        """List of availables Status for this element
        """
        current = 1
        beta = 2
        deprecated = 3
        draft = 4

    def __init__(self):
        """Class instantiation
        """
        super().__init__()
        self.uri = None
        self.full_uri = None
        self.major = 1
        self.minor = 0
        self.status = Version.Status("current")
        self.methods = {}
        self.types = {}
        self.references = {}

    def get_comparable_values(self):
        """Return a tupple of values representing the unicity of the object
        """
        return (int(self.major), int(self.minor), str(self.name))
apidoc/object/source_raw.py
class Category(Element, Displayable):

    """Element Category
    """

    def __init__(self, name):
        """Class instantiation
        """
        super().__init__()
        self.name = name
        self.label = name
        self.order = 99


Question: import unittest
from apidoc.factory.source.rootDto import RootDto as RootDtoFactory
from apidoc.factory.source.rootDto import Hydrator
from apidoc.object.source_raw import Root, Version, Method, Type, Category, Parameter
from apidoc.object.source_raw import ObjectString, ObjectArray, ObjectObject, ObjectDynamic, ObjectType, ObjectConst, ObjectEnum
from apidoc.object.source_dto import Root as RootDto
from apidoc.object.source_dto import Version as VersionDto
from apidoc.object.source_dto import MethodCategory, TypeCategory, MultiVersion, RequestParameter
        version.types = {"m": type}

        root_dto = RootDto()

        hydrator = Hydrator(version, {"v": version}, [])

        hydrator.hydrate_type(root_dto, root, type1)
        hydrator.hydrate_type(root_dto, root, type2)

        self.assertEqual(1, len(root_dto.type_categories))

    def test_hydrate_type_changed(self):

        root = Root()
        version1 = Version()
        version1.name = "v1"
        version2 = Version()
        version2.name = "v2"
        type1 = Type()
        type1.name = "m1"
        type1.description = "b"
        type1.item = ObjectString()
        type2 = Type()
        type2.name = "m1"
        type2.description = "c"
        type2.item = ObjectString()

        root.versions = {"v1": version1, "v2": version2}
        version1.types = {"m1": type1}
        version2.types = {"m1": type2}

        root_dto = RootDto()

        Hydrator(version1, root.versions, []).hydrate_type(root_dto, root, type1)
        Hydrator(version2, root.versions, []).hydrate_type(root_dto, root, type2)

        self.assertEqual(1, len(root_dto.type_categories))
        self.assertEqual(1, len(root_dto.type_categories[0].types))
        self.assertEqual(2, len(root_dto.type_categories[0].types[0].description))
        self.assertEqual(MultiVersion.Changes.updated, root_dto.type_categories[0].types[0].changes_status["v2"])

    def test_hydrate_value(self):

        version1 = Version()
        version1.name = "v1"
        version2 = Version()
        version2.name = "v2"

        versions = {"v1": version1, "v2": version2}

        object_dto = []
        object = "a"

        response = Hydrator(version1, versions, []).hydrate_value(object_dto, object)
        response = Hydrator(version2, versions, []).hydrate_value(object_dto, object)

        self.assertEqual(0, response)
        self.assertEqual(1, len(object_dto))
        self.assertEqual("a", object_dto[0].value)
        self.assertIn(version1.name, object_dto[0].versions)
        self.assertIn(version2.name, object_dto[0].versions)

    def test_hydrate_value__none(self):

        version1 = Version()
        version1.name = "v1"
        version2 = Version()
        version2.name = "v2"

        versions = {"v1": version1, "v2": version2}

        object_dto = []
        object = None

        response = Hydrator(version1, versions, []).hydrate_value(object_dto, object)

        self.assertEqual(0, response)
        self.assertEqual(0, len(object_dto))

    def test_hydrate_list(self):

        version1 = Version()
        version1.name = "v1"
        version2 = Version()
        version2.name = "v2"

        versions = {"v1": version1, "v2": version2}

        object_dto = []
        object = ["a", "b"]

        response = Hydrator(version1, versions, []).hydrate_list(object_dto, object)
        response = Hydrator(version2, versions, []).hydrate_list(object_dto, object)

        self.assertEqual(0, response)
        self.assertEqual(2, len(object_dto))
        self.assertEqual("a", object_dto[0].value)
        self.assertEqual("b", object_dto[1].value)
        self.assertIn(version1.name, object_dto[0].versions)
        self.assertIn(version2.name, object_dto[0].versions)

    def test_hydrate_object(self):

        version1 = Version()
        version1.name = "v1"
        version2 = Version()
        version2.name = "v2"

        versions = {"v1": version1, "v2": version2}

        object_dto = []

        object1 = ObjectObject()
        object2 = ObjectObject()
        object3 = ObjectObject()
        object1.name = "a"
        object2.name = "a"
        object3.name = "b"
        array = ObjectArray()
        array.name = "b"

Answer:         dynamic = ObjectDynamic()