'''
This module provides access to a virtual data lake via its web API. 
'''

import json
import requests
from . import parts
api = None
session = requests.session()


def public_level():
    '''
    Get the public access level.
    
    Returns:
      * an `Itm` object representing the public access level.
    '''
    
    return Itm('0:1')


def source_admin_level(source):
    '''
    Get the admin access level of a source.
    
    Parameters:
      source(int): the numeric identifier of a source.
    
    Returns:
      * an `Itm` object representing the admin access level of the source.
    '''
    
    return Itm(repr(source) + '_-9223372036854775807')


def set_api(url):
    '''Set the URL for access to the API.
    
    Parameters:
      * url(str): the URL of the system's API.
    '''
    global api
    api = url
    
    
def query(constraints, credential_and_key=None):
    '''
    Get data that satisfies a set of constraints
    
    Parameters:
      * constraints: a list of constraints that the data must satisfy.
        Each constraint can be supplied either as a `Constraint` object
        or as a 3-tuple whose elements are the subject, verb and object
        of a constraint. 
      * credential_and_key: a 2-tuple whose first element is an `Itm`
        object that is a credential to authorize the request, and
        whose second element is the credential's key.
        If a credential_and_key is not supplied, the request will be 
        made at the lowest (public) level.
      
    Returns:
      * a list of dictionaries, each of which contains an assignment
      of values to unknowns that satisfies the constraints.
    '''
    
    parms = {'command': 'query'}
    scanned_constraints = []
    files = dict()
    for input_constraint in constraints:
        constraint = __get_constraint(input_constraint)
        scanned_constraints.append(
            [
                __get_quantum(constraint.subj, files),
                __get_quantum(constraint.vrb, files),
                __get_quantum(constraint.obj, files)
            ])
        
    files.update({"constraints": ("constraints", json.dumps(scanned_constraints), "text/plain; charset=utf-8")})
    if credential_and_key is not None:
        auth = {'credential': credential_and_key[0].itemid, 'key': credential_and_key[1]}
        files.update({"auth": ("auth", json.dumps(auth), "text/plain; charset=utf-8")})
    response = session.post(api, files=files, params=parms)
    if response.status_code == 200: return __get_assignments_dict_list(response)
    else: __handle_http_error(response)
    
    
def update(changes, credential_and_key=None):
    '''
    Make a set of virtual data lake changes.
        
    Parameters:
      * changes: a `Change` object or a list of `Change` objects
      * credential_and_key: a 2-tuple whose first element is an `Itm`
        object that is a credential to authorize the request, and
        whose second element is the credential's key.
        If a credential_and_key is not supplied, the request will be 
        made at the lowest (public) level.
      
    Raises:
      * VdlException: if one of the commands cannot be executed,
        or if an error occurred in transmitting the request or in
        receiving or decoding the response. Depending on the nature
        of the error, either all of the commands or none of the
        commands will have been executed.
        
    Returns:
      * The assignments of items to the handles of the commands, 
        as a dictionary.  
    '''
    
    if isinstance(changes, Change): changes = [changes]
    parms = {'command': 'update'}
    scanned_changes = []
    files = dict()
    for change in changes:
        scanned_change = {}
        
        if isinstance(change, CreateItem):
            scanned_change['change'] = 'create item'
            scanned_change['handle'] = change.handle
            scanned_change['source'] = change.source
            if change.read is not None: scanned_change['read'] = change.read.itemid
            if change.write is not None: scanned_change['write'] = change.write.itemid
        
        elif isinstance(change, DeleteItem):
            scanned_change['change'] = 'delete item'
            scanned_change['item'] = change.item.itemid
        
        elif isinstance(change, PutTriple):
            scanned_change['change'] = 'put triple'
            scanned_change['subject'] = __get_quantum(change.subj, files)
            scanned_change['verb'] = __get_quantum(change.vrb, files)
            scanned_change['object'] = __get_quantum(change.obj, files)
        
        elif isinstance(change, RemoveTriples):
            scanned_change['change'] = 'remove triples'
            scanned_change['subject'] = repr(change.subj)
            scanned_change['verb'] = repr(change.vrb)
            if change.obj is not None: scanned_change['object'] = __get_datum(change.obj, files)
        
        elif isinstance(change, SetUniqueObject):
            scanned_change['change'] = 'set unique object'
            scanned_change['subject'] = repr(change.subj)
            scanned_change['verb'] = repr(change.vrb)
            scanned_change['object'] = __get_quantum(change.obj, files)
            
        scanned_changes.append(scanned_change)
            
    files.update({"changes": ("changes", json.dumps(scanned_changes), "text/plain; charset=utf-8")})
    if credential_and_key is not None:
        auth = {'credential': credential_and_key[0].itemid, 'key': credential_and_key[1]}
        files.update({"auth": ("auth", json.dumps(auth), "text/plain; charset=utf-8")})
    response = session.post(api, files=files, params=parms)
    if response.status_code == 200: return __get_items_dict(response)
    else: __handle_http_error(response)


