def load_config_dict(pipette_id: str) -> Tuple[
        'PipetteFusedSpec', 'PipetteModel']:
    """ Give updated config with overrides for a pipette. This will add
    the default value for a mutable config before returning the modified
    config value.
    """
    override = load_overrides(pipette_id)
    model = override['model']
    config = fuse_specs(model)

    if 'quirks' not in override.keys():
        override['quirks'] = {key: True for key in config['quirks']}

    for top_level_key in config.keys():
        if top_level_key != 'quirks':
            add_default(config[top_level_key])  # type: ignore

    config.update(override)  # type: ignore

    return config, model
def load(
        pipette_model: 'PipetteModel',
        pipette_id: str = None) -> PipetteConfig:
    """
    Load pipette config data


    This function loads from a combination of

    - the pipetteModelSpecs.json file in the wheel (should never be edited)
    - the pipetteNameSpecs.json file in the wheel (should never be edited)
    - any config overrides found in
      ``opentrons.config.CONFIG['pipette_config_overrides_dir']``

    This function reads from disk each time, so changes to the overrides
    will be picked up in subsequent calls.

    :param str pipette_model: The pipette model name (i.e. "p10_single_v1.3")
                              for which to load configuration
    :param pipette_id: An (optional) unique ID for the pipette to locate
                       config overrides. If the ID is not specified, the system
                       assumes this is a simulated pipette and does not
                       save settings. If the ID is specified but no overrides
                       corresponding to the ID are found, the system creates a
                       new overrides file for it.
    :type pipette_id: str or None
    :raises KeyError: if ``pipette_model`` is not in the top-level keys of
                      the pipetteModelSpecs.json file (and therefore not in
                      :py:attr:`configs`)

    :returns PipetteConfig: The configuration, loaded and checked
    """

    # Load the model config and update with the name config
    cfg = fuse_specs(pipette_model)

    # Load overrides if we have a pipette id
    if pipette_id:
        try:
            override = load_overrides(pipette_id)
            if 'quirks' in override.keys():
                override['quirks'] = [
                    qname for qname, qval in override['quirks'].items()
                    if qval]
            for legacy_key in (
                    'defaultAspirateFlowRate',
                    'defaultDispenseFlowRate',
                    'defaultBlowOutFlowRate'):
                override.pop(legacy_key, None)

        except FileNotFoundError:
            save_overrides(pipette_id, {}, pipette_model)
            log.info(
                "Save defaults for pipette model {} and id {}".format(
                    pipette_model, pipette_id))
        else:
            cfg.update(override)  # type: ignore

    # the ulPerMm functions are structured in pipetteModelSpecs.json as
    # a list sorted from oldest to newest. That means the latest functions
    # are always the last element and, as of right now, the older ones are
    # the first element (for models that only have one function, the first
    # and last elements are the same, which is fine). If we add more in the
    # future, we’ll have to change this code to select items more
    # intelligently
    if ff.use_old_aspiration_functions():
        log.debug("Using old aspiration functions")
        ul_per_mm = cfg['ulPerMm'][0]
    else:
        ul_per_mm = cfg['ulPerMm'][-1]

    smoothie_configs = cfg['smoothieConfigs']
    res = PipetteConfig(
        top=ensure_value(
            cfg, 'top', MUTABLE_CONFIGS),
        bottom=ensure_value(
            cfg, 'bottom', MUTABLE_CONFIGS),
        blow_out=ensure_value(
            cfg, 'blowout', MUTABLE_CONFIGS),
        drop_tip=ensure_value(
            cfg, 'dropTip', MUTABLE_CONFIGS),
        pick_up_current=ensure_value(cfg, 'pickUpCurrent', MUTABLE_CONFIGS),
        pick_up_distance=ensure_value(cfg, 'pickUpDistance', MUTABLE_CONFIGS),
        pick_up_increment=ensure_value(
            cfg, 'pickUpIncrement', MUTABLE_CONFIGS),
        pick_up_presses=ensure_value(cfg, 'pickUpPresses', MUTABLE_CONFIGS),
        pick_up_speed=ensure_value(cfg, 'pickUpSpeed', MUTABLE_CONFIGS),
        aspirate_flow_rate=cfg['defaultAspirateFlowRate']['value'],
        dispense_flow_rate=cfg['defaultDispenseFlowRate']['value'],
        channels=ensure_value(cfg, 'channels', MUTABLE_CONFIGS),
        model_offset=ensure_value(cfg, 'modelOffset', MUTABLE_CONFIGS),
        plunger_current=ensure_value(cfg, 'plungerCurrent', MUTABLE_CONFIGS),
        drop_tip_current=ensure_value(cfg, 'dropTipCurrent', MUTABLE_CONFIGS),
        drop_tip_speed=ensure_value(cfg, 'dropTipSpeed', MUTABLE_CONFIGS),
        min_volume=ensure_value(cfg, 'minVolume', MUTABLE_CONFIGS),
        max_volume=ensure_value(cfg, 'maxVolume', MUTABLE_CONFIGS),
        ul_per_mm=ul_per_mm,
        quirks=validate_quirks(ensure_value(cfg, 'quirks', MUTABLE_CONFIGS)),
        tip_overlap=cfg['tipOverlap'],
        tip_length=ensure_value(cfg, 'tipLength', MUTABLE_CONFIGS),
        display_name=ensure_value(cfg, 'displayName', MUTABLE_CONFIGS),
        name=cfg['name'],
        back_compat_names=cfg.get('backCompatNames', []),
        return_tip_height=cfg.get('returnTipHeight', 0.5),
        blow_out_flow_rate=cfg['defaultBlowOutFlowRate']['value'],
        max_travel=smoothie_configs['travelDistance'],
        home_position=smoothie_configs['homePosition'],
        steps_per_mm=smoothie_configs['stepsPerMM'],
        idle_current=cfg.get('idleCurrent', LOW_CURRENT_DEFAULT),
        default_blow_out_flow_rates=cfg['defaultBlowOutFlowRate'].get(
            'valuesByApiLevel',
            {'2.0': cfg['defaultBlowOutFlowRate']['value']}),
        default_dispense_flow_rates=cfg['defaultDispenseFlowRate'].get(
            'valuesByApiLevel',
            {'2.0': cfg['defaultDispenseFlowRate']['value']}),
        default_aspirate_flow_rates=cfg['defaultAspirateFlowRate'].get(
            'valuesByApiLevel',
            {'2.0': cfg['defaultAspirateFlowRate']['value']}),
    )

    return res
def test_fuse(model, name):
    defdict = fuse_specs(model, name)
    typeguard.check_type('defdict', defdict, PipetteFusedSpec)