Index: python/MythTV/__init__.py =================================================================== --- python/MythTV/__init__.py (revision 25163) +++ python/MythTV/__init__.py (working copy) @@ -25,10 +25,11 @@ \ 'Record', 'Recorded', 'RecordedProgram', 'OldRecorded', 'Job', \ 'Channel', 'Guide', 'Video', 'VideoGrabber', 'InternetContent', \ - 'InternetContentArticles', 'InternetSource', \ + 'InternetContentArticles', 'InternetSource', 'Song', 'Album', \ + 'Artist', 'MusicPlaylist', 'MusicDirectory', \ \ 'MythBE', 'BEEventMonitor', 'MythSystemEvent', 'SystemEvent', \ - 'Frontend', 'MythDB', 'MythVideo', 'MythXML'] + 'Frontend', 'MythDB', 'MythVideo', 'MythXML', 'MythMusic'] import26 = """ import warnings @@ -61,16 +62,3 @@ __version__ = OWN_VERSION static.mysqldb = MySQLdb.__version__ -if __name__ == '__main__': - banner = 'MythTV Python interactive shell.' - import code - try: - import readline, rlcompleter - except: - pass - else: - readline.parse_and_bind("tab: complete") - banner += ' TAB completion available.' - namespace = globals().copy() - namespace.update(locals()) - code.InteractiveConsole(namespace).interact(banner) Index: python/MythTV/utility.py =================================================================== --- python/MythTV/utility.py (revision 25163) +++ python/MythTV/utility.py (working copy) @@ -42,16 +42,15 @@ log(log.IMPORTANT, 'Updating %s from %s to %s' % \ (schemaname, db.settings.NULL[self.schemavar], newver)) - c = db.cursor() - try: - for sql, values in updates: - c.execute(sql, values) - except Exception, e: - log(log.IMPORTANT, 'Update of %s failed' % self.schemavar) - raise MythDBError(MythError.DB_SCHEMAUPDATE, e.args) + with db as c: + try: + for sql, values in updates: + c.execute(sql, values) + except Exception, e: + log(log.IMPORTANT, 'Update of %s failed' % self.schemavar) + raise MythDBError(MythError.DB_SCHEMAUPDATE, e.args) - c.close() db.settings.NULL[self.schemavar] = newver class databaseSearch( object ): @@ -179,16 +178,14 @@ # process query query = self.buildQuery(where, joinbit=joinbit) - c = self.inst.cursor(self.inst.log) - if len(where) > 0: - c.execute(query, fields) - else: - c.execute(query) + with self.inst.cursor(self.inst.log) as c: + if len(where) > 0: + c.execute(query, fields) + else: + c.execute(query) - row = c.fetchone() - while row is not None: - yield self.dbclass.fromRaw(row, self.inst) - row = c.fetchone() + for row in c: + yield self.dbclass.fromRaw(row, self.inst) def buildJoinOn(self, i): if len(self.joins[i]) == 3: Index: python/MythTV/database.py =================================================================== --- python/MythTV/database.py (revision 25163) +++ python/MythTV/database.py (working copy) @@ -55,36 +55,51 @@ _schema_name = 'Database' _localvars = ['_field_order'] + _table = None + _where = None + _key = None + _wheredat = None + _setwheredat = None + @classmethod def getAllEntries(cls, db=None): """cls.getAllEntries() -> tuple of DBData objects""" + return cls._fromQuery("", ()) + + @classmethod + def _fromQuery(cls, where, args, db=None): db = DBCache(db) + with db as c: + c.execute("""SELECT * FROM %s %s""" \ + % (cls._table, where), args) + for row in c: + yield cls.fromRaw(row, db) - c = db.cursor() - count = c.execute("""SELECT * FROM %s""" % cls._table) - for i in xrange(count): - res = c.fetchone() - yield cls.fromRaw(res, db) - c.close() - @classmethod def fromRaw(cls, raw, db=None): dbdata = cls(None, db=db) DictData.__init__(dbdata, raw) dbdata._evalwheredat() - dbdata._postinit() return dbdata - def _postinit(self): pass + def _evalwheredat(self, wheredat=None): + if self._where is None: + if self._key is None: + raise MythError('Invalid DBData subclass') + self._where = ' AND '.join(['%s=%%s' % key for key in self._key]) + if self._setwheredat is None: + if self._key is None: + raise MythError('Invalid DBData subclass') + self._setwheredat = ''.join(['self.%s,' % key for key in self._key]) + if wheredat is None: + self._wheredat = eval(self._setwheredat) + else: + self._wheredat = tuple(wheredat) - def _evalwheredat(self): - self._wheredat = eval(self._setwheredat) - def _setDefs(self): self._field_order = self._db.tablefields[self._table] self._log = MythLog(self._logmodule) self._fillNone() - self._wheredat = None def __init__(self, data, db=None): dict.__init__(self) @@ -97,21 +112,19 @@ if data is None: pass elif None in data: pass else: - self._wheredat = tuple(data) + self._evalwheredat(data) self._pull() - self._postinit() def _pull(self): """Updates table with data pulled from database.""" - c = self._db.cursor(self._log) - count = c.execute("""SELECT * FROM %s WHERE %s""" \ - % (self._table, self._where), self._wheredat) - if count == 0: - raise MythError('DBData() could not read from database') - elif count > 1: - raise MythError('DBData() could not find unique entry') - data = c.fetchone() - c.close() + with self._db.cursor(self._log) as c: + count = c.execute("""SELECT * FROM %s WHERE %s""" \ + % (self._table, self._where), self._wheredat) + if count == 0: + raise MythError('DBData() could not read from database') + elif count > 1: + raise MythError('DBData() could not find unique entry') + data = c.fetchone() DictData.update(self, self._process(data)) def copy(self): @@ -175,10 +188,9 @@ self._fillNone() dict.update(self, self._defaults) - def _postinit(self): - DBData._postinit(self) - if self._wheredat is not None: - self._origdata = dict(self) + def _evalwheredat(self, wheredat=None): + DBData._evalwheredat(self, wheredat) + self._origdata = dict(self) def __init__(self, data=None, db=None): DBData.__init__(self, data, db) @@ -188,9 +200,9 @@ data = self._sanitize(data, False) dict.update(self, data) - def create(self,data=None): + def _create(self,data=None): """ - obj.create(data=None) -> new database row + obj._create(data=None) -> new database row Creates a new database entry using given information. Will add any information in 'data' dictionary to local information @@ -206,15 +218,29 @@ for key in data.keys(): if data[key] is None: del data[key] - c = self._db.cursor(self._log) fields = ', '.join(data.keys()) format_string = ', '.join(['%s' for d in data.values()]) - c.execute("""INSERT INTO %s (%s) VALUES(%s)""" \ + with self._db.cursor(self._log) as c: + c.execute("""INSERT INTO %s (%s) VALUES(%s)""" \ % (self._table, fields, format_string), data.values()) - intid = c.lastrowid - c.close() + intid = c.lastrowid return intid + def create(self, data=None): + intid = self.create(data) + if self._key is None: + self._evalwheredat() + elif len(self._key) > 1: + self._evalwheredat() + elif 'auto_increment' in \ + self._db.tablefields[self._table][self._key[0]].extra: + # key is an autoincrement field, pull from line creation id + self._evalwheredat([intid]) + else: + self._evalwheredat() + self._pull() + return self + def _pull(self): DBData._pull(self) self._origdata = dict.copy(self) @@ -222,7 +248,6 @@ def _push(self): if (self._where is None) or (self._wheredat is None): return - c = self._db.cursor(self._log) data = self._sanitize(dict(self)) for key, value in data.items(): if value == self._origdata[key]: @@ -235,9 +260,9 @@ sql_values = data.values() sql_values.extend(self._wheredat) - c.execute("""UPDATE %s SET %s WHERE %s""" \ + with self._db.cursor(self._log) as c: + c.execute("""UPDATE %s SET %s WHERE %s""" \ % (self._table, format_string, self._where), sql_values) - c.close() self._pull() def update(self, *args, **keywords): @@ -263,10 +288,9 @@ if (self._where is None) or \ (self._wheredat is None): return - c = self._db.cursor(self._log) - c.execute("""DELETE FROM %s WHERE %s""" \ + with self._db.cursor(self._log) as c: + c.execute("""DELETE FROM %s WHERE %s""" \ % (self._table, self._where), self._wheredat) - c.close() class DBDataRef( list ): """ @@ -357,14 +381,14 @@ def _populate(self, force=False): if self._populated and (not force): return - c = self._db.cursor() - c.execute("""SELECT %s FROM %s WHERE %s""" % \ + with self._db as c: + c.execute("""SELECT %s FROM %s WHERE %s""" % \ (','.join(self._datfields), self._table, ' AND '.join(['%s=%%s' % f for f in self._ref])), self._refdat) - for row in c.fetchall(): - list.append(self, self.SubData(zip(self._datfields, row))) + for row in c: + list.append(self, self.SubData(zip(self._datfields, row))) self._populated = True self._origdata = self.deepcopy() @@ -404,32 +428,32 @@ diff = self^self._origdata if len(diff) == 0: return - c = self._db.cursor() fields = list(self._ref)+list(self._datfields) - # remove old entries - for v in (self._origdata&diff): - data = list(self._refdat)+v.values() - wf = [] - for i in range(len(data)): - if data[i] is None: - wf.append('%s IS %%s' % fields[i]) - else: - wf.append('%s=%%s' % fields[i]) - c.execute("""DELETE FROM %s WHERE %s""" % \ - (self._table, ' AND '.join(wf)), data) + with self._db as c: + # remove old entries + for v in (self._origdata&diff): + data = list(self._refdat)+v.values() + wf = [] + for i in range(len(data)): + if data[i] is None: + wf.append('%s IS %%s' % fields[i]) + else: + wf.append('%s=%%s' % fields[i]) + c.execute("""DELETE FROM %s WHERE %s""" % \ + (self._table, ' AND '.join(wf)), data) - # add new entries - data = [] - for v in (self&diff): # add new entries - data.append(list(self._refdat)+v.values()) - if len(data) > 0: - c.executemany("""INSERT INTO %s (%s) VALUES(%s)""" % \ - (self._table, - ','.join(fields), - ','.join(['%s' for a in fields])), data) - c.close() + data = [] + for v in (self&diff): + # add new entries + data.append(list(self._refdat)+v.values()) + if len(data) > 0: + c.executemany("""INSERT INTO %s (%s) VALUES(%s)""" % \ + (self._table, + ','.join(fields), + ','.join(['%s' for a in fields])), data) + self._origdata = self.deepcopy() def append(self, *data): @@ -494,8 +518,8 @@ datfields = self._datfields reffield = '%s.%s' % (self._table[1],self._cref[-1]) - c = self._db.cursor() - c.execute("""SELECT %s FROM %s JOIN %s ON %s WHERE %s""" % \ + with self._db as c: + c.execute("""SELECT %s FROM %s JOIN %s ON %s WHERE %s""" % \ (','.join(datfields+[reffield]), self._table[0], self._table[1], @@ -521,53 +545,53 @@ diff = self^self._origdata if len(diff) == 0: return - c = self._db.cursor() - # add new cross-references - newdata = self&diff - for d in newdata: - data = [d[a] for a in self._crdatfields] - fields = self._crdatfields - if c.execute("""SELECT %s FROM %s WHERE %s""" % \ - (self._cref[-1], self._table[1], - ' AND '.join(['%s=%%s' % f for f in fields])), - data): - # match found - d._cref = c.fetchone()[0] - else: + with self._db as c: + # add new cross-references + newdata = self&diff + for d in newdata: + data = [d[a] for a in self._crdatfields] + fields = self._crdatfields + if c.execute("""SELECT %s FROM %s WHERE %s""" % \ + (self._cref[-1], self._table[1], + ' AND '.join(['%s=%%s' % f for f in fields])), + data): + # match found + d._cref = c.fetchone()[0] + else: + c.execute("""INSERT INTO %s (%s) VALUES(%s)""" % \ + (self._table[1], + ','.join(self._crdatfields), + ','.join(['%s' for a in data])), + data) + d._cref = c.lastrowid + # add new references + for d in newdata: + data = [d[a] for a in self._rdatfields]+[d._cref]+self._refdat + fields = self._rdatfields+self._cref[:1]+self._ref c.execute("""INSERT INTO %s (%s) VALUES(%s)""" % \ - (self._table[1], - ','.join(self._crdatfields), - ','.join(['%s' for a in data])), - data) - d._cref = c.lastrowid - # add new references - for d in newdata: - data = [d[a] for a in self._rdatfields]+[d._cref]+self._refdat - fields = self._rdatfields+self._cref[:1]+self._ref - c.execute("""INSERT INTO %s (%s) VALUES(%s)""" % \ - (self._table[0], - ','.join(fields), - ','.join(['%s' for a in data])), - data) + (self._table[0], + ','.join(fields), + ','.join(['%s' for a in data])), + data) - # remove old references - olddata = self._origdata&diff - crefs = [d._cref for d in olddata] - for d in olddata: - data = [d[a] for a in self._rdatfields]+[d._cref]+self._refdat - fields = self._rdatfields+self._cref[:1]+self._ref - c.execute("""DELETE FROM %s WHERE %s""" % \ - (self._table[0], - ' AND '.join(['%s=%%s' % f for f in fields])), - data) - # remove unused cross-references - for cr in crefs: - c.execute("""SELECT COUNT(1) FROM %s WHERE %s=%%s""" % \ - (self._table[0], self._cref[0]), cr) - if c.fetchone()[0] == 0: - c.execute("""DELETE FROM %s WHERE %s=%%s""" % \ - (self._table[1], self._cref[-1]), cr) + # remove old references + olddata = self._origdata&diff + crefs = [d._cref for d in olddata] + for d in olddata: + data = [d[a] for a in self._rdatfields]+[d._cref]+self._refdat + fields = self._rdatfields+self._cref[:1]+self._ref + c.execute("""DELETE FROM %s WHERE %s""" % \ + (self._table[0], + ' AND '.join(['%s=%%s' % f for f in fields])), + data) + # remove unused cross-references + for cr in crefs: + c.execute("""SELECT COUNT(1) FROM %s WHERE %s=%%s""" % \ + (self._table[0], self._cref[0]), cr) + if c.fetchone()[0] == 0: + c.execute("""DELETE FROM %s WHERE %s=%%s""" % \ + (self._table[1], self._cref[-1]), cr) self._origdata = self.deepcopy() @@ -642,13 +666,12 @@ def __getitem__(self,key): if key not in self: # pull field list from database - c = self._db.cursor(self._log) - try: - c.execute("DESC %s" % (key,)) - except Exception, e: - MythDBError(MythDBError.DB_RAW, e.args) - self[key] = self._FieldData(c.fetchall()) - c.close() + with self._db.cursor(self._log) as c: + try: + c.execute("DESC %s" % (key,)) + except Exception, e: + raise MythDBError(MythDBError.DB_RAW, e.args) + self[key] = self._FieldData(c.fetchall()) # return cached information return OrdDict.__getitem__(self,key) @@ -678,43 +701,42 @@ def __getitem__(self, key): if key not in self: - c = self._db.cursor(self._log) - if c.execute("""SELECT data FROM settings - WHERE value=%%s - AND %s LIMIT 1""" \ - % self._where, key): - OrdDict.__setitem__(self, key, c.fetchone()[0]) - else: - return None - c.close() + with self._db.cursor(self._log) as c: + if c.execute("""SELECT data FROM settings + WHERE value=%%s + AND %s LIMIT 1""" \ + % self._where, key): + OrdDict.__setitem__(self, key, c.fetchone()[0]) + else: + return None return OrdDict.__getitem__(self, key) def __setitem__(self, key, value): - c = self._db.cursor(self._log) if value is None: if self[key] is None: return del self[key] else: - if self[key] is not None: - c.execute("""UPDATE settings - SET data=%%s - WHERE value=%%s - AND %s""" \ - % self._where, (value, key)) - else: - c.execute(self._insert, (key, value)) + with self._db.cursor(self._log) as c: + if self[key] is not None: + c.execute("""UPDATE settings + SET data=%%s + WHERE value=%%s + AND %s""" \ + % self._where, (value, key)) + else: + c.execute(self._insert, (key, value)) OrdDict.__setitem__(self,key,value) def __delitem__(self, key): if self[key] is None: return - c = self._db.cursor(self._log) - c.execute("""DELETE FROM settings - WHERE value=%%s - AND %s""" \ - % self._where, (key,)) - OrdDict.__delitem__(self, key) + with self._db.cursor(self._log) as c: + c.execute("""DELETE FROM settings + WHERE value=%%s + AND %s""" \ + % self._where, (key,)) + OrdDict.__delitem__(self, key) def get(self, value, default=None): res = self[value] @@ -722,11 +744,11 @@ return res def getall(self): - c = self._db.cursor(self._log) - c.execute("""SELECT value,data FROM settings - WHERE %s""" % self._where) - for k,v in c.fetchall(): - OrdDict.__setitem__(self, k, v) + with self._db.cursor(self._log) as c: + c.execute("""SELECT value,data FROM settings + WHERE %s""" % self._where) + for k,v in c.fetchall(): + OrdDict.__setitem__(self, k, v) return self.iteritems() _localvars = ['_field_order','_log','_db'] @@ -803,7 +825,7 @@ self.dbconn = dbconn self.ident = "sql://%s@%s:%d/" % \ - (dbconn['DBName'],dbconn['DBHostName'],dbconn['DBPort']) + (dbconn['DBName'], dbconn['DBHostName'], dbconn['DBPort']) if self.ident in self.shared: # reuse existing database connection self.db = self.shared[self.ident] @@ -879,15 +901,14 @@ def _check_schema(self, value, local, name='Database'): if self.settings is None: - c = self.cursor(self.log) - lines = c.execute("""SELECT data FROM settings + with self.cursor(self.log) as c: + lines = c.execute("""SELECT data FROM settings WHERE value LIKE(%s)""",(value,)) - if lines == 0: - c.close() - raise MythDBError(MythError.DB_SETTING, value) + if lines == 0: + c.close() + raise MythDBError(MythError.DB_SETTING, value) - sver = int(c.fetchone()[0]) - c.close() + sver = int(c.fetchone()[0]) else: sver = int(self.settings['NULL'][value]) @@ -904,32 +925,36 @@ -> tuple of StorageGroup objects groupname and hostname can be used as optional filters """ - c = self.cursor(self.log) - where = [] - wheredat = [] - if groupname: - where.append("groupname=%s") - wheredat.append(groupname) - if hostname: - where.append("hostname=%s") - wheredat.append(hostname) - if len(where): - where = 'WHERE '+' AND '.join(where) - count = c.execute("""SELECT * FROM storagegroup %s - ORDER BY id""" % where, wheredat) - else: - count = c.execute("""SELECT * FROM storagegroup - ORDER BY id""") + with self.cursor(self.log) as c: + where = [] + wheredat = [] + if groupname: + where.append("groupname=%s") + wheredat.append(groupname) + if hostname: + where.append("hostname=%s") + wheredat.append(hostname) + if len(where): + where = 'WHERE '+' AND '.join(where) + c.execute("""SELECT * FROM storagegroup %s + ORDER BY id""" % where, wheredat) + else: + c.execute("""SELECT * FROM storagegroup + ORDER BY id""") - for i in xrange(count): - row = c.fetchone() - yield StorageGroup.fromRaw(row, self) + for row in c: + yield StorageGroup.fromRaw(row, self) def cursor(self, log=None): if not log: log = self.log return self.db.cursor(log, self.cursorclass) + def __enter__(self): + return self.db.__enter__() + def __exit__(self, type, value, traceback): + self.db.__exit__(type, value, traceback) + class StorageGroup( DBData ): """ StorageGroup(id=None, db=None, raw=None) -> StorageGroup object Index: python/MythTV/dataheap.py =================================================================== --- python/MythTV/dataheap.py (revision 25163) +++ python/MythTV/dataheap.py (working copy) @@ -25,8 +25,11 @@ """ _table = 'record' - _where = 'recordid=%s' - _setwheredat = 'self.recordid,' + _key = ['recordid'] + + +# _where = 'recordid=%s' +# _setwheredat = 'self.recordid,' _defaults = {'recordid':None, 'type':RECTYPE.kAllRecord, 'title':u'Unknown', 'subtitle':'', 'description':'', 'category':'', 'station':'', 'seriesid':'', @@ -111,8 +114,8 @@ 'data' is a tuple containing (chanid, storagegroup) """ _table = 'recorded' - _where = 'chanid=%s AND starttime=%s' - _setwheredat = 'self.chanid,self.starttime' + _key = ['chanid','starttime'] + _defaults = {'title':u'Unknown', 'subtitle':'', 'description':'', 'category':'', 'hostname':'', 'bookmark':0, 'editing':0, 'cutlist':0, 'autoexpire':0, @@ -160,12 +163,11 @@ def __repr__(self): return str(self).encode('utf-8') - def _postinit(self): - DBDataWrite._postinit(self) - if self._wheredat is not None: - self.cast = self._Cast(self._wheredat, self._db) - self.seek = self._Seek(self._wheredat, self._db) - self.markup = self._Markup(self._wheredat, self._db) + def _evalwheredat(self, *args): + DBDataWrite._evalwheredat(self, *args) + self.cast = self._Cast(self._wheredat, self._db) + self.seek = self._Seek(self._wheredat, self._db) + self.markup = self._Markup(self._wheredat, self._db) @classmethod def fromProgram(cls, program): @@ -177,16 +179,6 @@ self.seek.commit() self.markup.commit() - def create(self, data=None): - """Recorded.create(data=None) -> Recorded object""" - DBDataWrite.create(self, data) - self._wheredat = (self.chanid,self.starttime) - self._pull() - self.cast = self._Cast(self._wheredat, self._db) - self.seek = self._Seek(self._wheredat, self._db) - self.markup = self._Markup(self._wheredat, self._db) - return self - def delete(self, force=False, rerecord=False): """ Recorded.delete(force=False, rerecord=False) -> retcode @@ -194,7 +186,7 @@ 'force' forces a delete if the file cannot be found. 'rerecord' sets the file as recordable in oldrecorded """ - return self.getProgram().delete(force, rerecord) + return Program.fromRecorded(self).delete(force, rerecord) def open(self, type='r'): """Recorded.open(type='r') -> file or FileTransfer object""" @@ -466,8 +458,9 @@ class Video( DBDataWrite, CMPVideo ): """Video(id=None, db=None, raw=None) -> Video object""" _table = 'videometadata' - _where = 'intid=%s' - _setwheredat = 'self.intid,' + _key = ['intid'] +# _where = 'intid=%s' +# _setwheredat = 'self.intid,' _defaults = {'subtitle':u'', 'director':u'Unknown', 'rating':u'NR', 'inetref':u'00000000', 'year':1895, 'userrating':0.0, @@ -495,6 +488,7 @@ c.close() def _cat_toname(self): + self._fill_cm() if self.category is not None: try: self.category = self._cm_toname[int(self.category)] @@ -503,6 +497,7 @@ self.category = 'none' def _cat_toid(self): + self._fill_cm() if self.category is not None: try: if self.category.lower() not in self._cm_toid: @@ -520,7 +515,6 @@ def _pull(self): DBDataWrite._pull(self) - self._fill_cm() self._cat_toname() def _push(self): @@ -544,38 +538,35 @@ res += u' - '+self.subtitle return u"