class Constraint:
    '''
    A constraint on data to be retrieved from a virtual data lake.
    It has a subject, a verb, and an object, each of which can be
    given or unknown.
    
    If a subject or a verb is given, then it must be an `Itm` object
    or a `NamedItm` object, because the subjects and verbs of triples
    are items.  If an object of a constraint is given, it can be an 
    `Itm` object, a `NamedItm` object, a boolean, an int, a float,
    a string, a bytes object or a bytearray object. If a subject,
    verb or object is unknown, then it is an `Unknown` object.
    
    The data retrieved from a virtual data lake using a list of constraints
    consists of a list of dictionaries. Each dictionary represents a
    solution that satisfies the constraints. The dictionary maps the 
    unknowns to the values assigned to them in the solution. The list
    is not in any particular order.
    
    The subject, verb and object of a constraint must not all be
    unknown.
    '''
    
    def __init__(self, subj, vrb, obj):
        '''
        Construct a Constraint.
        
        The supplied subject and verb can each be:
          * an `Itm` object
          * an `ItmName` object
          * an `Unknown` object
        
        The supplied object can be:
          * an `Itm` object
          * an `ItmName` object
          * an `Unknown` object
          * a bool
          * an int
          * a float
          * a str
          * a bytes object
          * a bytearray object
        
        Parameters:
          * subj: the subject of the constraint
          * vrb: the verb of the constraint
          * obj: the object of the constraint
    
        Raises:
          * VdlException if the subject, verb or object is not valid.
        '''

        if not isinstance(subj, (Itm, NamedItm, Unknown)): 
            raise VdlException("Invalid constraint subject: " + str(subj));
        if not isinstance(vrb, (Itm, NamedItm, Unknown)): 
            raise VdlException("Invalid constraint verb: " + str(vrb));
        if not isinstance(obj, (Itm, NamedItm, Unknown, bool, int, float, str, bytes, bytearray)): 
            raise VdlException("Invalid constraint object: " + str(obj));
        if isinstance(subj, Unknown) and isinstance(vrb, Unknown) and isinstance(obj, Unknown):
            raise VdlException("The subject, verb and object of a constraint must not all be unknown");
        self.subj = subj
        self.vrb = vrb
        self.obj = obj
        
        
    def __repr__(self):
        return "CONSTRAINT(" + self.subj.__repr__() + ', ' + self.vrb.__repr__() + ', ' + self.obj.__repr__() + ')'
        
        
def __get_constraint(constraint_or_tuple):
    '''
    Get a constraint that is supplied either as a 'Constraint' object
    or as a 3-tuple whose elements are the constraint's subject, verb
    and object.
    
    Parameters:
      * constraint_or_tuple: either a `Constraint` object or a
        3-tuple whose elements are the constraint's subject, verb
        and object.
        
    Raises:
      * VdlException: if a tuple is supplied that is not a 3-tuple
        containing constraint's subject, verb and object.
    
    Returns: a `Constraint` object.
    
    '''
    
    if isinstance(constraint_or_tuple, Constraint): return constraint_or_tuple
    if isinstance(constraint_or_tuple, tuple) and len(constraint_or_tuple) == 3: 
        return Constraint(constraint_or_tuple[0], constraint_or_tuple[1], constraint_or_tuple[2])
    raise VdlException("Not a constraint: " + str(constraint_or_tuple))
    

class Change:
    '''
    A virtual data lake change.
    
    '''
    
    def __init__(self):
        '''
        Construct a virtual data lake change.
        
        There is seldom any need to instantiate this class.
        It is the instances of its subclasses that are useful.
        '''
        pass
    
    
