2to3 -f raise
[sfa.git] / sfa / storage / record.py
index 87d4126..9622268 100644 (file)
-##
-# Implements support for SFA records
-#
-# TODO: Use existing PLC database methods? or keep this separate?
-##
+from __future__ import print_function
 
+from sfa.util.sfatime import utcparse, datetime_to_string
 from types import StringTypes
+from datetime import datetime
+from sfa.util.xml import XML
 from sfa.trust.gid import GID
-from sfa.storage.parameter import Parameter
-from sfa.util.xrn import get_authority
-from sfa.storage.row import Row
-from sfa.util.xml import XML 
-from sfa.util.sfalogging import logger
-
-class SfaRecord(Row):
-    """ 
-    The SfaRecord class implements an SFA Record. A SfaRecord is a tuple
-    (Hrn, GID, Type, Info).
-    Hrn specifies the Human Readable Name of the object
-    GID is the GID of the object
-    Type is user | authority | slice | component
-    Info is comprised of the following sub-fields
-           pointer = a pointer to the record in the PL database
-    The pointer is interpreted depending on the type of the record. For example,
-    if the type=="user", then pointer is assumed to be a person_id that indexes
-    into the persons table.
-    A given HRN may have more than one record, provided that the records are
-    of different types.
-    """
 
-    table_name = 'sfa'
-    
-    primary_key = 'record_id'
+from sfa.util.sfalogging import logger
 
-    ### the wsdl generator assumes this is named 'fields'
-    internal_fields = {
-        'record_id': Parameter(int, 'An id that uniquely identifies this record', ro=True),
-        'pointer': Parameter(int, 'An id that uniquely identifies this record in an external database ')
-    }
+class Record:
 
-    fields = {
-        'authority': Parameter(str, "The authority for this record"),
-        'peer_authority': Parameter(str, "The peer authority for this record"),
-        'hrn': Parameter(str, "Human readable name of object"),
-        'gid': Parameter(str, "GID of the object"),
-        'type': Parameter(str, "Record type"),
-        'last_updated': Parameter(int, 'Date and time of last update', ro=True),
-        'date_created': Parameter(int, 'Date and time this record was created', ro=True),
-    }
-    all_fields = dict(fields.items() + internal_fields.items())
-    ##
-    # Create an SFA Record
-    #
-    # @param name if !=None, assign the name of the record
-    # @param gid if !=None, assign the gid of the record
-    # @param type one of user | authority | slice | component
-    # @param pointer is a pointer to a PLC record
-    # @param dict if !=None, then fill in this record from the dictionary
-
-    def __init__(self, hrn=None, gid=None, type=None, pointer=None, authority=None, peer_authority=None, dict=None, string=None):
-        self.dirty = True
-        self.hrn = None
-        self.gid = None
-        self.type = None
-        self.pointer = None
-        self.set_peer_auth(peer_authority)
-        self.set_authority(authority)
-        if hrn:
-            self.set_name(hrn)
-        if gid:
-            self.set_gid(gid)
-        if type:
-            self.set_type(type)
-        if pointer:
-            self.set_pointer(pointer)
+    def __init__(self, dict=None, xml_str=None):
         if dict:
             self.load_from_dict(dict)
