Filter a list of resources according to a query expression.

    It accepts the optional part of the expression.

    .. warning: This function is experimental and unsafe as it uses eval,
                It also might require to open the resource.

    :param resources: iterable of resources.
    :param query: query expression.
    :param open_resource: function to open the resource.

def filter2(resources, query, open_resource):
    """Filter a list of resources according to a query expression.

    It accepts the optional part of the expression.

    .. warning: This function is experimental and unsafe as it uses eval,
                It also might require to open the resource.

    :param resources: iterable of resources.
    :param query: query expression.
    :param open_resource: function to open the resource.
    """

    if '{' in query:
        try:
            query, optional = query.split('{')
            optional, _ = optional.split('}')
        except ValueError:
            raise errors.VisaIOError(constants.VI_ERROR_INV_EXPR)
    else:
        optional = None

    filtered = filter(resources, query)

    if not optional:
        return filtered

    optional = optional.replace('&&', 'and').replace('||', 'or').replace('!', 'not ')
    optional = optional.replace('VI_', 'res.VI_')

    class AttrGetter():

        def __init__(self, resource_name):
            self.resource_name = resource_name
            self.parsed = parse_resource_name(resource_name)
            self.resource = None

        def __getattr__(self, item):
            if item == 'VI_ATTR_INTF_NUM':
                   return int(self.parsed.board)
            elif item == 'VI_ATTR_MANF_ID':
                   return self.parsed.manufacturer_id
            elif item == 'VI_ATTR_MODEL_CODE':
                   return self.parsed.model_code
            elif item == 'VI_ATTR_USB_SERIAL_NUM':
                   return self.parsed.serial_number
            elif item == 'VI_ATTR_USB_INTFC_NUM':
                   return int(self.parsed.board)
            elif item == 'VI_ATTR_TCPIP_ADDR':
                   return self.parsed.host_address
            elif item == 'VI_ATTR_TCPIP_DEVICE_NAME':
                   return self.parsed.lan_device_name
            elif item == 'VI_ATTR_TCPIP_PORT':
                   return int(self.parsed.port)
            elif item == 'VI_ATTR_INTF_NUM':
                   return int(self.parsed.board)
            elif item == 'VI_ATTR_GPIB_PRIMARY_ADDR':
                   return int(self.parsed.primary_address)
            elif item == 'VI_ATTR_GPIB_SECONDARY_ADDR':
                   return int(self.parsed.secondary_address)
            elif item == 'VI_ATTR_PXI_CHASSIS':
                return self.parsed.chassis_number
            elif item == 'VI_ATTR_MAINFRAME_LA':
                return self.parsed.vxi_logical_address

            if self.resource is None:
                self.resource = open_resource(self.resource_name)

            return self.resource.get_visa_attribute(item)

    @contextlib.contextmanager
    def open_close(resource_name):
        getter = AttrGetter(resource_name)
        yield getter
        if getter.resource is not None:
            getter.resource.close()

    selected = []
    for rn in filtered:
        with open_close(rn) as getter:
            if eval(optional, None, dict(res=getter)):
                selected.append(rn)