class CreateItem(Change):
    '''
    A virtual data lake change that creates an item.
    '''
    
    def __init__(self, handle, source, read=None, write=None):
        '''
        Construct a virtual data lake change that creates an item.
        
        Parameters:
          * handle: a handle that can be used to refer to the created item 
            in subsequent changes. It can be supplied as an alphanumeric 
            string or as an `Unknown` object whose name is to be used as the
            handle. 
          * source(int): the numeric identifier of the source in which
            the item is to be created.
          * read: the read access level of the item to be created. 
            This can be an `Itm` object or an `ItmName` object. 
            It defaults to the access level determined by the credential
            supplied in the credential_and_key parameter of the `update`
            function, or the lowest (public) level.
          * write: the write access level of the item to be created. 
            This can be an `Itm` object or an `ItmName` object. 
            It defaults to the access level determined by the credential
            supplied in the credential_and_key parameter of the `update`
            function, or the lowest (public) level.
            
        Raises: 
          * VdlException if the handle is neither a string nor an `Unknown` object.
        '''
        
        if isinstance(handle, str): self.handle = handle
        elif isinstance(handle, Unknown): self.handle = handle.name
        else:  raise VdlException('Not a valid handle ' + str(handle))
        self.source = source
        self.read = read
        self.write = write
        
        
    def __repr__(self):
        if self.read is None: prread = " WITH DEFAULT READ LEVEL"
        else: prread = " WITH READ LEVEL " + self.read.__repr__()
        if self.write is None: prwrite = " AND DEFAULT WRITE LEVEL"
        else: prwrite = " WITH WRITE LEVEL " + self.write.__repr__()
        return "CREATE ITEM " + self.handle + ' IN SOURCE ' + str(self.source) + prread + prwrite
    
    
class DeleteItem(Change):
    '''
    A virtual data lake change that deletes an item.
    '''
    
    def __init__(self, item):
        '''
        Construct a virtual data lake command that deletes an item.
        
        Parameters:
          * item: (`Itm`) the item to be deleted. 
        '''
        
        self.item = item
        
        
    def __repr__(self):
        return "DELETE " + self.item.__repr__()
    
    
class PutTriple(Change):
    '''
    A virtual data lake change that puts a triple.
    '''
    
    def __init__(self, subj, vrb, obj):
        '''
        Construct a virtual data lake change that puts a triple.
        
        This method ensures that a triple with the given subject,
        verb and object exists in the virtual data lake. If one
        exists already, a new one will not be created.
        
        Parameters:
          * subj: the subject. This can be specified by an `Itm` object, 
            an `ItmName` object, or a handle of an object created in a 
            previous change.
          * vrb: the verb. This can be specified by an `Itm` object, 
            an `ItmName` object, or a handle of an object created in a 
            previous change.
          * obj: the object. This can be an item, a Boolean, an integer, 
            a real number, a text object or a binary object. An item is
            specified by an `Itm` object, an `ItmName` object, or a 
            handle of an object created in a previous change. A Boolean
            is given as a bool, an integer as an int, a real number as a
            float, a text object as a string, and a binary object as either
            a bytes or a bytearray.
        '''
    
        self.subj = subj
        self.vrb = vrb
        self.obj = obj
        
        
    def __repr__(self):
        return "PUT TRIPLE(" + self.subj.__repr__() + ', ' + self.vrb.__repr__() + ', ' + self.obj.__repr__() + ')'
    
    
class RemoveTriples(Change):
    '''
    A virtual data lake change that removes one or more triples.
    '''
    
    def __init__(self, subj, vrb, obj=None):
        '''
        Construct a virtual data lake change that removes one or more triples.
        If an object is given, all triples with the given subject,
        verb and object are removed. (Normally, there will only be one
        such triple.) If no object is given, all triples with the given
        subject and verb are removed.
        
        Parameters:
          * subj: the subject. This can be specified by an `Itm` object 
            or an `ItmName` object.
          * vrb: the verb. This can be specified by an `Itm` object or 
            an `ItmName` object.
          * obj: the object. This can be an item, a Boolean, an integer, 
            a real number, a text object or a binary object. An item is
            specified by an `Itm` object or an `ItmName` object. A Boolean
            is given as a bool, an integer as an int, a real number as a
            float, a text object as a string, and a binary object as either
            a bytes or a bytearray.
        '''
    
        self.subj = subj
        self.vrb = vrb
        self.obj = obj
        
        
    def __repr__(self):
        if self.obj is None: printobj = 'ALL'
        else: printobj = self.obj.__repr__()
        return "REMOVE TRIPLES(" + self.subj.__repr__() + ', ' + self.vrb.__repr__() + ', ' + printobj + ')'
    
    