-        if string:
-            self.load_from_string(string)
-
-
-    def validate_last_updated(self, last_updated):
-        return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
-        
-    def update(self, new_dict):
-        if isinstance(new_dict, list):
-            new_dict = new_dict[0]
-
-        # Convert any boolean strings to real bools
-        for key in new_dict:
-            if isinstance(new_dict[key], StringTypes):
-                if new_dict[key].lower() in ["true"]:
-                    new_dict[key] = True
-                elif new_dict[key].lower() in ["false"]:
-                    new_dict[key] = False
-        dict.update(self, new_dict)
-
-    ##
-    # Set the name of the record
-    #
-    # @param hrn is a string containing the HRN
-
-    def set_name(self, hrn):
-        """
-        Set the name of the record
-        """
-        self.hrn = hrn
-        self['hrn'] = hrn
-        self.dirty = True
-
-    def set_authority(self, authority):
-        """
-        Set the authority
-        """
-        if not authority:
-            authority = ""
-        self.authority = authority
-        self['authority'] = authority
-        self.dirty = True    
-        
-
-    ##
-    # Set the GID of the record
-    #
-    # @param gid is a GID object or the string representation of a GID object
-
-    def set_gid(self, gid):
-        """
-        Set the GID of the record
-        """
-
-        if isinstance(gid, StringTypes):
-            self.gid = gid
-            self['gid'] = gid
-        else:
-            self.gid = gid.save_to_string(save_parents=True)
-            self['gid'] = gid.save_to_string(save_parents=True)
-        self.dirty = True
-
-    ##
-    # Set the type of the record
-    #
-    # @param type is a string: user | authority | slice | component
-
-    def set_type(self, type):
-        """
-        Set the type of the record
-        """
-        self.type = type
-        self['type'] = type
-        self.dirty = True
-
-    ##
-    # Set the pointer of the record
-    #
-    # @param pointer is an integer containing the ID of a PLC record
-
-    def set_pointer(self, pointer):
-        """
-        Set the pointer of the record
-        """
-        self.pointer = pointer
-        self['pointer'] = pointer
-        self.dirty = True
-
-
-    def set_peer_auth(self, peer_authority):
-        self.peer_authority = peer_authority
-        self['peer_authority'] = peer_authority
-        self.dirty = True
-
-    ##
-    # Return the name (HRN) of the record
-
-    def get_name(self):
-        """
-        Return the name (HRN) of the record
-        """
-        return self.hrn
-
-    ##
-    # Return the type of the record
-
-    def get_type(self):
-        """
-        Return the type of the record
-        """
-        return self.type
-
-    ##
-    # Return the pointer of the record. The pointer is an integer that may be
-    # used to look up the record in the PLC database. The evaluation of pointer
-    # depends on the type of the record
-
-    def get_pointer(self):
-        """
-        Return the pointer of the record. The pointer is an integer that may be
-        used to look up the record in the PLC database. The evaluation of pointer
-        depends on the type of the record
-        """
-        return self.pointer
-
-    ##
-    # Return the GID of the record, in the form of a GID object
-    # TODO: not the best name for the function, because we have things called
-    # gidObjects in the Cred
-
-    def get_gid_object(self):
-        """
-        Return the GID of the record, in the form of a GID object
-        """
-        return GID(string=self.gid)
-
-    ##
-    # Returns the value of a field
-
-    def get_field(self, fieldname, default=None):
-        # sometimes records act like classes, and sometimes they act like dicts
-        try:
-            return getattr(self, fieldname)
-        except AttributeError:
-            try:
-                 return self[fieldname]
-            except KeyError:
-                 if default != None:
-                     return default
-                 else:
-                     raise
-
-    ##
-    # Returns a list of field names in this record. 
-
-    def get_field_names(self):
-        """
-        Returns a list of field names in this record.
-        """
-        return self.fields.keys()
-
-    ##
-    # Given a field name ("hrn", "gid", ...) return the value of that field.
-    #
-    # @param fieldname is the name of field to be returned
-
-    def get_field_value_string(self, fieldname):
-        """
-        Given a field name ("hrn", "gid", ...) return the value of that field.
-        """
-        if fieldname == "authority":
-            val = get_authority(self['hrn'])
-        else:
-            try:
-                val = getattr(self, fieldname)
-            except:
-                val = self[fieldname] 
-        if isinstance(val, str):
-            return "'" + str(val) + "'"
-        else:
-            return str(val)
-
-    ##
-    # Given a list of field names, return a list of values for those public.
-    #
-    # @param fieldnames is a list of field names
-
-    def get_field_value_strings(self, fieldnames):
-        """
-        Given a list of field names, return a list of values for those public.
-        """
-        return [ self.get_field_value_string (fieldname) for fieldname in fieldnames ]
-
-    ##
-    # Return the record in the form of a dictionary
-
-    def as_dict(self):
-        """
-        Return the record in the form of a dictionary
-        """
-        return dict(self)
-
-    ##
-    # Load the record from a dictionary
-    #
-    # @param dict dictionary to load record public from
-
-    def load_from_dict(self, dict):
-        """
-        Load the record from a dictionary 
-        """
-
-        self.set_name(dict['hrn'])
-        gidstr = dict.get("gid", None)
-        if gidstr:
-            self.set_gid(dict['gid'])
-
-        if "pointer" in dict:
-           self.set_pointer(dict['pointer'])
-
-        self.set_type(dict['type'])
-        self.update(dict)        
+        elif xml_str:
+            xml = XML(xml_str)
+            xml_dict = xml.todict()
+            self.load_from_dict(xml_dict)  
+
+    def get_field(self, field):
+        return self.__dict__.get(field, None)
+
+    # xxx fixme
+    # turns out the date_created field is received by the client as a 'created' int
+    # (and 'last_updated' does not make it at all)
+    # let's be flexible
+    def date_repr (self,fields):
+        if not isinstance(fields,list):
+            fields = [fields]
+        for field in fields:
+            value = getattr(self,field,None)
+            if isinstance (value,datetime):
+                return datetime_to_string (value)
+            elif isinstance (value,(int,float)):
+                return datetime_to_string(utcparse(value))
+        # fallback
+        return "** undef_datetime **"
     
