4 from PLC.Faults import *
5 from PLC.Parameter import Parameter
9 Representation of a row in a database table. To use, optionally
10 instantiate with a dict of values. Update as you would a
11 dict. Commit to the database with sync().
14 # Set this to the name of the table that stores the row.
17 # Set this to the name of the primary key of the table. It is
18 # assumed that the this key is a sequence if it is not set when
22 # Set this to the names of tables that reference this table's
26 # Set this to a dict of the valid fields of this object and their
27 # types. Not all fields (e.g., joined fields) may be updated via
31 def __init__(self, api, fields = {}):
32 dict.__init__(self, fields)
37 Validates values. Will validate a value with a custom function
38 if a function named 'validate_[key]' exists.
41 # Warn about mandatory fields
42 mandatory_fields = self.api.db.fields(self.table_name, notnull = True, hasdef = False)
43 for field in mandatory_fields:
44 if not self.has_key(field) or self[field] is None:
45 raise PLCInvalidArgument, field + " must be specified and cannot be unset in class %s"%self.__class__.__name__
47 # Validate values before committing
48 for key, value in self.iteritems():
49 if value is not None and hasattr(self, 'validate_' + key):
50 validate = getattr(self, 'validate_' + key)
51 self[key] = validate(value)
53 time_format = "%Y-%m-%d %H:%M:%S"
54 def validate_timestamp (self, timestamp, check_future=False):
55 # in case we try to sync the same object twice
56 if isinstance(timestamp,str):
57 # calendar.timegm is the inverse of time.gmtime, in that it computes in UTC
58 # surprisingly enough, no other method in the time module behaves this way
59 # this method is documented in the time module's documentation
60 timestamp = calendar.timegm (time.strptime (timestamp,Row.time_format))
61 human = time.strftime (Row.time_format, time.gmtime(timestamp))
62 if check_future and (timestamp < time.time()):
63 raise PLCInvalidArgument, "%s: date must be in the future"%human
66 def sync(self, commit = True, insert = None):
68 Flush changes back to the database.
71 # Validate all specified fields
74 # Filter out fields that cannot be set or updated directly
75 all_fields = self.api.db.fields(self.table_name)
76 fields = dict(filter(lambda (key, value): \
77 key in all_fields and \
78 (key not in self.fields or \
79 not isinstance(self.fields[key], Parameter) or \
80 not self.fields[key].ro),
83 # Parameterize for safety
85 values = [self.api.db.param(key, value) for (key, value) in fields.items()]
87 # If the primary key (usually an auto-incrementing serial
88 # identifier) has not been specified, or the primary key is the
89 # only field in the table, or insert has been forced.
90 if not self.has_key(self.primary_key) or \
91 all_fields == [self.primary_key] or \
94 sql = "INSERT INTO %s (%s) VALUES (%s)" % \
95 (self.table_name, ", ".join(keys), ", ".join(values))
98 columns = ["%s = %s" % (key, value) for (key, value) in zip(keys, values)]
99 sql = "UPDATE %s SET " % self.table_name + \
100 ", ".join(columns) + \
103 self.api.db.param(self.primary_key, self[self.primary_key]))
105 self.api.db.do(sql, fields)
107 if not self.has_key(self.primary_key):
108 self[self.primary_key] = self.api.db.last_insert_id(self.table_name, self.primary_key)
113 def delete(self, commit = True):
115 Delete row from its primary table, and from any tables that
119 assert self.primary_key in self
121 for table in self.join_tables + [self.table_name]:
122 if isinstance(table, tuple):
126 key = self.primary_key
128 sql = "DELETE FROM %s WHERE %s = %s" % \
130 self.api.db.param(self.primary_key, self[self.primary_key]))
132 self.api.db.do(sql, self)
139 Representation of row(s) in a database table.
142 def __init__(self, api, classobj, columns = None):
144 self.classobj = classobj
148 columns = classobj.fields
150 columns = filter(lambda x: x in classobj.fields, columns)
152 raise PLCInvalidArgument, "No valid return fields specified"
154 self.columns = columns
156 def sync(self, commit = True):
158 Flush changes back to the database.
164 def selectall(self, sql, params = None):
166 Given a list of rows from the database, fill ourselves with
170 for row in self.api.db.selectall(sql, params):
171 obj = self.classobj(self.api, row)
174 def dict(self, key_field = None):
176 Return ourself as a dict keyed on key_field.
179 if key_field is None:
180 key_field = self.classobj.primary_key
182 return dict([(obj[key_field], obj) for obj in self])