class SetUniqueObject(Change):
    '''
    A virtual data lake change that sets a unique object for a given
    subject and verb.
    '''
    
    def __init__(self, subj, vrb, obj):
        '''
        Construct a virtual data lake change that sets a unique object
        for a given subject and verb.
        
        This method ensures that a triple with the given subject,
        verb and object exists in the virtual data lake and that
        there are no other triples with the given subject and verb. 
        
        If a triple with the subject, verb and object exists already, 
        a new one need not be created. If more than one triple 
        with the given subject and verb exists, then all but one
        will be deleted.
        
        Parameters:
          * subj: the subject. This can be specified by an `Itm` object, 
            or an `ItmName` object.
          * vrb: the verb. This can be specified by an `Itm` object, 
            or an `ItmName` object.
          * obj: the object. This can be an item, a Boolean, an integer, 
            a real number, a text object or a binary object. An item is
            specified by an `Itm` object, an `ItmName` object, or a 
            handle of an object created in a previous change. A Boolean
            is given as a bool, an integer as an int, a real number as a
            float, a text object as a string, and a binary object as either
            a bytes or a bytearray.
        '''
    
        self.subj = subj
        self.vrb = vrb
        self.obj = obj
        
        
    def __repr__(self):
        return "SET UNIQUE OBJECT(" + self.subj.__repr__() + ', ' + self.vrb.__repr__() + ', ' + self.obj.__repr__() + ')'
        

class Unknown:
    '''
    An unknown value in a constraint.
    '''
    
    def __init__(self, name):
        '''
        Construct an Unknown
        
        Parameters:
          * name: the name of the unknown (e.g. 'x')
        '''
        
        self.name = name
        
    def __repr__(self):
        return self.name
        
    def __str__(self):
        return "UNKNOWN " + self.name

   
class Itm:
    '''
    A class whose objects represent items in the virtual data lake.
    
    An item in turn typically represents a real world object or property.
    '''
    def __init__(self, itemid):
        '''
        Construct a representation of an item.
        
        Parameters:
          * itemid(str): the identifier of an item.
          Item identifiers are assigned automatically by the virtual data lake.
          It is not possible to create an item with an arbitrary identifier.
          This constructor is used by functions that have obtained item identifiers
          by interacting with the virtual data lake or from user input. 
        '''
        self.itemid = itemid
         
    def __repr__(self):
        return str(self.itemid)
         
    def __str__(self):
        return "ITEM " + str(self.itemid)
    
    
class NamedItm:
    '''
    A class whose objects represent named items in the virtual data lake. 
    '''

    def __init__(self, source, name):
        '''
        Construct a representation of a named item that exists in the virtual data lake.
        (This does not create a named item in the virtual data lake if there is not
        already one with the given source and name.)
        
        Parameters:
          * source (int): the numeric identifier of the source containing the item.
          * name (str): the name of the item, unique within its source.
        '''
        
        self.source = source
        self.name = name
        
        
    def __repr__(self):
        return repr(self.source) + ':' + self.name
        
        
    def __str__(self):
        return "NAMED ITEM " + str(self.source) + ':' + self.name
    

class Triple:
    '''
    A class whose objects represent triples in the virtual data lake.
    
    A triple has a subject, a verb, and an object. 
    (Note that the word "object" is used here in the sense 
    of a component of a triple rather than a member of a Python class.) 
    The subject of a triple is an item.
    The verb of a triple is an item.
    The object of a triple can be: an item, a Boolean, an integer, a real number,
    a piece of text, or a sequence of bytes.
    '''
    def __init__(self, subj, vrb, obj):
        '''
        Construct a representation of a triple.
        
        Parameters:
          * subj(`Itm`) a representation of the subject of the triple.
          * vrb(`Itm`): a representation of the verb of the triple.
          * obj: the object of the triple. 
        This can be: an `Itm` object that represents an item, a bool,
        an int, a float, a str, a bytes object, or a bytearray object.
        '''
        self.subj = subj
        self.vrb = vrb
        self.obj = obj
         
    def __repr__(self):
        return '<' + repr(self.subj) + "," + repr(self.vrb) + "," + repr(self.obj) + '>'
         
    def __str__(self):
        return "TRIPLE " + str(self.subj) + ", " + str(self.vrb) + ", " + str(self.obj) 


class VdlException(RuntimeError):
    '''
    A run-time exception occurring during execution of a 
    virtual data lake access function.
    '''
    def __init__(self, *messages):
        super().__init__(*messages)
       
  