-    ##
-    # Save the record to a string. The string contains an XML representation of
-    # the record.
-
-    def save_to_string(self):
-        """
-        Save the record to a string. The string contains an XML representation of
-        the record.
-        """
-        recorddict = self.as_dict()
-        filteredDict = dict([(key, val) for (key, val) in recorddict.iteritems() if key in self.fields.keys()])
-        record = XML('<record/>')
-        record.parse_dict(filteredDict)
-        str = record.toxml()
-        return str
-
-    ##
-    # Load the record from a string. The string is assumed to contain an XML
-    # representation of the record.
-
-    def load_from_string(self, str):
-        """
-        Load the record from a string. The string is assumed to contain an XML
-        representation of the record.
-        """
-        #dict = xmlrpclib.loads(str)[0][0]
-
-        record = XML(str)
-        self.load_from_dict(record.todict())
-
-    ##
-    # Dump the record to stdout
     #
-    # @param dump_parents if true, then the parents of the GID will be dumped
-
-    def dump(self, dump_parents=False):
-        """
-        Walk tree and dump records.
-        """
-        #print "RECORD", self.name
-        #print "        hrn:", self.name
-        #print "       type:", self.type
-        #print "        gid:"
-        #if (not self.gid):
-        #    print "        None"
-        #else:
-        #    self.get_gid_object().dump(8, dump_parents)
-        #print "    pointer:", self.pointer
-       
-        order = SfaRecord.fields.keys() 
-        for key in self.keys():
-            if key not in order:
-                order.append(key)
-        for key in order:
-            if key in self and key in self.fields:
-                if key in 'gid' and self[key]:
-                    gid = GID(string=self[key])
-                    print "     %s:" % key
-                    gid.dump(8, dump_parents)
-                else:    
-                    print "     %s: %s" % (key, self[key])
+    # need to filter out results, esp. wrt relationships
+    # exclude_types must be a tuple so we can use isinstance
+    # 
+    def record_to_dict (self, exclude_types=None):
+        if exclude_types is None:
+            exclude_types = ()
+        d = self.__dict__
+        def exclude (k, v):
+            return k.startswith('_') or isinstance (v, exclude_types)
+        keys = [ k for k, v in d.items() if not exclude(k, v) ]
+        return { k : d[k] for k in keys }
     
-    def summary_string(self):
-        return "Record(record_id=%s, hrn=%s, type=%s, authority=%s, pointer=%s)" % \
-                (self.get('record_id'), self.get('hrn'), self.get('type'), self.get('authority'), \
-                 self.get('pointer'))
-
-    def getdict(self):
-        return dict(self)
-   
-    def sync(self):
-        """ 
-        Sync this record with the database.
-        """ 
-        from sfa.storage.table import SfaTable
-        table = SfaTable()
-        filter = {}
-        if self.get('record_id'):
-            filter['record_id'] = self.get('record_id')
-        if self.get('hrn') and self.get('type'):
-            filter['hrn'] = self.get('hrn') 
-            filter['type'] = self.get('type')
-            if self.get('pointer'):
-                filter['pointer'] = self.get('pointer')
-        existing_records = table.find(filter)
-        if not existing_records:
-            table.insert(self)
+    def toxml(self):
+        return self.save_as_xml()
+
+    def load_from_dict (self, d):
+        for (k,v) in d.iteritems():
+            # experimental
+            if isinstance(v, StringTypes) and v.lower() in ['true']:
+                v = True
+            if isinstance(v, StringTypes) and v.lower() in ['false']:
+                v = False
+            setattr(self, k, v)
+
+    # in addition we provide convenience for converting to and from xml records
+    # for this purpose only, we need the subclasses to define 'fields' as either
+    # a list or a dictionary
+    def fields (self):
+        fields = self.__dict__.keys()
+        return fields
+
+    def save_as_xml (self):
+        # xxx not sure about the scope here
+        input_dict = dict( [ (key, getattr(self,key)) for key in self.fields() if getattr(self,key,None) ] )
+        xml_record = XML("<record />")
+        xml_record.parse_dict(input_dict)
+        return xml_record.toxml()
+
+    def dump(self, format=None, dump_parents=False, sort=False):
+        if not format:
+            format = 'text'
         else:
