Example #1
0
def test_default_schema():
    r"""Test getting default schema."""
    s = schema.get_schema()
    assert (s is not None)
    schema.clear_schema()
    assert (schema._schema is None)
    s = schema.get_schema()
    assert (s is not None)
    for k in s.keys():
        assert (isinstance(s[k].subtypes, list))
        assert (isinstance(s[k].classes, list))
Example #2
0
def prep_model(yml, iodict):
    r"""Prepare yaml model entry for parsing with schema.

    Args:
        yml (dict): YAML entry for model that will be modified.
        iodict (dict): Log of all input/outputs.

    """
    class2language = get_schema().class2language
    # Standardize format of input/output
    for io in ['inputs', 'outputs']:
        if io not in yml:
            yml[io] = []
        if not isinstance(yml[io], list):
            yml[io] = [yml[io]]
        if io[:-1] in yml:
            if isinstance(yml[io[:-1]], list):
                yml[io] += yml.pop(io[:-1])
            else:
                yml[io].append(yml.pop(io[:-1]))
        for i in range(len(yml[io])):
            if isinstance(yml[io][i], str):
                yml[io][i] = dict(name=yml[io][i])
            if 'driver' in yml[io][i]:
                yml[io][i].setdefault('working_dir', yml['working_dir'])
            if 'name' in yml[io][i]:
                iodict[io][yml[io][i]['name']] = yml[io][i]
    # Replace driver with language
    if ('language' not in yml) and ('driver' in yml):
        yml['language'] = class2language[yml.pop('driver')][0]
    # String to list
    if ('client_of' in yml) and (not isinstance(yml['client_of'], list)):
        yml['client_of'] = [yml['client_of']]
Example #3
0
def get_supported_comm():
    r"""Get a list of the communication mechanisms supported by cis_interface.

    Returns:
        list: The names of communication mechanisms supported by cis_interface.

    """
    from cis_interface import schema
    s = schema.get_schema()
    out = s['comm'].classes
    out.remove('CommBase')
    return list(set(out))
Example #4
0
def get_supported_lang():
    r"""Get a list of the model programming languages that are supported
    by cis_interface.

    Returns:
        list: The names of programming languages supported by cis_interface.
    
    """
    from cis_interface import schema
    s = schema.get_schema()
    out = s['model'].subtypes
    if 'c++' in out:
        out[out.index('c++')] = 'cpp'
    return list(set(out))
Example #5
0
def is_lang_installed(lang):
    r"""Check to see if cis_interface can run models written in a programming
    language on the current machine.

    Args:
        lang (str): Programming language to check.

    Returns:
        bool: True if models in the provided language can be run on the current
            machine, False otherwise.

    """
    from cis_interface import schema, drivers
    s = schema.get_schema()
    drv = drivers.import_driver(s.language2class[lang])
    return drv.is_installed()
Example #6
0
def test_create_schema():
    r"""Test creating new schema."""
    fname = 'test_schema.yml'
    if os.path.isfile(fname):  # pragma: debug
        os.remove(fname)
    # Test saving/loading schema
    s0 = schema.create_schema()
    s0.save(fname)
    assert (os.path.isfile(fname))
    s1 = schema.get_schema(fname)
    nt.assert_equal(s1, s0)
    os.remove(fname)
    # Test getting schema
    s2 = schema.load_schema(fname)
    assert (os.path.isfile(fname))
    nt.assert_equal(s2, s0)
    os.remove(fname)
Example #7
0
def parse_model(yml, existing):
    r"""Parse a yaml entry for a model.

    Args:
        yml (dict): YAML dictionary for a model.
        existing (dict): Dictionary of existing components.

    Returns:
        dict: Updated log of all entries.

    """
    _lang2driver = get_schema().language2class
    yml['driver'] = _lang2driver[yml.pop('language')]
    # Add server driver
    if yml.get('is_server', False):
        srv = {
            'name': yml['name'],
            'driver': 'ServerDriver',
            'args': yml['name'] + '_SERVER',
            'working_dir': yml['working_dir']
        }
        yml['inputs'].append(srv)
        yml['clients'] = []
    # Add client driver
    if yml.get('client_of', []):
        srv_names = yml['client_of']
        # prep_model converts to list
        # if isinstance(srv_names, str):
        #     srv_names = [srv_names]
        yml['client_of'] = srv_names
        for srv in srv_names:
            cli = {
                'name': '%s_%s' % (srv, yml['name']),
                'driver': 'ClientDriver',
                'args': srv + '_SERVER',
                'working_dir': yml['working_dir']
            }
            yml['outputs'].append(cli)
    # Model index and I/O channels
    yml['model_index'] = len(existing['model'])
    for io in ['inputs', 'outputs']:
        for x in yml[io]:
            x['model_driver'] = [yml['name']]
            existing = parse_component(x, io[:-1], existing=existing)
    return existing