def __get_assignments_dict(response):
    '''
    Get a dict that contains a set of assignments of values to 
    unknowns from the content of a request response.
    
    Parameters:
      * content: the content of a request response returned by the
        imported requests module.
      
    Raises:
      * VdlException
      * PartException
      
    Returns:
      * a dict containing the set of values.
    '''
    
    parts = __get_parts(response)
    json_part = parts['json']
    if json_part is None: raise VdlException('No JSON part')
    assignments = json.loads(json_part.content().decode())
    if not isinstance(assignments, dict): raise VdlException('Not a value set')
    return __get_assigned_values(assignments, parts)
       
  
def __get_assignments_dict_list(response):
    '''
    Get a list of dicts, each of which contains a set of assignments
    of values to unknowns, from the content of a request response.
    
    Parameters:
      * content: the content of a request response returned by the
        imported requests module.
      
    Raises:
      * VdlException
      * PartException
      
    Returns:
      * a list of dicts, each of which contains a set of assignments
        of values to unknowns.
    '''
    
    parts = __get_parts(response)
    json_part = parts['json']
    if json_part is None: raise VdlException('No JSON part')
    assignments_list = json.loads(json_part.content().decode())
    if not isinstance(assignments_list, list): 
        raise VdlException('Not a list of value sets')
    values_list = []
    for assignments in assignments_list:
        if not isinstance(assignments, dict): raise VdlException('Not a value set')
        values_list.append(__get_assigned_values(assignments, parts))
    return values_list
    
    
def __get_assigned_values(assignments_dict, parts):
    '''
    Get the assigned values from an assignments dictionary in
    a multipart response.
    
    The content is returned in multipart form data format. One of
    the parts is named 'json' and contains one or more assignments 
    dictionaries. The assignments dictionaries assign python data
    values to the names. The python types of the python data values 
    depend on the virtual data lake types of the corresponding
    objects in the virtual data lake.
    
    A virtual data lake object can have one of these types: ITEM,
    BOOLEAN, INTEGRAL, REAL, TEXT, or BINARY. The corresponding
    python data types are `Itm`, bool, int, float, str, and bytes
    (or bytearray).
    
    Each entry of the assigments dictionaries is a name-value pair,
    where the value is another dictionary, which has two items: 
    'type' and 'value'. The 'type' is ITEM, BOOLEAN, INTEGRAL, REAL, 
    TEXT, or BINARY.
    
    If the type is ITEM, the value is a str that is an item identifier,
    and the assigned python data value is an `Itm` object whose itemid
    is that identifier. If the type is BOOLEAN, INTEGRAL, or REAL, 
    the value is a str representing a bool, int, or float, as appropriate,
    and the assigned python data value is the value represented by the str.
    If the type is TEXT, the value is a str and the assigned value is
    that str. If the type is BINARY, the value is a str that is the name 
    of a part of the multipart response whose content type is 
    application/octet-stream, and the assigned python value is its content.
    
    Parameters:
      * assignments_dict: an assignments dictionary in a multipart response.
      * parts: the parts of the multipart response
      
    Raises:
      * VdlException
      
    Returns:
      * a dict containing the assigned values.
    '''
    
    assigned_values = {}
    for assignment in assignments_dict.items():
        name = assignment[0]
        value_dict = assignment[1]
        if not isinstance(value_dict, dict): raise VdlException('Invalid JSON content')
        value_type = value_dict['type']
        value = value_dict['value']
        if name is None or value_type is None or value is None:
            raise VdlException('Invalid assignment in JSON content')
        if value_type == "ITEM": 
            assigned_values[name] = Itm(value)
        elif value_type == "BOOLEAN": 
            assigned_values[name] = (value.lower() == "true")
        elif value_type == "INTEGRAL": 
            assigned_values[name] = int(value)
        elif value_type == "REAL": 
            assigned_values[name] = float(value)
        elif value_type == "TEXT": 
            assigned_values[name] = value
        elif value_type == "BINARY": 
            part = parts[value]
            if part is None: raise VdlException('Missing binary content for: ' + name)
            assigned_values[name] = part.content()
        else: assigned_values[name] = str(value)
    return assigned_values
       
  
def __get_items_dict(response):
    '''
    Get a dictionary keyed by item handles whose values are items
    from the content of a request response.
    
    Parameters:
      * content: the content of a request response returned by the
        imported requests module.
      
    Raises:
      * VdlException
      * PartException
      
    Returns:
      * a dictionary keyed by item handles whose values are item 
      identifiers.
    '''
    
    parts = __get_parts(response)
    json_part = parts['json']
    if json_part is None: raise VdlException('No JSON part')
    itemids_dict = json.loads(json_part.content().decode())
    if not isinstance(itemids_dict, dict): 
        raise VdlException('Not a dict of item identifiers')
    items_dict = {}
    for handle in itemids_dict: items_dict[handle] = Itm(itemids_dict[handle])
    return items_dict


