def datatype_factory(datatype, value, version=None, validation_level=None):
    """
    Factory function for both base and complex datatypes. It generates the correct object according
    to the datatype in input.
    It should be noted that if you use the factory it is not possible to specify
    some parameters for the datatype (e.g. the format for datetime base datatypes)
    If the value is not valid for the datatype specified if the ``validation_level`` is
    :attr:`hl7apy.consts.VALIDATION_LEVEL.TOLERANT` it generates an :class:`hl7apy.base_datatypes.ST` object

    :type datatype: ``str``
    :param datatype: The datatype to be generated

    :param value: The value of the datatype

    :type version: ``str``
    :param version: A valid HL7 version. It must be one of
        :attr:`SUPPRTED_LIBRARIES <hl7apy.SUPPORTED_LIBRARIES>`

    :type validation_level: ``int``
    :param validation_level: It must be a value from class :attr:`validation_level`
        :class:`VALIDATION_LEVEL hl7apy.consts.VALIDATION_LEVEL` or ``None`` to use the default value

    :rtype: The type specified in datatype

    :raises :exc:`ValueError`: If the ``validation_level`` is
        :attr:`VALIDATION_LEVEL.STRICT <hl7apy.consts.VALIDATION_LEVEL.STRICT>`
     and the value is not valid for the specified datatype

    :raises :exc:`InvalidDatatype <hl7apy.exceptions.InvalidDatatype>`: If the ``datatype`` specified is not
        valid for the given ``version``

    """

    from hl7apy.validation import Validator

    if validation_level is None:
        validation_level = get_default_validation_level()

    if version is None:
        version = get_default_version()

    lib = load_library(version)

    base_datatypes = lib.get_base_datatypes()

    factories = base_datatypes.copy()

    if 'DT' in factories:
        factories['DT'] = date_factory
    if 'TM' in factories:
        factories['TM'] = timestamp_factory
    if 'DTM' in factories:
        factories['DTM'] = datetime_factory
    if 'NM' in factories:
        factories['NM'] = numeric_factory
    if 'SI' in factories:
        factories['SI'] = sequence_id_factory

    try:
        factory = factories[datatype]
        if isinstance(factory, FunctionType):
            return factory(value, base_datatypes[datatype], validation_level=validation_level)
        return factory(value, validation_level=validation_level)
    except KeyError:
        raise InvalidDataType(datatype)
    except ValueError as e:
        if Validator.is_strict(validation_level):
            raise e
        # TODO: Do we really want this? In that case the parent's datatype must be changed accordingly
        return factories['ST'](value)