Example #8
0
    def convertSpec(self, spec):
        """Convert spec."""
        cisspec = uiToCis(spec['content'])

        # Write to temp file and validate
        tmpfile = tempfile.NamedTemporaryFile(suffix="yml", prefix="cis",
                                              delete=False)
        yaml.safe_dump(cisspec, tmpfile, default_flow_style=False)
        yml_prep = prep_yaml(tmpfile)
        os.remove(tmpfile.name)

        v = get_schema().validator
        yml_norm = v.normalized(yml_prep)
        if not v.validate(yml_norm):
            print(v.errors)
            raise RestException('Invalid model %s', 400, v.errors)

        self.setRawResponse()
        return pyaml.dump(cisspec)
Example #9
0
def cdriver2filetype(driver):
    r"""Convert a connection driver to a file type.

    Args:
        driver (str): The name of the connection driver.

    Returns:
        str: The corresponding file type for the driver.

    """
    schema = get_schema()
    conntypes = schema.class2conntype
    filetypes = schema.class2filetype
    if driver not in conntypes:
        raise ValueError("%s is not a registered connection driver." % driver)
    icomm, ocomm, direction = conntypes[driver][0]
    if direction == 'input':
        ftype = filetypes[icomm][0]
    else:
        ftype = filetypes[ocomm][0]
    return ftype
Example #10
0
def parse_yaml(files):
    r"""Parse list of yaml files.

    Args:
        files (str, list): Either the path to a single yaml file or a list of
            yaml files.

    Raises:
        ValueError: If the yml dictionary is missing a required keyword or has
            an invalid value.
        RuntimeError: If one of the I/O channels is not initialized with driver
            information.

    Returns:
        dict: Dictionary of information parsed from the yamls.

    """
    # Parse files using schema
    yml_prep = prep_yaml(files)
    v = get_schema().validator
    yml_norm = v.normalized(yml_prep)
    if not v.validate(yml_norm):
        pprint.pprint(yml_norm)
        pprint.pprint(v.errors)
        raise ValueError("Invalid yaml.")
    yml_all = v.document
    # Parse models, then connections to ensure connections can be processed
    existing = None
    for k in ['models', 'connections']:
        for yml in yml_all[k]:
            existing = parse_component(yml, k[:-1], existing=existing)
    # Make sure that I/O channels initialized
    for io in ['input', 'output']:
        for k, v in existing[io].items():
            if 'driver' not in v:
                raise RuntimeError("No driver established for %s channel %s" %
                                   (io, k))
    # Link io drivers back to models
    existing = link_model_io(existing)
    return existing
