SickGear/lib/imdb/parser/sql/objectadapter.py
echel0n 2dcd26e69c Update imdbpy libs to v5.0
Fixed invalid indexer id issues for TVRage shows.

Fixed issues for getting posters and backdrops for TVRage shows.

We now convert XML straight to a dict object for Indexer APIs, improved overall performance api's

Fixed issues with TVRage shows and displaying genre's properly.
2014-05-28 23:09:38 -07:00

211 lines
7.8 KiB
Python

"""
parser.sql.objectadapter module (imdb.parser.sql package).
This module adapts the SQLObject ORM to the internal mechanism.
Copyright 2008-2010 Davide Alberani <da@erlug.linux.it>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import sys
import logging
from sqlobject import *
from sqlobject.sqlbuilder import ISNULL, ISNOTNULL, AND, OR, IN, CONTAINSSTRING
from dbschema import *
_object_logger = logging.getLogger('imdbpy.parser.sql.object')
# Maps our placeholders to SQLAlchemy's column types.
MAP_COLS = {
INTCOL: IntCol,
UNICODECOL: UnicodeCol,
STRINGCOL: StringCol
}
# Exception raised when Table.get(id) returns no value.
NotFoundError = SQLObjectNotFound
# class method to be added to the SQLObject class.
def addIndexes(cls, ifNotExists=True):
"""Create all required indexes."""
for col in cls._imdbpySchema.cols:
if col.index:
idxName = col.index
colToIdx = col.name
if col.indexLen:
colToIdx = {'column': col.name, 'length': col.indexLen}
if idxName in [i.name for i in cls.sqlmeta.indexes]:
# Check if the index is already present.
continue
idx = DatabaseIndex(colToIdx, name=idxName)
cls.sqlmeta.addIndex(idx)
try:
cls.createIndexes(ifNotExists)
except dberrors.OperationalError, e:
_object_logger.warn('Skipping creation of the %s.%s index: %s' %
(cls.sqlmeta.table, col.name, e))
addIndexes = classmethod(addIndexes)
# Global repository for "fake" tables with Foreign Keys - need to
# prevent troubles if addForeignKeys is called more than one time.
FAKE_TABLES_REPOSITORY = {}
def _buildFakeFKTable(cls, fakeTableName):
"""Return a "fake" table, with foreign keys where needed."""
countCols = 0
attrs = {}
for col in cls._imdbpySchema.cols:
countCols += 1
if col.name == 'id':
continue
if not col.foreignKey:
# A non-foreign key column - add it as usual.
attrs[col.name] = MAP_COLS[col.kind](**col.params)
continue
# XXX: Foreign Keys pointing to TableName.ColName not yet supported.
thisColName = col.name
if thisColName.endswith('ID'):
thisColName = thisColName[:-2]
fks = col.foreignKey.split('.', 1)
foreignTableName = fks[0]
if len(fks) == 2:
foreignColName = fks[1]
else:
foreignColName = 'id'
# Unused...
#fkName = 'fk_%s_%s_%d' % (foreignTableName, foreignColName,
# countCols)
# Create a Foreign Key column, with the correct references.
fk = ForeignKey(foreignTableName, name=thisColName, default=None)
attrs[thisColName] = fk
# Build a _NEW_ SQLObject subclass, with foreign keys, if needed.
newcls = type(fakeTableName, (SQLObject,), attrs)
return newcls
def addForeignKeys(cls, mapTables, ifNotExists=True):
"""Create all required foreign keys."""
# Do not even try, if there are no FK, in this table.
if not filter(None, [col.foreignKey for col in cls._imdbpySchema.cols]):
return
fakeTableName = 'myfaketable%s' % cls.sqlmeta.table
if fakeTableName in FAKE_TABLES_REPOSITORY:
newcls = FAKE_TABLES_REPOSITORY[fakeTableName]
else:
newcls = _buildFakeFKTable(cls, fakeTableName)
FAKE_TABLES_REPOSITORY[fakeTableName] = newcls
# Connect the class with foreign keys.
newcls.setConnection(cls._connection)
for col in cls._imdbpySchema.cols:
if col.name == 'id':
continue
if not col.foreignKey:
continue
# Get the SQL that _WOULD BE_ run, if we had to create
# this "fake" table.
fkQuery = newcls._connection.createReferenceConstraint(newcls,
newcls.sqlmeta.columns[col.name])
if not fkQuery:
# Probably the db doesn't support foreign keys (SQLite).
continue
# Remove "myfaketable" to get references to _real_ tables.
fkQuery = fkQuery.replace('myfaketable', '')
# Execute the query.
newcls._connection.query(fkQuery)
# Disconnect it.
newcls._connection.close()
addForeignKeys = classmethod(addForeignKeys)
# Module-level "cache" for SQLObject classes, to prevent
# "class TheClass is already in the registry" errors, when
# two or more connections to the database are made.
# XXX: is this the best way to act?
TABLES_REPOSITORY = {}
def getDBTables(uri=None):
"""Return a list of classes to be used to access the database
through the SQLObject ORM. The connection uri is optional, and
can be used to tailor the db schema to specific needs."""
DB_TABLES = []
for table in DB_SCHEMA:
if table.name in TABLES_REPOSITORY:
DB_TABLES.append(TABLES_REPOSITORY[table.name])
continue
attrs = {'_imdbpyName': table.name, '_imdbpySchema': table,
'addIndexes': addIndexes, 'addForeignKeys': addForeignKeys}
for col in table.cols:
if col.name == 'id':
continue
attrs[col.name] = MAP_COLS[col.kind](**col.params)
# Create a subclass of SQLObject.
# XXX: use a metaclass? I can't see any advantage.
cls = type(table.name, (SQLObject,), attrs)
DB_TABLES.append(cls)
TABLES_REPOSITORY[table.name] = cls
return DB_TABLES
def toUTF8(s):
"""For some strange reason, sometimes SQLObject wants utf8 strings
instead of unicode."""
return s.encode('utf_8')
def setConnection(uri, tables, encoding='utf8', debug=False):
"""Set connection for every table."""
kw = {}
# FIXME: it's absolutely unclear what we should do to correctly
# support unicode in MySQL; with some versions of SQLObject,
# it seems that setting use_unicode=1 is the _wrong_ thing to do.
_uriLower = uri.lower()
if _uriLower.startswith('mysql'):
kw['use_unicode'] = 1
#kw['sqlobject_encoding'] = encoding
kw['charset'] = encoding
# On some server configurations, we will need to explictly enable
# loading data from local files
kw['local_infile'] = 1
conn = connectionForURI(uri, **kw)
conn.debug = debug
# XXX: doesn't work and a work-around was put in imdbpy2sql.py;
# is there any way to modify the text_factory parameter of
# a SQLite connection?
#if uri.startswith('sqlite'):
# major = sys.version_info[0]
# minor = sys.version_info[1]
# if major > 2 or (major == 2 and minor > 5):
# sqliteConn = conn.getConnection()
# sqliteConn.text_factory = str
for table in tables:
table.setConnection(conn)
#table.sqlmeta.cacheValues = False
# FIXME: is it safe to set table._cacheValue to False? Looks like
# we can't retrieve correct values after an update (I think
# it's never needed, but...) Anyway, these are set to False
# for performance reason at insert time (see imdbpy2sql.py).
table._cacheValue = False
# Required by imdbpy2sql.py.
conn.paramstyle = conn.module.paramstyle
return conn