def __get_parts(response):
    '''
    Get the parts of a multi-part response
    
    Parameters:
      * response: a requests response object with
        multipart/form-data-encoded content
        
    Raises:
      * VdlException
      * PartException
      
    Returns:
      * the parts of the response, as a dictionary of
        parts.part objects
    '''
    
    content_type = response.headers['content-type']
    if content_type is None: raise VdlException('No content type header')
    content_type_line = 'Content-Type: ' + content_type 
    content_type_header = parts.Header(content_type_line)
    boundary = content_type_header.get_parm('boundary')
    return parts.get_parts(response.content, boundary)
       
       
def __get_quantum(spec, files):
    '''
    Get the representation of a quantity that can be given as a 
    particular item or piece of data or can be unknown.
    
    For a 'given' quantity, the value is a dict with entries
    'type' and 'value', where the type can be: 
      * ITEM: the value is then the item identifier or the item's 
        qualified name
      * BOOLEAN: the value is then a bool
      * INTEGRAL: the value is then an int
      * REAL: the value is then a float
      * TEXT: the value is then a str
      * BINARY: the value is then the name of an entry in the
        files dict, and the value of that entry is a bytes or 
        bytearray object.
      
    For an 'unknown' entry, the value is a dict with one entry:
    'unknown' whose value is the name of the unknown.
    
    Parameters:
      * spec: a specification of the subject, verb, or object.
        For a given item, this can be an `Itm` or `ItmName` object. 
        For a given piece of data, it can be a bool, int, float, or
        str. For a binary object, it is the name of a files dict entry
        whose value is a bytes, or bytearray object. For an unknown 
        quantity, it is an `Unknown` object.
      
    Raises:
      * VdlException if the specification is invalid
      
    Returns:
      * the representation of the specified quantity.
    '''
    
    if isinstance(spec, Unknown): return {'unknown': repr(spec)}
    else: return __get_datum(spec, files)
       
       
def __get_datum(spec, files):
    '''
    Get the representation of a quantity that is a particular item or 
    piece of data.
    
    The value is a dict with entries
    'type' and 'value', where the type can be: 
      * ITEM: the value is then the item identifier or the item's 
        qualified name
      * BOOLEAN: the value is then a bool
      * INTEGRAL: the value is then an int
      * REAL: the value is then a float
      * TEXT: the value is then a str
      * BINARY: the value is then the name of an entry in the
        files dict, and the value of that entry is a bytes or 
        bytearray object.
    
    Parameters:
      * spec: a specification of the subject, verb, or object.
        For a given item, this can be an `Itm` or `ItmName` object. 
        For a given piece of data, it can be a bool, int, float, or
        str. For a binary object, it is the name of a files dict entry
        whose value is a bytes, or bytearray object. 
      
    Raises:
      * VdlException if the specification is invalid
      
    Returns:
      * the representation of the specified quantity.
    '''
    
    if isinstance(spec, Itm): return {'type': 'ITEM', 'value': repr(spec)}
    elif isinstance(spec, NamedItm): return {'type': 'ITEM', 'value': repr(spec)}
    elif isinstance(spec, bool): return {'type': 'BOOLEAN', 'value': repr(spec)}
    elif isinstance(spec, int): return {'type': 'INTEGRAL', 'value': repr(spec)}
    elif isinstance(spec, float): return {'type': 'REAL', 'value': repr(spec)}
    elif isinstance(spec, str): return {'type': 'TEXT', 'value': spec}
    elif isinstance(spec, bytes) or isinstance(spec, bytearray):
        i = len(files)
        files.update({'b'+repr(i): ('b'+repr(i), spec, 'application/octet-stream')})
        return {'type': 'BINARY', 'value': 'b'+repr(i)}
    else: raise VdlException('Invalid specification: ' + str(spec))
        
                        
def __handle_http_error(response):
    '''
    Handle an error that occurs during an HTTP communication
    with a virtual data lake. The error may occur in transmission
    or during execution of the request by the virtual data lake.
    
    Parameters:
      * response the response to the HTTP request returned by the 
        imported requests module   
      
    Raises:
      * VdlException
    '''
    
    msg = None
    try:
        msg = response.json()['error']
    except:
        msg = str(response.status_code) + ': ' + response.reason
    raise VdlException(msg)