Example #11
0
def parse_connection(yml, existing):
    r"""Parse a yaml entry for a connection between I/O channels.

    Args:
        yml (dict): YAML dictionary for a connection.
        existing (dict): Dictionary of existing components.

    Raises:
        RuntimeError: If the 'input' entry is not a model output or file.
        RuntimeError: If neither the 'input' or 'output' entries correspond
            to model I/O channels.

    Returns:
        dict: Updated log of all entries.

    """
    schema = get_schema()
    comm2conn = schema.conntype2class
    ftyp2comm = schema.filetype2class
    conn_keys_gen = ['input', 'input_file', 'output', 'output_file']
    conn_keys = list(set(schema['connection'].keys()) - set(conn_keys_gen))
    yml_conn = {}
    if not isinstance(yml.get('input', []), list):
        yml['input'] = [yml['input']]
    if not isinstance(yml.get('output', []), list):
        yml['output'] = [yml['output']]
    # File input
    if 'input_file' in yml:
        fname = os.path.expanduser(yml['input_file']['name'])
        if not os.path.isabs(fname):
            fname = os.path.join(yml['input_file']['working_dir'], fname)
        fname = os.path.normpath(fname)
        if not os.path.isfile(fname):
            raise RuntimeError(
                ("Input '%s' not found in any of the registered " +
                 "model outputs and is not a file.") % fname)
        args = fname
        name = ','.join(yml['output'])
        ocomm_pair = None
        icomm_pair = (ftyp2comm[yml['input_file']['filetype']], 'DefaultComm')
        yml_conn.update(**yml['input_file'])
    # File output
    elif 'output_file' in yml:
        fname = os.path.expanduser(yml['output_file']['name'])
        if not yml['output_file'].get('in_temp', False):
            if not os.path.isabs(fname):
                fname = os.path.join(yml['output_file']['working_dir'], fname)
            fname = os.path.normpath(fname)
        args = fname
        name = ','.join(yml['input'])
        ocomm_pair = ('DefaultComm', ftyp2comm[yml['output_file']['filetype']])
        icomm_pair = None
        yml_conn.update(**yml['output_file'])
    # Generic Input/Output
    else:
        iname = ','.join(yml['input'])
        oname = ','.join(yml['output'])
        args = '%s_to_%s' % (iname, oname)
        name = args
        # TODO: Use RMQ drivers when models are on different machines
        # ocomm_pair = ('DefaultComm', 'rmq')
        # icomm_pair = ('rmq', 'DefaultComm')
        ocomm_pair = ('DefaultComm', 'DefaultComm')
        icomm_pair = ('DefaultComm', 'DefaultComm')
    # Output driver
    xo = None
    if ocomm_pair is not None:
        iyml = yml['input']
        iname = ','.join(iyml)
        if len(iyml) == 1:
            xo = existing['output'][iyml[0]]
        else:
            xo = {'name': iname, 'comm': [], 'model_driver': []}
            for y in iyml:
                xo['comm'].append(existing['output'][y])
                xo['model_driver'] += existing['output'][y]['model_driver']
                del existing['output'][y]
            existing = parse_component(xo, 'output', existing)
        xo['args'] = args
        xo['driver'] = comm2conn[(ocomm_pair[0], ocomm_pair[1], 'output')]
    # Input driver
    xi = None
    if icomm_pair is not None:
        oyml = yml['output']
        oname = ','.join(oyml)
        if len(oyml) == 1:
            xi = existing['input'][oyml[0]]
        else:
            xi = {'name': oname, 'comm': [], 'model_driver': []}
            for y in oyml:
                xi['comm'].append(existing['input'][y])
                xi['model_driver'] += existing['input'][y]['model_driver']
                del existing['input'][y]
            existing = parse_component(xi, 'input', existing)
        xi['args'] = args
        xi['driver'] = comm2conn[(icomm_pair[0], icomm_pair[1], 'input')]
    # Transfer connection keywords to one connection driver
    yml_conn.pop('name', None)
    for k in conn_keys:
        if k in yml:
            yml_conn[k] = yml[k]
    if xi is None:
        xo.update(**yml_conn)
    else:
        xi.update(**yml_conn)
    yml['name'] = name
    return existing
Example #12
0
def bridge_io_drivers(yml, iodict):
    r"""Create connection from input/output driver.

    Args:
        dict: YAML entry for model output that should be modified.
        iodict (dict): Log of all input/outputs.

    """
    s = get_schema()
    conn_keys_gen = ['input', 'input_file', 'output', 'output_file']
    file_keys = list(set(s['file'].keys()) - set(s['comm'].keys()))
    conn_keys = list(set(s['connection'].keys()) - set(conn_keys_gen))
    idrivers = []
    odrivers = []
    pairs = []
    new_connections = []
    for x in iodict['inputs'].values():
        if ('driver' in x) and ('args' in x):
            idrivers.append((x['args'], x['name']))
    for x in iodict['outputs'].values():
        if ('driver' in x) and ('args' in x):
            iidx = None
            for i, (iargs, iname) in enumerate(idrivers):
                if x['args'] == iargs:
                    iidx = i
                    break
            if iidx is not None:
                pairs.append((x['name'], idrivers.pop(iidx)[1]))
            else:
                odrivers.append((x['args'], x['name']))
    # Create direct connections from output to input
    for (oname, iname) in pairs:
        oyml = iodict['outputs'][oname]
        iyml = iodict['inputs'][iname]
        conn = dict(input=oname, output=iname)
        new_connections.append((oyml, iyml, conn))
        oyml.pop('working_dir', None)
        iyml.pop('working_dir', None)
    # File input
    for k, v in idrivers:
        oyml = None
        iyml = iodict['inputs'][v]
        fyml = dict(name=k, filetype=cdriver2filetype(iyml['driver']))
        conn = dict(input_file=fyml, output=v)
        new_connections.append((oyml, iyml, conn))
    # File output
    for k, v in odrivers:
        oyml = iodict['outputs'][v]
        iyml = None
        fyml = dict(name=k, filetype=cdriver2filetype(oyml['driver']))
        conn = dict(output_file=fyml, input=v)
        new_connections.append((oyml, iyml, conn))
    # Transfer keyword arguments
    for oyml, iyml, conn in new_connections:
        if oyml is None:
            fkey = 'input_file'
            fyml = iyml
            ymls = [iyml]
        elif iyml is None:
            fkey = 'output_file'
            fyml = oyml
            ymls = [oyml]
        else:
            fkey = None
            fyml = None
            ymls = [oyml, iyml]
        # File keywords
        if fyml is not None:
            for k in file_keys:
                if k in fyml:
                    conn[fkey][k] = fyml.pop(k)
        # Connection keywords
        for y in ymls:
            for k in conn_keys:
                if k not in y:
                    continue
                if k == 'translator':
                    conn.setdefault(k, [])
                    conn[k].append(y.pop(k))
                else:
                    conn.setdefault(k, y.pop(k))
            del y['driver'], y['args']
        # Add connection
        iodict['connections'].append(conn)