-            existing_record = existing_records[0]
-            self['record_id'] = existing_record['record_id']
-            table.update(self) 
-
-    def delete(self):
-        """
-        Remove record from the database.
-        """
-        from sfa.storage.table import SfaTable
-        table = SfaTable()
-        if self.get('record_id'):
-            filter['record_id'] = self.get('record_id')
-        if self.get('hrn') and self.get('type'):
-            filter['hrn'] = self.get('hrn')
-            filter['type'] = self.get('type')
-            if self.get('pointer'):
-                filter['pointer'] = self.get('pointer')
-        existing_records = table.find(filter)
-        for record in existing_records:
-            table.remove(record)
-
-class UserRecord(SfaRecord):
-
-    fields = {
-        'email': Parameter(str, 'email'),
-        'first_name': Parameter(str, 'First name'),
-        'last_name': Parameter(str, 'Last name'),
-        'phone': Parameter(str, 'Phone Number'),
-        'keys': Parameter(str, 'Public key'),
-        'slices': Parameter([str], 'List of slices this user belongs to'),
-        }
-    fields.update(SfaRecord.fields)
-    
-class SliceRecord(SfaRecord):
-    fields = {
-        'name': Parameter(str, 'Slice name'),
-        'url': Parameter(str, 'Slice url'),
-        'expires': Parameter(int, 'Date and time this slice exipres'),
-        'researcher': Parameter([str], 'List of users for this slice'),
-        'PI': Parameter([str], 'List of PIs responsible for this slice'),
-        'description': Parameter([str], 'Description of this slice'), 
-        }
-    fields.update(SfaRecord.fields)
-
-class NodeRecord(SfaRecord):
-    fields = {
-        'hostname': Parameter(str, 'This nodes dns name'),
-        'node_type': Parameter(str, 'Type of node this is'),
-        'node_type': Parameter(str, 'Type of node this is'),
-        'latitude': Parameter(str, 'latitude'),
-        'longitude': Parameter(str, 'longitude'),
-        }
-    fields.update(SfaRecord.fields)
-
-
-class AuthorityRecord(SfaRecord):
-    fields =  {
-        'name': Parameter(str, 'Name'),
-        'login_base': Parameter(str, 'login base'),
-        'enabled': Parameter(bool, 'Is this site enabled'),
-        'url': Parameter(str, 'URL'),
-        'nodes': Parameter([str], 'List of nodes at this site'),  
-        'operator': Parameter([str], 'List of operators'),
-        'researcher': Parameter([str], 'List of researchers'),
-        'PI': Parameter([str], 'List of Principal Investigators'),
-        }
-    fields.update(SfaRecord.fields)
-    
-
+            format = format.lower()
+        if format == 'text':
+            self.dump_text(dump_parents,sort=sort)
+        elif format == 'xml':
+            print(self.save_as_xml())
+        elif format == 'simple':
+            print(self.dump_simple())
+        else:
+            raise Exception("Invalid format %s" % format)
+
+    def dump_text(self, dump_parents=False, sort=False):
+        print(40*'=')
+        print("RECORD")
+        # print remaining fields
+        fields = self.fields()
+        if sort: fields.sort()
+        for attrib_name in fields:
+            attrib = getattr(self, attrib_name)
+            # skip internals
+            if attrib_name.startswith('_'):     continue
+            # skip callables
+            if callable (attrib):               continue
+            # handle gid 
+            if attrib_name == 'gid':
+                print("    gid:")      
+                print(GID(string=attrib).dump_string(8, dump_parents))
+            elif attrib_name in ['date created', 'last updated']:
+                print("    %s: %s" % (attrib_name, self.date_repr(attrib_name)))
+            else:
+                print("    %s: %s" % (attrib_name, attrib))
+
+    def dump_simple(self):
+        return "%s"%self