Example #13
0
def prep_connection(yml, iodict):
    r"""Prepare yaml connection entry for parsing with schema.

    Args:
        yml (dict): YAML entry for connection that will be modified.
        iodict (dict): Log of all input/outputs.

    """
    # Plural
    if ('inputs' in yml):
        yml['input'] = yml.pop('inputs')
    if ('outputs' in yml):
        yml['output'] = yml.pop('outputs')
    # Input
    if ('input' in yml):
        if isinstance(yml['input'], list) and (len(yml['input']) == 1):
            yml['input'] = yml['input'][0]
        if ((isinstance(yml['input'], str)
             and (yml['input'] not in iodict['outputs'])
             and ('input_file' not in yml))):
            yml['input_file'] = yml.pop('input')
        elif isinstance(yml['input'], list):
            for x in yml['input']:
                if x not in iodict['outputs']:
                    raise RuntimeError(
                        '%s not connected and cannot be a file.' % x)
    if isinstance(yml.get('input_file', None), str):
        yml['input_file'] = dict(name=yml['input_file'])
    if ('read_meth' in yml):
        if 'input_file' not in yml:
            raise ValueError("'read_meth' set, but input is not a file.")
        yml['input_file'].update(**rwmeth2filetype(yml.pop('read_meth')))
    # Output
    if ('output' in yml):
        if isinstance(yml['output'], list) and (len(yml['output']) == 1):
            yml['output'] = yml['output'][0]
        if ((isinstance(yml['output'], str)
             and (yml['output'] not in iodict['inputs'])
             and ('output_file' not in yml))):
            yml['output_file'] = yml.pop('output')
        elif isinstance(yml['output'], list):
            for x in yml['output']:
                if x not in iodict['inputs']:
                    raise RuntimeError(
                        '%s not connected and cannot be a file.' % x)
    if isinstance(yml.get('output_file', None), str):
        yml['output_file'] = dict(name=yml['output_file'])
    if ('write_meth' in yml):
        if 'output_file' not in yml:
            raise ValueError("'write_meth' set, but output is not a file.")
        yml['output_file'].update(**rwmeth2filetype(yml.pop('write_meth')))
    # Error if neither input nor output is connected to a model
    if ('input_file' in yml) and ('output_file' in yml):
        raise RuntimeError(
            ("Input '%s' and Output '%s' are both being " +
             "considered as files since neither is connected " + "to a model.")
            % (yml['input_file']['name'], yml['output_file']['name']))
    # Move remaining keys to input/output comm/file
    working_dir = yml.pop('working_dir', None)
    s = get_schema()
    conn_keys = list(set(s['connection'].keys()))

    def migrate_keys(from_dict, to_dict):
        klist = list(from_dict.keys())
        if not isinstance(to_dict, list):
            to_dict = [to_dict]
        for k in klist:
            if k not in conn_keys:
                v = from_dict.pop(k)
                for d in to_dict:
                    d.setdefault(k, v)

    if 'input_file' in yml:
        if working_dir:
            yml['input_file'].setdefault('working_dir', working_dir)
        migrate_keys(yml, yml['input_file'])
    elif 'output_file' in yml:
        if working_dir:
            yml['output_file'].setdefault('working_dir', working_dir)
        migrate_keys(yml, yml['output_file'])
    elif 'input' in yml:
        if isinstance(yml['input'], list):
            io_names = yml['input']
        else:
            io_names = [yml['input']]
        allio = [iodict['outputs'][x] for x in io_names]
        migrate_keys(yml, allio)