Merge branch 'refs/heads/develop'
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
|
from couchpotato.core.logger import CPLog
|
||||||
import hashlib
|
import hashlib
|
||||||
import os.path
|
import os.path
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
log = CPLog(__name__)
|
||||||
|
|
||||||
def getDataDir():
|
def getDataDir():
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
@@ -102,3 +105,15 @@ def natsortKey(s):
|
|||||||
|
|
||||||
def natcmp(a, b):
|
def natcmp(a, b):
|
||||||
return cmp(natsortKey(a), natsortKey(b))
|
return cmp(natsortKey(a), natsortKey(b))
|
||||||
|
|
||||||
|
def getTitle(library_dict):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
return library_dict['titles'][0]['title']
|
||||||
|
except:
|
||||||
|
log.error('Could not get title for %s' % library_dict['identifier'])
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
log.error('Could not get title for library item: %s' % library_dict)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class CoreNotifier(Notification):
|
|||||||
q.update({Notif.read: True})
|
q.update({Notif.read: True})
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
@@ -91,7 +91,7 @@ class CoreNotifier(Notification):
|
|||||||
ndict['type'] = 'notification'
|
ndict['type'] = 'notification'
|
||||||
notifications.append(ndict)
|
notifications.append(ndict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'empty': len(notifications) == 0,
|
'empty': len(notifications) == 0,
|
||||||
@@ -116,7 +116,7 @@ class CoreNotifier(Notification):
|
|||||||
ndict['time'] = time.time()
|
ndict['time'] = time.time()
|
||||||
self.messages.append(ndict)
|
self.messages.append(ndict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def frontend(self, type = 'notification', data = {}):
|
def frontend(self, type = 'notification', data = {}):
|
||||||
@@ -146,7 +146,7 @@ class CoreNotifier(Notification):
|
|||||||
ndict['type'] = 'notification'
|
ndict['type'] = 'notification'
|
||||||
messages.append(ndict)
|
messages.append(ndict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
self.messages = []
|
self.messages = []
|
||||||
return jsonified({
|
return jsonified({
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class History(Notification):
|
|||||||
)
|
)
|
||||||
db.add(history)
|
db.add(history)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class FileManager(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
type_dict = ft.to_dict()
|
type_dict = ft.to_dict()
|
||||||
db.close()
|
#db.close()
|
||||||
return type_dict
|
return type_dict
|
||||||
|
|
||||||
def getTypes(self):
|
def getTypes(self):
|
||||||
@@ -100,5 +100,5 @@ class FileManager(Plugin):
|
|||||||
for type_object in results:
|
for type_object in results:
|
||||||
types.append(type_object.to_dict())
|
types.append(type_object.to_dict())
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return types
|
return types
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class LibraryPlugin(Plugin):
|
|||||||
|
|
||||||
library_dict = l.to_dict(self.default_dict)
|
library_dict = l.to_dict(self.default_dict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return library_dict
|
return library_dict
|
||||||
|
|
||||||
def update(self, identifier, default_title = '', force = False):
|
def update(self, identifier, default_title = '', force = False):
|
||||||
@@ -130,7 +130,7 @@ class LibraryPlugin(Plugin):
|
|||||||
|
|
||||||
fireEvent('library.update_finish', data = library_dict)
|
fireEvent('library.update_finish', data = library_dict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return library_dict
|
return library_dict
|
||||||
|
|
||||||
def updateReleaseDate(self, identifier):
|
def updateReleaseDate(self, identifier):
|
||||||
@@ -144,7 +144,7 @@ class LibraryPlugin(Plugin):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
dates = library.info.get('release_date', {})
|
dates = library.info.get('release_date', {})
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return dates
|
return dates
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class MoviePlugin(Plugin):
|
|||||||
if m:
|
if m:
|
||||||
results = m.to_dict(self.default_dict)
|
results = m.to_dict(self.default_dict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None):
|
def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None):
|
||||||
@@ -177,7 +177,7 @@ class MoviePlugin(Plugin):
|
|||||||
})
|
})
|
||||||
movies.append(temp)
|
movies.append(temp)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return movies
|
return movies
|
||||||
|
|
||||||
def availableChars(self, status = ['active']):
|
def availableChars(self, status = ['active']):
|
||||||
@@ -203,7 +203,7 @@ class MoviePlugin(Plugin):
|
|||||||
if char not in chars:
|
if char not in chars:
|
||||||
chars += char
|
chars += char
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return chars
|
return chars
|
||||||
|
|
||||||
def listView(self):
|
def listView(self):
|
||||||
@@ -250,7 +250,7 @@ class MoviePlugin(Plugin):
|
|||||||
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
|
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
|
||||||
fireEventAsync('searcher.single', movie.to_dict(self.default_dict))
|
fireEventAsync('searcher.single', movie.to_dict(self.default_dict))
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
})
|
||||||
@@ -324,7 +324,7 @@ class MoviePlugin(Plugin):
|
|||||||
if (force_readd or do_search) and search_after:
|
if (force_readd or do_search) and search_after:
|
||||||
fireEventAsync('searcher.single', movie_dict)
|
fireEventAsync('searcher.single', movie_dict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return movie_dict
|
return movie_dict
|
||||||
|
|
||||||
|
|
||||||
@@ -371,7 +371,7 @@ class MoviePlugin(Plugin):
|
|||||||
movie_dict = m.to_dict(self.default_dict)
|
movie_dict = m.to_dict(self.default_dict)
|
||||||
fireEventAsync('searcher.single', movie_dict)
|
fireEventAsync('searcher.single', movie_dict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
})
|
})
|
||||||
@@ -426,7 +426,7 @@ class MoviePlugin(Plugin):
|
|||||||
else:
|
else:
|
||||||
fireEvent('movie.restatus', movie.id, single = True)
|
fireEvent('movie.restatus', movie.id, single = True)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def restatus(self, movie_id):
|
def restatus(self, movie_id):
|
||||||
@@ -437,7 +437,7 @@ class MoviePlugin(Plugin):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
|
|
||||||
m = db.query(Movie).filter_by(id = movie_id).first()
|
m = db.query(Movie).filter_by(id = movie_id).first()
|
||||||
if not m:
|
if not m or len(m.library.titles) == 0:
|
||||||
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -455,6 +455,6 @@ class MoviePlugin(Plugin):
|
|||||||
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
|
m.status_id = active_status.get('id') if move_to_wanted else done_status.get('id')
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class ProfilePlugin(Plugin):
|
|||||||
for profile in profiles:
|
for profile in profiles:
|
||||||
temp.append(profile.to_dict(self.to_dict))
|
temp.append(profile.to_dict(self.to_dict))
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -84,7 +84,7 @@ class ProfilePlugin(Plugin):
|
|||||||
|
|
||||||
profile_dict = p.to_dict(self.to_dict)
|
profile_dict = p.to_dict(self.to_dict)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True,
|
'success': True,
|
||||||
'profile': profile_dict
|
'profile': profile_dict
|
||||||
@@ -95,7 +95,7 @@ class ProfilePlugin(Plugin):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
default = db.query(Profile).first()
|
default = db.query(Profile).first()
|
||||||
default_dict = default.to_dict(self.to_dict)
|
default_dict = default.to_dict(self.to_dict)
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return default_dict
|
return default_dict
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class ProfilePlugin(Plugin):
|
|||||||
order += 1
|
order += 1
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
@@ -138,7 +138,7 @@ class ProfilePlugin(Plugin):
|
|||||||
message = 'Failed deleting Profile: %s' % e
|
message = 'Failed deleting Profile: %s' % e
|
||||||
log.error(message)
|
log.error(message)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': success,
|
'success': success,
|
||||||
@@ -187,5 +187,5 @@ class ProfilePlugin(Plugin):
|
|||||||
|
|
||||||
order += 1
|
order += 1
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class QualityPlugin(Plugin):
|
|||||||
q = mergeDicts(self.getQuality(quality.identifier), quality.to_dict())
|
q = mergeDicts(self.getQuality(quality.identifier), quality.to_dict())
|
||||||
temp.append(q)
|
temp.append(q)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def single(self, identifier = ''):
|
def single(self, identifier = ''):
|
||||||
@@ -80,7 +80,7 @@ class QualityPlugin(Plugin):
|
|||||||
if quality:
|
if quality:
|
||||||
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return quality_dict
|
return quality_dict
|
||||||
|
|
||||||
def getQuality(self, identifier):
|
def getQuality(self, identifier):
|
||||||
@@ -100,7 +100,7 @@ class QualityPlugin(Plugin):
|
|||||||
setattr(quality, params.get('value_type'), params.get('value'))
|
setattr(quality, params.get('value_type'), params.get('value'))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
@@ -152,7 +152,7 @@ class QualityPlugin(Plugin):
|
|||||||
order += 1
|
order += 1
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def guess(self, files, extra = {}):
|
def guess(self, files, extra = {}):
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class Release(Plugin):
|
|||||||
|
|
||||||
fireEvent('movie.restatus', movie.id)
|
fireEvent('movie.restatus', movie.id)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ class Release(Plugin):
|
|||||||
rel.delete()
|
rel.delete()
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
@@ -126,7 +126,7 @@ class Release(Plugin):
|
|||||||
rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id')
|
rel.status_id = available_status.get('id') if rel.status_id is ignored_status.get('id') else ignored_status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
@@ -153,14 +153,14 @@ class Release(Plugin):
|
|||||||
'files': {}
|
'files': {}
|
||||||
}), manual = True)
|
}), manual = True)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': True
|
'success': True
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
log.error('Couldn\'t find release with id: %s' % id)
|
log.error('Couldn\'t find release with id: %s' % id)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return jsonified({
|
return jsonified({
|
||||||
'success': False
|
'success': False
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from couchpotato.api import addApiView
|
|||||||
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
from couchpotato.core.helpers.request import jsonified
|
from couchpotato.core.helpers.request import jsonified
|
||||||
from couchpotato.core.helpers.variable import getExt, mergeDicts
|
from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Library, File, Profile
|
from couchpotato.core.settings.model import Library, File, Profile
|
||||||
@@ -82,8 +82,10 @@ class Renamer(Plugin):
|
|||||||
remove_files = []
|
remove_files = []
|
||||||
remove_releases = []
|
remove_releases = []
|
||||||
|
|
||||||
|
movie_title = getTitle(group['library'])
|
||||||
|
|
||||||
# Add _UNKNOWN_ if no library item is connected
|
# Add _UNKNOWN_ if no library item is connected
|
||||||
if not group['library']:
|
if not group['library'] or not movie_title:
|
||||||
if group['dirname']:
|
if group['dirname']:
|
||||||
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_UNKNOWN_%s' % group['dirname'])
|
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_UNKNOWN_%s' % group['dirname'])
|
||||||
else: # Add it to filename
|
else: # Add it to filename
|
||||||
@@ -100,12 +102,13 @@ class Renamer(Plugin):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
library = group['library']
|
library = group['library']
|
||||||
|
movie_title = getTitle(library)
|
||||||
|
|
||||||
# Find subtitle for renaming
|
# Find subtitle for renaming
|
||||||
fireEvent('renamer.before', group)
|
fireEvent('renamer.before', group)
|
||||||
|
|
||||||
# Remove weird chars from moviename
|
# Remove weird chars from moviename
|
||||||
movie_name = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', group['library']['titles'][0]['title'])
|
movie_name = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', movie_title)
|
||||||
|
|
||||||
# Put 'The' at the end
|
# Put 'The' at the end
|
||||||
name_the = movie_name
|
name_the = movie_name
|
||||||
@@ -369,14 +372,14 @@ class Renamer(Plugin):
|
|||||||
fireEventAsync('renamer.after', group)
|
fireEventAsync('renamer.after', group)
|
||||||
|
|
||||||
# Notify on download
|
# Notify on download
|
||||||
download_message = 'Downloaded %s (%s)' % (group['library']['titles'][0]['title'], replacements['quality'])
|
download_message = 'Downloaded %s (%s)' % (movie_title, replacements['quality'])
|
||||||
fireEventAsync('movie.downloaded', message = download_message, data = group)
|
fireEventAsync('movie.downloaded', message = download_message, data = group)
|
||||||
|
|
||||||
# Break if CP wants to shut down
|
# Break if CP wants to shut down
|
||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
self.renaming_started = False
|
self.renaming_started = False
|
||||||
|
|
||||||
def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = ''):
|
def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = ''):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from couchpotato.core.settings.model import File
|
|||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
from enzyme.exceptions import NoParserError, ParseError
|
from enzyme.exceptions import NoParserError, ParseError
|
||||||
from guessit import guess_movie_info
|
from guessit import guess_movie_info
|
||||||
from subliminal.videos import scan, Video
|
from subliminal.videos import Video
|
||||||
import enzyme
|
import enzyme
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -455,7 +455,7 @@ class Scanner(Plugin):
|
|||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
# Search based on OpenSubtitleHash
|
# Search based on OpenSubtitleHash
|
||||||
if not imdb_id and not group['is_dvd']:
|
if not imdb_id and not group['is_dvd']:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from couchpotato.core.event import addEvent
|
from couchpotato.core.event import addEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
|
from couchpotato.core.helpers.variable import getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.plugins.score.scores import nameScore, nameRatioScore, \
|
from couchpotato.core.plugins.score.scores import nameScore, nameRatioScore, \
|
||||||
@@ -35,6 +36,6 @@ class Score(Plugin):
|
|||||||
score += providerScore(nzb['provider'])
|
score += providerScore(nzb['provider'])
|
||||||
|
|
||||||
# Duplicates in name
|
# Duplicates in name
|
||||||
score += duplicateScore(nzb['name'], movie['library']['titles'][0]['title'])
|
score += duplicateScore(nzb['name'], getTitle(movie['library']))
|
||||||
|
|
||||||
return score
|
return score
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from couchpotato import get_session
|
from couchpotato import get_session
|
||||||
from couchpotato.core.event import addEvent, fireEvent
|
from couchpotato.core.event import addEvent, fireEvent
|
||||||
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
from couchpotato.core.helpers.encoding import simplifyString, toUnicode
|
||||||
from couchpotato.core.helpers.variable import md5, getImdb
|
from couchpotato.core.helpers.variable import md5, getImdb, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.plugins.base import Plugin
|
from couchpotato.core.plugins.base import Plugin
|
||||||
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
|
||||||
@@ -61,7 +61,7 @@ class Searcher(Plugin):
|
|||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
self.in_progress = False
|
self.in_progress = False
|
||||||
|
|
||||||
def single(self, movie):
|
def single(self, movie):
|
||||||
@@ -78,7 +78,10 @@ class Searcher(Plugin):
|
|||||||
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
|
||||||
available_status = fireEvent('status.get', 'available', single = True)
|
available_status = fireEvent('status.get', 'available', single = True)
|
||||||
|
|
||||||
default_title = movie['library']['titles'][0]['title']
|
default_title = getTitle(movie['library'])
|
||||||
|
if not default_title:
|
||||||
|
return
|
||||||
|
|
||||||
for quality_type in movie['profile']['types']:
|
for quality_type in movie['profile']['types']:
|
||||||
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases):
|
if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases):
|
||||||
log.info('To early to search for %s, %s' % (quality_type['quality']['identifier'], default_title))
|
log.info('To early to search for %s, %s' % (quality_type['quality']['identifier'], default_title))
|
||||||
@@ -101,6 +104,10 @@ class Searcher(Plugin):
|
|||||||
if len(sorted_results) == 0:
|
if len(sorted_results) == 0:
|
||||||
log.debug('Nothing found for %s in %s' % (default_title, quality_type['quality']['label']))
|
log.debug('Nothing found for %s in %s' % (default_title, quality_type['quality']['label']))
|
||||||
|
|
||||||
|
# Check if movie isn't deleted while searching
|
||||||
|
if not db.query(Movie).filter_by(id = movie.get('id')).first():
|
||||||
|
return
|
||||||
|
|
||||||
# Add them to this movie releases list
|
# Add them to this movie releases list
|
||||||
for nzb in sorted_results:
|
for nzb in sorted_results:
|
||||||
|
|
||||||
@@ -148,7 +155,7 @@ class Searcher(Plugin):
|
|||||||
if self.shuttingDown():
|
if self.shuttingDown():
|
||||||
break
|
break
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def download(self, data, movie, manual = False):
|
def download(self, data, movie, manual = False):
|
||||||
@@ -164,7 +171,7 @@ class Searcher(Plugin):
|
|||||||
rls.status_id = snatched_status.get('id')
|
rls.status_id = snatched_status.get('id')
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
log_movie = '%s (%s) in %s' % (movie['library']['titles'][0]['title'], movie['library']['year'], rls.quality.label)
|
log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
|
||||||
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
|
snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
|
||||||
log.info(snatch_message)
|
log.info(snatch_message)
|
||||||
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
|
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
|
||||||
@@ -191,7 +198,7 @@ class Searcher(Plugin):
|
|||||||
except Exception, e:
|
except Exception, e:
|
||||||
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
|
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
log.info('Tried to download, but none of the downloaders are enabled')
|
log.info('Tried to download, but none of the downloaders are enabled')
|
||||||
@@ -270,7 +277,7 @@ class Searcher(Plugin):
|
|||||||
if self.checkNFO(nzb['name'], movie['library']['identifier']):
|
if self.checkNFO(nzb['name'], movie['library']['identifier']):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], movie['library']['titles'][0]['title'], movie['library']['year']))
|
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'" % (nzb['name'], getTitle(movie['library']), movie['library']['year']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}, single_category = False):
|
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}, single_category = False):
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class StatusPlugin(Plugin):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
status = db.query(Status).filter_by(id = id).first()
|
status = db.query(Status).filter_by(id = id).first()
|
||||||
status_dict = status.to_dict()
|
status_dict = status.to_dict()
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
return status_dict
|
return status_dict
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ class StatusPlugin(Plugin):
|
|||||||
s = status.to_dict()
|
s = status.to_dict()
|
||||||
temp.append(s)
|
temp.append(s)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def add(self, identifier):
|
def add(self, identifier):
|
||||||
@@ -82,7 +82,7 @@ class StatusPlugin(Plugin):
|
|||||||
|
|
||||||
status_dict = s.to_dict()
|
status_dict = s.to_dict()
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return status_dict
|
return status_dict
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
@@ -102,5 +102,5 @@ class StatusPlugin(Plugin):
|
|||||||
s.label = toUnicode(label)
|
s.label = toUnicode(label)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Subtitle(Plugin):
|
|||||||
# get subtitles for those files
|
# get subtitles for those files
|
||||||
subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services)
|
subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services)
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
def searchSingle(self, group):
|
def searchSingle(self, group):
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
|
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
|
||||||
|
if (window.top != window.self) // Only run on top window
|
||||||
|
return;
|
||||||
|
|
||||||
var version = {{version}},
|
var version = {{version}},
|
||||||
host = '{{host}}',
|
host = '{{host}}',
|
||||||
api = '{{api}}';
|
api = '{{api}}';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from couchpotato.core.helpers.encoding import toUnicode
|
from couchpotato.core.helpers.encoding import toUnicode
|
||||||
|
from couchpotato.core.helpers.variable import getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.metadata.base import MetaDataBase
|
from couchpotato.core.providers.metadata.base import MetaDataBase
|
||||||
from xml.etree.ElementTree import Element, SubElement, tostring
|
from xml.etree.ElementTree import Element, SubElement, tostring
|
||||||
@@ -32,7 +33,7 @@ class XBMC(MetaDataBase):
|
|||||||
# Title
|
# Title
|
||||||
try:
|
try:
|
||||||
el = SubElement(nfoxml, 'title')
|
el = SubElement(nfoxml, 'title')
|
||||||
el.text = toUnicode(data['library']['titles'][0]['title'])
|
el.text = toUnicode(getTitle(data['library']))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class MovieResultModifier(Plugin):
|
|||||||
except:
|
except:
|
||||||
log.error('Tried getting more info on searched movies: %s' % traceback.format_exc())
|
log.error('Tried getting more info on searched movies: %s' % traceback.format_exc())
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
def checkLibrary(self, result):
|
def checkLibrary(self, result):
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class CouchPotatoApi(MovieProvider):
|
|||||||
db = get_session()
|
db = get_session()
|
||||||
active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all()
|
active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all()
|
||||||
movies = [x.library.identifier for x in active_movies]
|
movies = [x.library.identifier for x in active_movies]
|
||||||
db.close()
|
#db.close()
|
||||||
|
|
||||||
suggestions = self.suggest(movies, ignore)
|
suggestions = self.suggest(movies, ignore)
|
||||||
|
|
||||||
|
|||||||
@@ -64,8 +64,12 @@ class IMDBAPI(MovieProvider):
|
|||||||
movie_data = {}
|
movie_data = {}
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if isinstance(movie, (str, unicode)):
|
try:
|
||||||
movie = json.loads(movie)
|
if isinstance(movie, (str, unicode)):
|
||||||
|
movie = json.loads(movie)
|
||||||
|
except ValueError:
|
||||||
|
log.info('No proper json to decode')
|
||||||
|
return movie_data
|
||||||
|
|
||||||
if movie.get('Response') == 'Parse Error':
|
if movie.get('Response') == 'Parse Error':
|
||||||
return movie_data
|
return movie_data
|
||||||
|
|||||||
@@ -69,13 +69,7 @@ class TheMovieDb(MovieProvider):
|
|||||||
try:
|
try:
|
||||||
nr = 0
|
nr = 0
|
||||||
|
|
||||||
# Sort on returned score first when year is in q
|
for movie in raw:
|
||||||
if re.search('\s\d{4}', q):
|
|
||||||
movies = sorted(raw, key = lambda k: k['score'], reverse = True)
|
|
||||||
else:
|
|
||||||
movies = raw
|
|
||||||
|
|
||||||
for movie in movies:
|
|
||||||
results.append(self.parseMovie(movie))
|
results.append(self.parseMovie(movie))
|
||||||
|
|
||||||
nr += 1
|
nr += 1
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
|
from couchpotato.core.helpers.variable import getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
@@ -26,7 +27,7 @@ class Moovee(NZBProvider):
|
|||||||
if self.isDisabled() or not self.isAvailable(self.urls['search']) or quality.get('hd', False):
|
if self.isDisabled() or not self.isAvailable(self.urls['search']) or quality.get('hd', False):
|
||||||
return results
|
return results
|
||||||
|
|
||||||
q = '%s %s' % (movie['library']['titles'][0]['title'], quality.get('identifier'))
|
q = '%s %s' % (getTitle(movie['library']), quality.get('identifier'))
|
||||||
url = self.urls['search'] % tryUrlencode(q)
|
url = self.urls['search'] % tryUrlencode(q)
|
||||||
|
|
||||||
cache_key = 'moovee.%s' % q
|
cache_key = 'moovee.%s' % q
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from BeautifulSoup import BeautifulSoup
|
from BeautifulSoup import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -25,7 +25,7 @@ class Mysterbin(NZBProvider):
|
|||||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
||||||
return results
|
return results
|
||||||
|
|
||||||
q = '"%s" %s %s' % (movie['library']['titles'][0]['title'], movie['library']['year'], quality.get('identifier'))
|
q = '"%s" %s %s' % (getTitle(movie['library']), movie['library']['year'], quality.get('identifier'))
|
||||||
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
||||||
q = '%s -%s' % (q, ignored.strip())
|
q = '%s -%s' % (q, ignored.strip())
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from BeautifulSoup import BeautifulSoup
|
|||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -27,7 +27,7 @@ class NZBClub(NZBProvider, RSS):
|
|||||||
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
if self.isDisabled() or not self.isAvailable(self.urls['search']):
|
||||||
return results
|
return results
|
||||||
|
|
||||||
q = '"%s" %s %s' % (movie['library']['titles'][0]['title'], movie['library']['year'], quality.get('identifier'))
|
q = '"%s" %s %s' % (getTitle(movie['library']), movie['library']['year'], quality.get('identifier'))
|
||||||
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
for ignored in Env.setting('ignored_words', 'searcher').split(','):
|
||||||
q = '%s -%s' % (q, ignored.strip())
|
q = '%s -%s' % (q, ignored.strip())
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from BeautifulSoup import BeautifulSoup
|
|||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
|
||||||
from couchpotato.core.helpers.rss import RSS
|
from couchpotato.core.helpers.rss import RSS
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
from couchpotato.environment import Env
|
from couchpotato.environment import Env
|
||||||
@@ -29,7 +29,7 @@ class NzbIndex(NZBProvider, RSS):
|
|||||||
if self.isDisabled() or not self.isAvailable(self.urls['api']):
|
if self.isDisabled() or not self.isAvailable(self.urls['api']):
|
||||||
return results
|
return results
|
||||||
|
|
||||||
q = '%s %s %s' % (movie['library']['titles'][0]['title'], movie['library']['year'], quality.get('identifier'))
|
q = '%s %s %s' % (getTitle(movie['library']), movie['library']['year'], quality.get('identifier'))
|
||||||
arguments = tryUrlencode({
|
arguments = tryUrlencode({
|
||||||
'q': q,
|
'q': q,
|
||||||
'age': Env.setting('retention', 'nzb'),
|
'age': Env.setting('retention', 'nzb'),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.nzb.base import NZBProvider
|
from couchpotato.core.providers.nzb.base import NZBProvider
|
||||||
import re
|
import re
|
||||||
@@ -25,7 +25,7 @@ class X264(NZBProvider):
|
|||||||
if self.isDisabled() or not self.isAvailable(self.urls['search'].split('requests')[0]) or not quality.get('hd', False):
|
if self.isDisabled() or not self.isAvailable(self.urls['search'].split('requests')[0]) or not quality.get('hd', False):
|
||||||
return results
|
return results
|
||||||
|
|
||||||
q = '%s %s %s' % (movie['library']['titles'][0]['title'], movie['library']['year'], quality.get('identifier'))
|
q = '%s %s %s' % (getTitle(movie['library']), movie['library']['year'], quality.get('identifier'))
|
||||||
url = self.urls['search'] % tryUrlencode(q)
|
url = self.urls['search'] % tryUrlencode(q)
|
||||||
|
|
||||||
cache_key = 'x264.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
cache_key = 'x264.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from BeautifulSoup import BeautifulSoup
|
from BeautifulSoup import BeautifulSoup
|
||||||
from couchpotato.core.event import fireEvent
|
from couchpotato.core.event import fireEvent
|
||||||
from couchpotato.core.helpers.variable import tryInt
|
from couchpotato.core.helpers.variable import tryInt, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.torrent.base import TorrentProvider
|
from couchpotato.core.providers.torrent.base import TorrentProvider
|
||||||
import StringIO
|
import StringIO
|
||||||
@@ -38,7 +38,7 @@ class KickAssTorrents(TorrentProvider):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
cache_key = 'kickasstorrents.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
cache_key = 'kickasstorrents.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))
|
||||||
data = self.getCache(cache_key, self.urls['search'] % (movie['library']['titles'][0]['title'], movie['library']['identifier'].replace('tt', '')))
|
data = self.getCache(cache_key, self.urls['search'] % (getTitle(movie['library']), movie['library']['identifier'].replace('tt', '')))
|
||||||
if data:
|
if data:
|
||||||
|
|
||||||
cat_ids = self.getCatId(quality['identifier'])
|
cat_ids = self.getCatId(quality['identifier'])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from BeautifulSoup import SoupStrainer, BeautifulSoup
|
from BeautifulSoup import SoupStrainer, BeautifulSoup
|
||||||
from couchpotato.core.helpers.encoding import tryUrlencode
|
from couchpotato.core.helpers.encoding import tryUrlencode
|
||||||
from couchpotato.core.helpers.variable import mergeDicts
|
from couchpotato.core.helpers.variable import mergeDicts, getTitle
|
||||||
from couchpotato.core.logger import CPLog
|
from couchpotato.core.logger import CPLog
|
||||||
from couchpotato.core.providers.trailer.base import TrailerProvider
|
from couchpotato.core.providers.trailer.base import TrailerProvider
|
||||||
from string import letters, digits
|
from string import letters, digits
|
||||||
@@ -19,7 +19,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
|
|
||||||
def search(self, group):
|
def search(self, group):
|
||||||
|
|
||||||
movie_name = group['library']['titles'][0]['title']
|
movie_name = getTitle(group['library'])
|
||||||
|
|
||||||
url = self.urls['api'] % self.movieUrlName(movie_name)
|
url = self.urls['api'] % self.movieUrlName(movie_name)
|
||||||
data = self.getCache('hdtrailers.%s' % group['library']['identifier'], url)
|
data = self.getCache('hdtrailers.%s' % group['library']['identifier'], url)
|
||||||
@@ -44,7 +44,7 @@ class HDTrailers(TrailerProvider):
|
|||||||
def findViaAlternative(self, group):
|
def findViaAlternative(self, group):
|
||||||
results = {'480p':[], '720p':[], '1080p':[]}
|
results = {'480p':[], '720p':[], '1080p':[]}
|
||||||
|
|
||||||
movie_name = group['library']['titles'][0]['title']
|
movie_name = getTitle(group['library'])
|
||||||
|
|
||||||
url = "%s?%s" % (self.url['backup'], tryUrlencode({'s':movie_name}))
|
url = "%s?%s" % (self.url['backup'], tryUrlencode({'s':movie_name}))
|
||||||
data = self.getCache('hdtrailers.alt.%s' % group['library']['identifier'], url)
|
data = self.getCache('hdtrailers.alt.%s' % group['library']['identifier'], url)
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class Settings(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
db.close()
|
#db.close()
|
||||||
return prop
|
return prop
|
||||||
|
|
||||||
def setProperty(self, identifier, value = ''):
|
def setProperty(self, identifier, value = ''):
|
||||||
@@ -221,4 +221,4 @@ class Settings(object):
|
|||||||
p.value = toUnicode(value)
|
p.value = toUnicode(value)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
#db.close()
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class Env(object):
|
|||||||
|
|
||||||
''' Environment variables '''
|
''' Environment variables '''
|
||||||
_encoding = ''
|
_encoding = ''
|
||||||
_uses_git = False
|
|
||||||
_debug = False
|
_debug = False
|
||||||
_dev = False
|
_dev = False
|
||||||
_settings = Settings()
|
_settings = Settings()
|
||||||
@@ -66,7 +65,7 @@ class Env(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getEngine():
|
def getEngine():
|
||||||
return create_engine(Env.get('db_path'), echo = False)
|
return create_engine(Env.get('db_path'), echo = False, pool_recycle = 30)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setting(attr, section = 'core', value = None, default = '', type = None):
|
def setting(attr, section = 'core', value = None, default = '', type = None):
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ def getOptions(base_path, args):
|
|||||||
dest = 'console_log', help = "Log to console")
|
dest = 'console_log', help = "Log to console")
|
||||||
parser.add_argument('--quiet', action = 'store_true',
|
parser.add_argument('--quiet', action = 'store_true',
|
||||||
dest = 'quiet', help = 'No console logging')
|
dest = 'quiet', help = 'No console logging')
|
||||||
parser.add_argument('--nogit', action = 'store_true',
|
|
||||||
dest = 'nogit', help = 'No git available')
|
|
||||||
parser.add_argument('--daemon', action = 'store_true',
|
parser.add_argument('--daemon', action = 'store_true',
|
||||||
dest = 'daemon', help = 'Daemonize the app')
|
dest = 'daemon', help = 'Daemonize the app')
|
||||||
parser.add_argument('--pid_file', default = os.path.join(data_dir, 'couchpotato.pid'),
|
parser.add_argument('--pid_file', default = os.path.join(data_dir, 'couchpotato.pid'),
|
||||||
@@ -93,7 +91,6 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
|
|
||||||
# Register environment settings
|
# Register environment settings
|
||||||
Env.set('encoding', encoding)
|
Env.set('encoding', encoding)
|
||||||
Env.set('uses_git', not options.nogit)
|
|
||||||
Env.set('app_dir', base_path)
|
Env.set('app_dir', base_path)
|
||||||
Env.set('data_dir', data_dir)
|
Env.set('data_dir', data_dir)
|
||||||
Env.set('log_path', os.path.join(log_dir, 'CouchPotato.log'))
|
Env.set('log_path', os.path.join(log_dir, 'CouchPotato.log'))
|
||||||
@@ -132,7 +129,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
|||||||
|
|
||||||
# Logger
|
# Logger
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%H:%M:%S')
|
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%m-%d %H:%M:%S')
|
||||||
level = logging.DEBUG if debug else logging.INFO
|
level = logging.DEBUG if debug else logging.INFO
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from elixir.statements import Statement
|
|||||||
from elixir.collection import EntityCollection, GlobalEntityCollection
|
from elixir.collection import EntityCollection, GlobalEntityCollection
|
||||||
|
|
||||||
|
|
||||||
__version__ = '0.7.1'
|
__version__ = '0.8.0dev'
|
||||||
|
|
||||||
__all__ = ['Entity', 'EntityBase', 'EntityMeta', 'EntityCollection',
|
__all__ = ['Entity', 'EntityBase', 'EntityMeta', 'EntityCollection',
|
||||||
'entities',
|
'entities',
|
||||||
@@ -85,11 +85,6 @@ def drop_all(*args, **kwargs):
|
|||||||
def setup_all(create_tables=False, *args, **kwargs):
|
def setup_all(create_tables=False, *args, **kwargs):
|
||||||
'''Setup the table and mapper of all entities in the default entity
|
'''Setup the table and mapper of all entities in the default entity
|
||||||
collection.
|
collection.
|
||||||
|
|
||||||
This is called automatically if any entity of the collection is configured
|
|
||||||
with the `autosetup` option and it is first accessed,
|
|
||||||
instanciated (called) or the create_all method of a metadata containing
|
|
||||||
tables from any of those entities is called.
|
|
||||||
'''
|
'''
|
||||||
setup_entities(entities)
|
setup_entities(entities)
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ Default entity collection implementation
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from elixir.py23compat import rsplit
|
|
||||||
|
|
||||||
class BaseCollection(list):
|
class BaseCollection(list):
|
||||||
def __init__(self, entities=None):
|
def __init__(self, entities=None):
|
||||||
list.__init__(self)
|
list.__init__(self)
|
||||||
@@ -24,7 +22,7 @@ class BaseCollection(list):
|
|||||||
root = entity._descriptor.resolve_root
|
root = entity._descriptor.resolve_root
|
||||||
if root:
|
if root:
|
||||||
full_path = '%s.%s' % (root, full_path)
|
full_path = '%s.%s' % (root, full_path)
|
||||||
module_path, classname = rsplit(full_path, '.', 1)
|
module_path, classname = full_path.rsplit('.', 1)
|
||||||
module = sys.modules[module_path]
|
module = sys.modules[module_path]
|
||||||
res = getattr(module, classname, None)
|
res = getattr(module, classname, None)
|
||||||
if res is None:
|
if res is None:
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ This module provides the ``Entity`` base class, as well as its metaclass
|
|||||||
``EntityMeta``.
|
``EntityMeta``.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from py23compat import sorted
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
@@ -25,11 +23,6 @@ from elixir import options
|
|||||||
from elixir.properties import Property
|
from elixir.properties import Property
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
try:
|
|
||||||
from sqlalchemy.orm import EXT_PASS
|
|
||||||
SA05orlater = False
|
|
||||||
except ImportError:
|
|
||||||
SA05orlater = True
|
|
||||||
|
|
||||||
__doc_all__ = ['Entity', 'EntityMeta']
|
__doc_all__ = ['Entity', 'EntityMeta']
|
||||||
|
|
||||||
@@ -205,6 +198,7 @@ class EntityDescriptor(object):
|
|||||||
parent_desc = self.parent._descriptor
|
parent_desc = self.parent._descriptor
|
||||||
tablename = parent_desc.table_fullname
|
tablename = parent_desc.table_fullname
|
||||||
join_clauses = []
|
join_clauses = []
|
||||||
|
fk_columns = []
|
||||||
for pk_col in parent_desc.primary_keys:
|
for pk_col in parent_desc.primary_keys:
|
||||||
colname = options.MULTIINHERITANCECOL_NAMEFORMAT % \
|
colname = options.MULTIINHERITANCECOL_NAMEFORMAT % \
|
||||||
{'entity': self.parent.__name__.lower(),
|
{'entity': self.parent.__name__.lower(),
|
||||||
@@ -214,12 +208,14 @@ class EntityDescriptor(object):
|
|||||||
# a real column object when said column is not yet
|
# a real column object when said column is not yet
|
||||||
# attached to a table
|
# attached to a table
|
||||||
pk_col_name = "%s.%s" % (tablename, pk_col.key)
|
pk_col_name = "%s.%s" % (tablename, pk_col.key)
|
||||||
fk = ForeignKey(pk_col_name, ondelete='cascade')
|
col = Column(colname, pk_col.type, primary_key=True)
|
||||||
col = Column(colname, pk_col.type, fk,
|
fk_columns.append(col)
|
||||||
primary_key=True)
|
|
||||||
self.add_column(col)
|
self.add_column(col)
|
||||||
join_clauses.append(col == pk_col)
|
join_clauses.append(col == pk_col)
|
||||||
self.join_condition = and_(*join_clauses)
|
self.join_condition = and_(*join_clauses)
|
||||||
|
self.add_constraint(
|
||||||
|
ForeignKeyConstraint(fk_columns,
|
||||||
|
parent_desc.primary_keys, ondelete='CASCADE'))
|
||||||
elif self.inheritance == 'concrete':
|
elif self.inheritance == 'concrete':
|
||||||
# Copy primary key columns from the parent.
|
# Copy primary key columns from the parent.
|
||||||
for col in self.parent._descriptor.columns:
|
for col in self.parent._descriptor.columns:
|
||||||
@@ -286,7 +282,7 @@ class EntityDescriptor(object):
|
|||||||
self.add_constraint(
|
self.add_constraint(
|
||||||
ForeignKeyConstraint(
|
ForeignKeyConstraint(
|
||||||
[e.parent.key for e in con.elements],
|
[e.parent.key for e in con.elements],
|
||||||
[e._get_colspec() for e in con.elements],
|
[e.target_fullname for e in con.elements],
|
||||||
name=con.name, #TODO: modify it
|
name=con.name, #TODO: modify it
|
||||||
onupdate=con.onupdate, ondelete=con.ondelete,
|
onupdate=con.onupdate, ondelete=con.ondelete,
|
||||||
use_alter=con.use_alter))
|
use_alter=con.use_alter))
|
||||||
@@ -370,6 +366,11 @@ class EntityDescriptor(object):
|
|||||||
|
|
||||||
order = []
|
order = []
|
||||||
for colname in order_by:
|
for colname in order_by:
|
||||||
|
#FIXME: get_column uses self.columns[key] instead of property
|
||||||
|
# names. self.columns correspond to the columns of the table if
|
||||||
|
# the table was already created and to self._columns otherwise,
|
||||||
|
# which is a ColumnCollection indexed on columns.key
|
||||||
|
# See ticket #108.
|
||||||
col = self.get_column(colname.strip('-'))
|
col = self.get_column(colname.strip('-'))
|
||||||
if colname.startswith('-'):
|
if colname.startswith('-'):
|
||||||
col = desc(col)
|
col = desc(col)
|
||||||
@@ -493,17 +494,13 @@ class EntityDescriptor(object):
|
|||||||
(col.key, self.entity.__name__))
|
(col.key, self.entity.__name__))
|
||||||
else:
|
else:
|
||||||
del self._columns[col.key]
|
del self._columns[col.key]
|
||||||
|
# are indexed on col.key
|
||||||
self._columns.add(col)
|
self._columns.add(col)
|
||||||
|
|
||||||
if col.primary_key:
|
if col.primary_key:
|
||||||
self.has_pk = True
|
self.has_pk = True
|
||||||
|
|
||||||
# Autosetup triggers shouldn't be active anymore at this point, so we
|
table = self.entity.table
|
||||||
# can theoretically access the entity's table safely. But the problem
|
|
||||||
# is that if, for some reason, the trigger removal phase didn't
|
|
||||||
# happen, we'll get an infinite loop. So we just make sure we don't
|
|
||||||
# get one in any case.
|
|
||||||
table = type.__getattribute__(self.entity, 'table')
|
|
||||||
if table is not None:
|
if table is not None:
|
||||||
if check_duplicate and col.key in table.columns.keys():
|
if check_duplicate and col.key in table.columns.keys():
|
||||||
raise Exception("Column '%s' already exist in table '%s' ! " %
|
raise Exception("Column '%s' already exist in table '%s' ! " %
|
||||||
@@ -595,6 +592,7 @@ class EntityDescriptor(object):
|
|||||||
#------------------------
|
#------------------------
|
||||||
# some useful properties
|
# some useful properties
|
||||||
|
|
||||||
|
@property
|
||||||
def table_fullname(self):
|
def table_fullname(self):
|
||||||
'''
|
'''
|
||||||
Complete name of the table for the related entity.
|
Complete name of the table for the related entity.
|
||||||
@@ -605,8 +603,8 @@ class EntityDescriptor(object):
|
|||||||
return "%s.%s" % (schema, self.tablename)
|
return "%s.%s" % (schema, self.tablename)
|
||||||
else:
|
else:
|
||||||
return self.tablename
|
return self.tablename
|
||||||
table_fullname = property(table_fullname)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def columns(self):
|
def columns(self):
|
||||||
if self.entity.table is not None:
|
if self.entity.table is not None:
|
||||||
return self.entity.table.columns
|
return self.entity.table.columns
|
||||||
@@ -615,8 +613,8 @@ class EntityDescriptor(object):
|
|||||||
# return the parent entity's columns (for example for order_by
|
# return the parent entity's columns (for example for order_by
|
||||||
# using a column defined in the parent.
|
# using a column defined in the parent.
|
||||||
return self._columns
|
return self._columns
|
||||||
columns = property(columns)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def primary_keys(self):
|
def primary_keys(self):
|
||||||
"""
|
"""
|
||||||
Returns the list of primary key columns of the entity.
|
Returns the list of primary key columns of the entity.
|
||||||
@@ -630,15 +628,15 @@ class EntityDescriptor(object):
|
|||||||
return self.parent._descriptor.primary_keys
|
return self.parent._descriptor.primary_keys
|
||||||
else:
|
else:
|
||||||
return [col for col in self.columns if col.primary_key]
|
return [col for col in self.columns if col.primary_key]
|
||||||
primary_keys = property(primary_keys)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def table(self):
|
def table(self):
|
||||||
if self.entity.table is not None:
|
if self.entity.table is not None:
|
||||||
return self.entity.table
|
return self.entity.table
|
||||||
else:
|
else:
|
||||||
return FakeTable(self)
|
return FakeTable(self)
|
||||||
table = property(table)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def primary_key_properties(self):
|
def primary_key_properties(self):
|
||||||
"""
|
"""
|
||||||
Returns the list of (mapper) properties corresponding to the primary
|
Returns the list of (mapper) properties corresponding to the primary
|
||||||
@@ -653,30 +651,32 @@ class EntityDescriptor(object):
|
|||||||
for prop in mapper.iterate_properties:
|
for prop in mapper.iterate_properties:
|
||||||
if isinstance(prop, ColumnProperty):
|
if isinstance(prop, ColumnProperty):
|
||||||
for col in prop.columns:
|
for col in prop.columns:
|
||||||
|
#XXX: Why is this extra loop necessary? What is this
|
||||||
|
# "proxy_set" supposed to mean?
|
||||||
for col in col.proxy_set:
|
for col in col.proxy_set:
|
||||||
col_to_prop[col] = prop
|
col_to_prop[col] = prop
|
||||||
pk_cols = [c for c in mapper.mapped_table.c if c.primary_key]
|
pk_cols = [c for c in mapper.mapped_table.c if c.primary_key]
|
||||||
self._pk_props = [col_to_prop[c] for c in pk_cols]
|
self._pk_props = [col_to_prop[c] for c in pk_cols]
|
||||||
return self._pk_props
|
return self._pk_props
|
||||||
primary_key_properties = property(primary_key_properties)
|
|
||||||
|
|
||||||
class FakePK(object):
|
class FakePK(object):
|
||||||
def __init__(self, descriptor):
|
def __init__(self, descriptor):
|
||||||
self.descriptor = descriptor
|
self.descriptor = descriptor
|
||||||
|
|
||||||
|
@property
|
||||||
def columns(self):
|
def columns(self):
|
||||||
return self.descriptor.primary_keys
|
return self.descriptor.primary_keys
|
||||||
columns = property(columns)
|
|
||||||
|
|
||||||
class FakeTable(object):
|
class FakeTable(object):
|
||||||
def __init__(self, descriptor):
|
def __init__(self, descriptor):
|
||||||
self.descriptor = descriptor
|
self.descriptor = descriptor
|
||||||
self.primary_key = FakePK(descriptor)
|
self.primary_key = FakePK(descriptor)
|
||||||
|
|
||||||
|
@property
|
||||||
def columns(self):
|
def columns(self):
|
||||||
return self.descriptor.columns
|
return self.descriptor.columns
|
||||||
columns = property(columns)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def fullname(self):
|
def fullname(self):
|
||||||
'''
|
'''
|
||||||
Complete name of the table for the related entity.
|
Complete name of the table for the related entity.
|
||||||
@@ -687,46 +687,8 @@ class FakeTable(object):
|
|||||||
return "%s.%s" % (schema, self.descriptor.tablename)
|
return "%s.%s" % (schema, self.descriptor.tablename)
|
||||||
else:
|
else:
|
||||||
return self.descriptor.tablename
|
return self.descriptor.tablename
|
||||||
fullname = property(fullname)
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerProxy(object):
|
|
||||||
"""
|
|
||||||
A class that serves as a "trigger" ; accessing its attributes runs
|
|
||||||
the setup_all function.
|
|
||||||
|
|
||||||
Note that the `setup_all` is called on each access of the attribute.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, class_, attrname):
|
|
||||||
self.class_ = class_
|
|
||||||
self.attrname = attrname
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
elixir.setup_all()
|
|
||||||
#FIXME: it's possible to get an infinite loop here if setup_all doesn't
|
|
||||||
#remove the triggers for this entity. This can happen if the entity is
|
|
||||||
#not in the `entities` list for some reason.
|
|
||||||
proxied_attr = getattr(self.class_, self.attrname)
|
|
||||||
return getattr(proxied_attr, name)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
proxied_attr = getattr(self.class_, self.attrname)
|
|
||||||
return "<TriggerProxy (%s)>" % (self.class_.__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TriggerAttribute(object):
|
|
||||||
|
|
||||||
def __init__(self, attrname):
|
|
||||||
self.attrname = attrname
|
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
|
||||||
#FIXME: it's possible to get an infinite loop here if setup_all doesn't
|
|
||||||
#remove the triggers for this entity. This can happen if the entity is
|
|
||||||
#not in the `entities` list for some reason.
|
|
||||||
elixir.setup_all()
|
|
||||||
return getattr(owner, self.attrname)
|
|
||||||
|
|
||||||
def is_entity(cls):
|
def is_entity(cls):
|
||||||
"""
|
"""
|
||||||
Scan the bases classes of `cls` to see if any is an instance of
|
Scan the bases classes of `cls` to see if any is an instance of
|
||||||
@@ -805,13 +767,6 @@ def instrument_class(cls):
|
|||||||
# setup misc options here (like tablename etc.)
|
# setup misc options here (like tablename etc.)
|
||||||
desc.setup_options()
|
desc.setup_options()
|
||||||
|
|
||||||
# create trigger proxies
|
|
||||||
# TODO: support entity_name... It makes sense only for autoloaded
|
|
||||||
# tables for now, and would make more sense if we support "external"
|
|
||||||
# tables
|
|
||||||
if desc.autosetup:
|
|
||||||
_install_autosetup_triggers(cls)
|
|
||||||
|
|
||||||
|
|
||||||
class EntityMeta(type):
|
class EntityMeta(type):
|
||||||
"""
|
"""
|
||||||
@@ -823,11 +778,6 @@ class EntityMeta(type):
|
|||||||
def __init__(cls, name, bases, dict_):
|
def __init__(cls, name, bases, dict_):
|
||||||
instrument_class(cls)
|
instrument_class(cls)
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs):
|
|
||||||
if cls._descriptor.autosetup and not hasattr(cls, '_setup_done'):
|
|
||||||
elixir.setup_all()
|
|
||||||
return type.__call__(cls, *args, **kwargs)
|
|
||||||
|
|
||||||
def __setattr__(cls, key, value):
|
def __setattr__(cls, key, value):
|
||||||
if isinstance(value, Property):
|
if isinstance(value, Property):
|
||||||
if hasattr(cls, '_setup_done'):
|
if hasattr(cls, '_setup_done'):
|
||||||
@@ -839,84 +789,6 @@ class EntityMeta(type):
|
|||||||
type.__setattr__(cls, key, value)
|
type.__setattr__(cls, key, value)
|
||||||
|
|
||||||
|
|
||||||
def _install_autosetup_triggers(cls, entity_name=None):
|
|
||||||
#TODO: move as much as possible of those "_private" values to the
|
|
||||||
# descriptor, so that we don't mess the initial class.
|
|
||||||
warnings.warn("The 'autosetup' option on entities is deprecated. "
|
|
||||||
"Please call setup_all() manually after all your entities have been "
|
|
||||||
"declared.", DeprecationWarning, stacklevel=4)
|
|
||||||
tablename = cls._descriptor.tablename
|
|
||||||
schema = cls._descriptor.table_options.get('schema', None)
|
|
||||||
cls._table_key = sqlalchemy.schema._get_table_key(tablename, schema)
|
|
||||||
|
|
||||||
table_proxy = TriggerProxy(cls, 'table')
|
|
||||||
|
|
||||||
md = cls._descriptor.metadata
|
|
||||||
md.tables[cls._table_key] = table_proxy
|
|
||||||
|
|
||||||
# We need to monkeypatch the metadata's table iterator method because
|
|
||||||
# otherwise it doesn't work if the setup is triggered by the
|
|
||||||
# metadata.create_all().
|
|
||||||
# This is because ManyToMany relationships add tables AFTER the list
|
|
||||||
# of tables that are going to be created is "computed"
|
|
||||||
# (metadata.tables.values()).
|
|
||||||
# see:
|
|
||||||
# - table_iterator method in MetaData class in sqlalchemy/schema.py
|
|
||||||
# - visit_metadata method in sqlalchemy/ansisql.py
|
|
||||||
if SA05orlater:
|
|
||||||
warnings.warn(
|
|
||||||
"The automatic setup via metadata.create_all() through "
|
|
||||||
"the autosetup option doesn't work with SQLAlchemy 0.5 and later!")
|
|
||||||
else:
|
|
||||||
# SA 0.6 does not use table_iterator anymore (it was already deprecated
|
|
||||||
# since SA 0.5.0)
|
|
||||||
original_table_iterator = md.table_iterator
|
|
||||||
if not hasattr(original_table_iterator,
|
|
||||||
'_non_elixir_patched_iterator'):
|
|
||||||
def table_iterator(*args, **kwargs):
|
|
||||||
elixir.setup_all()
|
|
||||||
return original_table_iterator(*args, **kwargs)
|
|
||||||
table_iterator.__doc__ = original_table_iterator.__doc__
|
|
||||||
table_iterator._non_elixir_patched_iterator = \
|
|
||||||
original_table_iterator
|
|
||||||
md.table_iterator = table_iterator
|
|
||||||
|
|
||||||
#TODO: we might want to add all columns that will be available as
|
|
||||||
#attributes on the class itself (in SA 0.4+). This is a pretty
|
|
||||||
#rare usecase, as people will normally hit the query attribute before the
|
|
||||||
#column attributes, but I've seen people hitting this problem...
|
|
||||||
for name in ('c', 'table', 'mapper', 'query'):
|
|
||||||
setattr(cls, name, TriggerAttribute(name))
|
|
||||||
|
|
||||||
cls._has_triggers = True
|
|
||||||
|
|
||||||
|
|
||||||
def _cleanup_autosetup_triggers(cls):
|
|
||||||
if not hasattr(cls, '_has_triggers'):
|
|
||||||
return
|
|
||||||
|
|
||||||
for name in ('table', 'mapper'):
|
|
||||||
setattr(cls, name, None)
|
|
||||||
|
|
||||||
for name in ('c', 'query'):
|
|
||||||
delattr(cls, name)
|
|
||||||
|
|
||||||
desc = cls._descriptor
|
|
||||||
md = desc.metadata
|
|
||||||
|
|
||||||
# the fake table could have already been removed (namely in a
|
|
||||||
# single table inheritance scenario)
|
|
||||||
md.tables.pop(cls._table_key, None)
|
|
||||||
|
|
||||||
# restore original table iterator if not done already
|
|
||||||
if not SA05orlater:
|
|
||||||
if hasattr(md.table_iterator, '_non_elixir_patched_iterator'):
|
|
||||||
md.table_iterator = \
|
|
||||||
md.table_iterator._non_elixir_patched_iterator
|
|
||||||
|
|
||||||
del cls._has_triggers
|
|
||||||
|
|
||||||
|
|
||||||
def setup_entities(entities):
|
def setup_entities(entities):
|
||||||
'''Setup all entities in the list passed as argument'''
|
'''Setup all entities in the list passed as argument'''
|
||||||
|
|
||||||
@@ -928,9 +800,6 @@ def setup_entities(entities):
|
|||||||
if isinstance(attr, Property):
|
if isinstance(attr, Property):
|
||||||
delattr(entity, name)
|
delattr(entity, name)
|
||||||
|
|
||||||
if entity._descriptor.autosetup:
|
|
||||||
_cleanup_autosetup_triggers(entity)
|
|
||||||
|
|
||||||
for method_name in (
|
for method_name in (
|
||||||
'setup_autoload_table', 'create_pk_cols', 'setup_relkeys',
|
'setup_autoload_table', 'create_pk_cols', 'setup_relkeys',
|
||||||
'before_table', 'setup_table', 'setup_reltables', 'after_table',
|
'before_table', 'setup_table', 'setup_reltables', 'after_table',
|
||||||
@@ -955,8 +824,7 @@ def setup_entities(entities):
|
|||||||
def cleanup_entities(entities):
|
def cleanup_entities(entities):
|
||||||
"""
|
"""
|
||||||
Try to revert back the list of entities passed as argument to the state
|
Try to revert back the list of entities passed as argument to the state
|
||||||
they had just before their setup phase. It will not work entirely for
|
they had just before their setup phase.
|
||||||
autosetup entities as we need to remove the autosetup triggers.
|
|
||||||
|
|
||||||
As of now, this function is *not* functional in that it doesn't revert to
|
As of now, this function is *not* functional in that it doesn't revert to
|
||||||
the exact same state the entities were before setup. For example, the
|
the exact same state the entities were before setup. For example, the
|
||||||
@@ -968,8 +836,6 @@ def cleanup_entities(entities):
|
|||||||
"""
|
"""
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
desc = entity._descriptor
|
desc = entity._descriptor
|
||||||
if desc.autosetup:
|
|
||||||
_cleanup_autosetup_triggers(entity)
|
|
||||||
|
|
||||||
if hasattr(entity, '_setup_done'):
|
if hasattr(entity, '_setup_done'):
|
||||||
del entity._setup_done
|
del entity._setup_done
|
||||||
@@ -1007,6 +873,7 @@ class EntityBase(object):
|
|||||||
for key, value in kwargs.iteritems():
|
for key, value in kwargs.iteritems():
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def update_or_create(cls, data, surrogate=True):
|
def update_or_create(cls, data, surrogate=True):
|
||||||
pk_props = cls._descriptor.primary_key_properties
|
pk_props = cls._descriptor.primary_key_properties
|
||||||
|
|
||||||
@@ -1016,17 +883,16 @@ class EntityBase(object):
|
|||||||
record = cls.query.get(pk_tuple)
|
record = cls.query.get(pk_tuple)
|
||||||
if record is None:
|
if record is None:
|
||||||
if surrogate:
|
if surrogate:
|
||||||
raise Exception("cannot create surrogate with pk")
|
raise Exception("Cannot create surrogate with pk")
|
||||||
else:
|
else:
|
||||||
record = cls()
|
record = cls()
|
||||||
else:
|
else:
|
||||||
if surrogate:
|
if surrogate:
|
||||||
record = cls()
|
record = cls()
|
||||||
else:
|
else:
|
||||||
raise Exception("cannot create non surrogate without pk")
|
raise Exception("Cannot create non surrogate without pk")
|
||||||
record.from_dict(data)
|
record.from_dict(data)
|
||||||
return record
|
return record
|
||||||
update_or_create = classmethod(update_or_create)
|
|
||||||
|
|
||||||
def from_dict(self, data):
|
def from_dict(self, data):
|
||||||
"""
|
"""
|
||||||
@@ -1105,10 +971,11 @@ class EntityBase(object):
|
|||||||
|
|
||||||
# This bunch of session methods, along with all the query methods below
|
# This bunch of session methods, along with all the query methods below
|
||||||
# only make sense when using a global/scoped/contextual session.
|
# only make sense when using a global/scoped/contextual session.
|
||||||
|
@property
|
||||||
def _global_session(self):
|
def _global_session(self):
|
||||||
return self._descriptor.session.registry()
|
return self._descriptor.session.registry()
|
||||||
_global_session = property(_global_session)
|
|
||||||
|
|
||||||
|
#FIXME: remove all deprecated methods, possibly all of these
|
||||||
def merge(self, *args, **kwargs):
|
def merge(self, *args, **kwargs):
|
||||||
return self._global_session.merge(self, *args, **kwargs)
|
return self._global_session.merge(self, *args, **kwargs)
|
||||||
|
|
||||||
@@ -1126,6 +993,7 @@ class EntityBase(object):
|
|||||||
return self._global_session.save_or_update(self, *args, **kwargs)
|
return self._global_session.save_or_update(self, *args, **kwargs)
|
||||||
|
|
||||||
# query methods
|
# query methods
|
||||||
|
@classmethod
|
||||||
def get_by(cls, *args, **kwargs):
|
def get_by(cls, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns the first instance of this class matching the given criteria.
|
Returns the first instance of this class matching the given criteria.
|
||||||
@@ -1133,8 +1001,8 @@ class EntityBase(object):
|
|||||||
session.query(MyClass).filter_by(...).first()
|
session.query(MyClass).filter_by(...).first()
|
||||||
"""
|
"""
|
||||||
return cls.query.filter_by(*args, **kwargs).first()
|
return cls.query.filter_by(*args, **kwargs).first()
|
||||||
get_by = classmethod(get_by)
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def get(cls, *args, **kwargs):
|
def get(cls, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return the instance of this class based on the given identifier,
|
Return the instance of this class based on the given identifier,
|
||||||
@@ -1142,7 +1010,6 @@ class EntityBase(object):
|
|||||||
session.query(MyClass).get(...)
|
session.query(MyClass).get(...)
|
||||||
"""
|
"""
|
||||||
return cls.query.get(*args, **kwargs)
|
return cls.query.get(*args, **kwargs)
|
||||||
get = classmethod(get)
|
|
||||||
|
|
||||||
|
|
||||||
class Entity(EntityBase):
|
class Entity(EntityBase):
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from sqlalchemy.orm import reconstructor
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'before_insert',
|
'before_insert',
|
||||||
'after_insert',
|
'after_insert',
|
||||||
@@ -22,9 +24,4 @@ before_update = create_decorator('before_update')
|
|||||||
after_update = create_decorator('after_update')
|
after_update = create_decorator('after_update')
|
||||||
before_delete = create_decorator('before_delete')
|
before_delete = create_decorator('before_delete')
|
||||||
after_delete = create_decorator('after_delete')
|
after_delete = create_decorator('after_delete')
|
||||||
try:
|
|
||||||
from sqlalchemy.orm import reconstructor
|
|
||||||
except ImportError:
|
|
||||||
def reconstructor(func):
|
|
||||||
raise Exception('The reconstructor method decorator is only '
|
|
||||||
'available with SQLAlchemy 0.5 and later')
|
|
||||||
|
|||||||
@@ -222,12 +222,12 @@ def associable(assoc_entity, plural_name=None, lazy=True):
|
|||||||
|
|
||||||
# add helper methods
|
# add helper methods
|
||||||
def select_by(cls, **kwargs):
|
def select_by(cls, **kwargs):
|
||||||
return cls.query.join([attr_name, 'targets']) \
|
return cls.query.join(attr_name, 'targets') \
|
||||||
.filter_by(**kwargs).all()
|
.filter_by(**kwargs).all()
|
||||||
setattr(entity, 'select_by_%s' % self.name, classmethod(select_by))
|
setattr(entity, 'select_by_%s' % self.name, classmethod(select_by))
|
||||||
|
|
||||||
def select(cls, *args, **kwargs):
|
def select(cls, *args, **kwargs):
|
||||||
return cls.query.join([attr_name, 'targets']) \
|
return cls.query.join(attr_name, 'targets') \
|
||||||
.filter(*args, **kwargs).all()
|
.filter(*args, **kwargs).all()
|
||||||
setattr(entity, 'select_%s' % self.name, classmethod(select))
|
setattr(entity, 'select_%s' % self.name, classmethod(select))
|
||||||
|
|
||||||
|
|||||||
@@ -1,251 +0,0 @@
|
|||||||
'''
|
|
||||||
This extension is DEPRECATED. Please use the orderinglist SQLAlchemy
|
|
||||||
extension instead.
|
|
||||||
|
|
||||||
For details:
|
|
||||||
http://www.sqlalchemy.org/docs/05/reference/ext/orderinglist.html
|
|
||||||
|
|
||||||
For an Elixir example:
|
|
||||||
http://elixir.ematia.de/trac/wiki/Recipes/UsingEntityForOrderedList
|
|
||||||
or
|
|
||||||
http://elixir.ematia.de/trac/browser/elixir/0.7.0/tests/test_o2m.py#L155
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An ordered-list plugin for Elixir to help you make an entity be able to be
|
|
||||||
managed in a list-like way. Much inspiration comes from the Ruby on Rails
|
|
||||||
acts_as_list plugin, which is currently more full-featured than this plugin.
|
|
||||||
|
|
||||||
Once you flag an entity with an `acts_as_list()` statement, a column will be
|
|
||||||
added to the entity called `position` which will be an integer column that is
|
|
||||||
managed for you by the plugin. You can pass an alternative column name to
|
|
||||||
the plugin using the `column_name` keyword argument.
|
|
||||||
|
|
||||||
In addition, your entity will get a series of new methods attached to it,
|
|
||||||
including:
|
|
||||||
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| Method Name | Description |
|
|
||||||
+======================+======================================================+
|
|
||||||
| ``move_lower`` | Move the item lower in the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| ``move_higher`` | Move the item higher in the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| ``move_to_bottom`` | Move the item to the bottom of the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| ``move_to_top`` | Move the item to the top of the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
| ``move_to`` | Move the item to a specific position in the list |
|
|
||||||
+----------------------+------------------------------------------------------+
|
|
||||||
|
|
||||||
|
|
||||||
Sometimes, your entities that represent list items will be a part of different
|
|
||||||
lists. To implement this behavior, simply pass the `acts_as_list` statement a
|
|
||||||
callable that returns a "qualifier" SQLAlchemy expression. This expression will
|
|
||||||
be added to the generated WHERE clauses used by the plugin.
|
|
||||||
|
|
||||||
Example model usage:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
from elixir import *
|
|
||||||
from elixir.ext.list import acts_as_list
|
|
||||||
|
|
||||||
class ToDo(Entity):
|
|
||||||
subject = Field(String(128))
|
|
||||||
owner = ManyToOne('Person')
|
|
||||||
|
|
||||||
def qualify(self):
|
|
||||||
return ToDo.owner_id == self.owner_id
|
|
||||||
|
|
||||||
acts_as_list(qualifier=qualify)
|
|
||||||
|
|
||||||
class Person(Entity):
|
|
||||||
name = Field(String(64))
|
|
||||||
todos = OneToMany('ToDo', order_by='position')
|
|
||||||
|
|
||||||
|
|
||||||
The above example can then be used to manage ordered todo lists for people.
|
|
||||||
Note that you must set the `order_by` property on the `Person.todo` relation in
|
|
||||||
order for the relation to respect the ordering. Here is an example of using
|
|
||||||
this model in practice:
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
p = Person.query.filter_by(name='Jonathan').one()
|
|
||||||
p.todos.append(ToDo(subject='Three'))
|
|
||||||
p.todos.append(ToDo(subject='Two'))
|
|
||||||
p.todos.append(ToDo(subject='One'))
|
|
||||||
session.commit(); session.clear()
|
|
||||||
|
|
||||||
p = Person.query.filter_by(name='Jonathan').one()
|
|
||||||
p.todos[0].move_to_bottom()
|
|
||||||
p.todos[2].move_to_top()
|
|
||||||
session.commit(); session.clear()
|
|
||||||
|
|
||||||
p = Person.query.filter_by(name='Jonathan').one()
|
|
||||||
assert p.todos[0].subject == 'One'
|
|
||||||
assert p.todos[1].subject == 'Two'
|
|
||||||
assert p.todos[2].subject == 'Three'
|
|
||||||
|
|
||||||
|
|
||||||
For more examples, refer to the unit tests for this plugin.
|
|
||||||
'''
|
|
||||||
|
|
||||||
from elixir.statements import Statement
|
|
||||||
from elixir.events import before_insert, before_delete
|
|
||||||
from sqlalchemy import Column, Integer, select, func, literal, and_
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
__all__ = ['acts_as_list']
|
|
||||||
__doc_all__ = []
|
|
||||||
|
|
||||||
|
|
||||||
def get_entity_where(instance):
|
|
||||||
clauses = []
|
|
||||||
for column in instance.table.primary_key.columns:
|
|
||||||
instance_value = getattr(instance, column.name)
|
|
||||||
clauses.append(column == instance_value)
|
|
||||||
return and_(*clauses)
|
|
||||||
|
|
||||||
|
|
||||||
class ListEntityBuilder(object):
|
|
||||||
|
|
||||||
def __init__(self, entity, qualifier=None, column_name='position'):
|
|
||||||
warnings.warn("The act_as_list extension is deprecated. Please use "
|
|
||||||
"SQLAlchemy's orderinglist extension instead",
|
|
||||||
DeprecationWarning, stacklevel=6)
|
|
||||||
self.entity = entity
|
|
||||||
self.qualifier_method = qualifier
|
|
||||||
self.column_name = column_name
|
|
||||||
|
|
||||||
def create_non_pk_cols(self):
|
|
||||||
if self.entity._descriptor.autoload:
|
|
||||||
for c in self.entity.table.c:
|
|
||||||
if c.name == self.column_name:
|
|
||||||
self.position_column = c
|
|
||||||
if not hasattr(self, 'position_column'):
|
|
||||||
raise Exception(
|
|
||||||
"Could not find column '%s' in autoloaded table '%s', "
|
|
||||||
"needed by entity '%s'." % (self.column_name,
|
|
||||||
self.entity.table.name, self.entity.__name__))
|
|
||||||
else:
|
|
||||||
self.position_column = Column(self.column_name, Integer)
|
|
||||||
self.entity._descriptor.add_column(self.position_column)
|
|
||||||
|
|
||||||
def after_table(self):
|
|
||||||
position_column = self.position_column
|
|
||||||
position_column_name = self.column_name
|
|
||||||
|
|
||||||
qualifier_method = self.qualifier_method
|
|
||||||
if not qualifier_method:
|
|
||||||
qualifier_method = lambda self: None
|
|
||||||
|
|
||||||
def _init_position(self):
|
|
||||||
s = select(
|
|
||||||
[(func.max(position_column)+1).label('value')],
|
|
||||||
qualifier_method(self)
|
|
||||||
).union(
|
|
||||||
select([literal(1).label('value')])
|
|
||||||
)
|
|
||||||
a = s.alias()
|
|
||||||
# we use a second func.max to get the maximum between 1 and the
|
|
||||||
# real max position if any exist
|
|
||||||
setattr(self, position_column_name, select([func.max(a.c.value)]))
|
|
||||||
|
|
||||||
# Note that this method could be rewritten more simply like below,
|
|
||||||
# but because this extension is going to be deprecated anyway,
|
|
||||||
# I don't want to risk breaking something I don't want to maintain.
|
|
||||||
# setattr(self, position_column_name, select(
|
|
||||||
# [func.coalesce(func.max(position_column), 0) + 1],
|
|
||||||
# qualifier_method(self)
|
|
||||||
# ))
|
|
||||||
_init_position = before_insert(_init_position)
|
|
||||||
|
|
||||||
def _shift_items(self):
|
|
||||||
self.table.update(
|
|
||||||
and_(
|
|
||||||
position_column > getattr(self, position_column_name),
|
|
||||||
qualifier_method(self)
|
|
||||||
),
|
|
||||||
values={
|
|
||||||
position_column : position_column - 1
|
|
||||||
}
|
|
||||||
).execute()
|
|
||||||
_shift_items = before_delete(_shift_items)
|
|
||||||
|
|
||||||
def move_to_bottom(self):
|
|
||||||
# move the items that were above this item up one
|
|
||||||
self.table.update(
|
|
||||||
and_(
|
|
||||||
position_column >= getattr(self, position_column_name),
|
|
||||||
qualifier_method(self)
|
|
||||||
),
|
|
||||||
values = {
|
|
||||||
position_column : position_column - 1
|
|
||||||
}
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
# move this item to the max position
|
|
||||||
# MySQL does not support the correlated subquery, so we need to
|
|
||||||
# execute the query (through scalar()). See ticket #34.
|
|
||||||
self.table.update(
|
|
||||||
get_entity_where(self),
|
|
||||||
values={
|
|
||||||
position_column : select(
|
|
||||||
[func.max(position_column) + 1],
|
|
||||||
qualifier_method(self)
|
|
||||||
).scalar()
|
|
||||||
}
|
|
||||||
).execute()
|
|
||||||
|
|
||||||
def move_to_top(self):
|
|
||||||
self.move_to(1)
|
|
||||||
|
|
||||||
def move_to(self, position):
|
|
||||||
current_position = getattr(self, position_column_name)
|
|
||||||
|
|
||||||
# determine which direction we're moving
|
|
||||||
if position < current_position:
|
|
||||||
where = and_(
|
|
||||||
position <= position_column,
|
|
||||||
position_column < current_position,
|
|
||||||
qualifier_method(self)
|
|
||||||
)
|
|
||||||
modifier = 1
|
|
||||||
elif position > current_position:
|
|
||||||
where = and_(
|
|
||||||
current_position < position_column,
|
|
||||||
position_column <= position,
|
|
||||||
qualifier_method(self)
|
|
||||||
)
|
|
||||||
modifier = -1
|
|
||||||
|
|
||||||
# shift the items in between the current and new positions
|
|
||||||
self.table.update(where, values = {
|
|
||||||
position_column : position_column + modifier
|
|
||||||
}).execute()
|
|
||||||
|
|
||||||
# update this item's position to the desired position
|
|
||||||
self.table.update(get_entity_where(self)) \
|
|
||||||
.execute(**{position_column_name: position})
|
|
||||||
|
|
||||||
def move_lower(self):
|
|
||||||
# replace for ex.: p.todos.insert(x + 1, p.todos.pop(x))
|
|
||||||
self.move_to(getattr(self, position_column_name) + 1)
|
|
||||||
|
|
||||||
def move_higher(self):
|
|
||||||
self.move_to(getattr(self, position_column_name) - 1)
|
|
||||||
|
|
||||||
|
|
||||||
# attach new methods to entity
|
|
||||||
self.entity._init_position = _init_position
|
|
||||||
self.entity._shift_items = _shift_items
|
|
||||||
self.entity.move_lower = move_lower
|
|
||||||
self.entity.move_higher = move_higher
|
|
||||||
self.entity.move_to_bottom = move_to_bottom
|
|
||||||
self.entity.move_to_top = move_to_top
|
|
||||||
self.entity.move_to = move_to
|
|
||||||
|
|
||||||
|
|
||||||
acts_as_list = Statement(ListEntityBuilder)
|
|
||||||
@@ -124,16 +124,6 @@ The list of supported arguments are as follows:
|
|||||||
| | module by setting the ``__session__`` attribute of |
|
| | module by setting the ``__session__`` attribute of |
|
||||||
| | that module. |
|
| | that module. |
|
||||||
+---------------------+-------------------------------------------------------+
|
+---------------------+-------------------------------------------------------+
|
||||||
| ``autosetup`` | DEPRECATED. Specify whether that entity will contain |
|
|
||||||
| | automatic setup triggers. |
|
|
||||||
| | That is if this entity will be |
|
|
||||||
| | automatically setup (along with all other entities |
|
|
||||||
| | which were already declared) if any of the following |
|
|
||||||
| | condition happen: some of its attributes are accessed |
|
|
||||||
| | ('c', 'table', 'mapper' or 'query'), instanciated |
|
|
||||||
| | (called) or the create_all method of this entity's |
|
|
||||||
| | metadata is called. Defaults to ``False``. |
|
|
||||||
+---------------------+-------------------------------------------------------+
|
|
||||||
| ``allowcoloverride``| Specify whether it is allowed to override columns. |
|
| ``allowcoloverride``| Specify whether it is allowed to override columns. |
|
||||||
| | By default, Elixir forbids you to add a column to an |
|
| | By default, Elixir forbids you to add a column to an |
|
||||||
| | entity's table which already exist in that table. If |
|
| | entity's table which already exist in that table. If |
|
||||||
@@ -225,7 +215,6 @@ MIGRATION_TO_07_AID = False
|
|||||||
#
|
#
|
||||||
options_defaults = dict(
|
options_defaults = dict(
|
||||||
abstract=False,
|
abstract=False,
|
||||||
autosetup=False,
|
|
||||||
inheritance='single',
|
inheritance='single',
|
||||||
polymorphic=True,
|
polymorphic=True,
|
||||||
identity=None,
|
identity=None,
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
# Some helper functions to get by without Python 2.4
|
|
||||||
|
|
||||||
# set
|
|
||||||
try:
|
|
||||||
set = set
|
|
||||||
except NameError:
|
|
||||||
from sets import Set as set
|
|
||||||
|
|
||||||
orig_cmp = cmp
|
|
||||||
# [].sort
|
|
||||||
def sort_list(l, cmp=None, key=None, reverse=False):
|
|
||||||
try:
|
|
||||||
l.sort(cmp, key, reverse)
|
|
||||||
except TypeError, e:
|
|
||||||
if not str(e).startswith('sort expected at most 1 arguments'):
|
|
||||||
raise
|
|
||||||
if cmp is None:
|
|
||||||
cmp = orig_cmp
|
|
||||||
if key is not None:
|
|
||||||
# the cmp=cmp parameter is required to get the original comparator
|
|
||||||
# into the lambda namespace
|
|
||||||
cmp = lambda self, other, cmp=cmp: cmp(key(self), key(other))
|
|
||||||
if reverse:
|
|
||||||
cmp = lambda self, other, cmp=cmp: -cmp(self,other)
|
|
||||||
l.sort(cmp)
|
|
||||||
|
|
||||||
# sorted
|
|
||||||
try:
|
|
||||||
sorted = sorted
|
|
||||||
except NameError:
|
|
||||||
# global name 'sorted' doesn't exist in Python2.3
|
|
||||||
# this provides a poor-man's emulation of the sorted built-in method
|
|
||||||
def sorted(l, cmp=None, key=None, reverse=False):
|
|
||||||
sorted_list = list(l)
|
|
||||||
sort_list(sorted_list, cmp, key, reverse)
|
|
||||||
return sorted_list
|
|
||||||
|
|
||||||
# rsplit
|
|
||||||
try:
|
|
||||||
''.rsplit
|
|
||||||
def rsplit(s, delim, maxsplit):
|
|
||||||
return s.rsplit(delim, maxsplit)
|
|
||||||
|
|
||||||
except AttributeError:
|
|
||||||
def rsplit(s, delim, maxsplit):
|
|
||||||
"""Return a list of the words of the string s, scanning s
|
|
||||||
from the end. To all intents and purposes, the resulting
|
|
||||||
list of words is the same as returned by split(), except
|
|
||||||
when the optional third argument maxsplit is explicitly
|
|
||||||
specified and nonzero. When maxsplit is nonzero, at most
|
|
||||||
maxsplit number of splits - the rightmost ones - occur,
|
|
||||||
and the remainder of the string is returned as the first
|
|
||||||
element of the list (thus, the list will have at most
|
|
||||||
maxsplit+1 elements). New in version 2.4.
|
|
||||||
>>> rsplit('foo.bar.baz', '.', 0)
|
|
||||||
['foo.bar.baz']
|
|
||||||
>>> rsplit('foo.bar.baz', '.', 1)
|
|
||||||
['foo.bar', 'baz']
|
|
||||||
>>> rsplit('foo.bar.baz', '.', 2)
|
|
||||||
['foo', 'bar', 'baz']
|
|
||||||
>>> rsplit('foo.bar.baz', '.', 99)
|
|
||||||
['foo', 'bar', 'baz']
|
|
||||||
"""
|
|
||||||
assert maxsplit >= 0
|
|
||||||
|
|
||||||
if maxsplit == 0: return [s]
|
|
||||||
|
|
||||||
# the following lines perform the function, but inefficiently.
|
|
||||||
# This may be adequate for compatibility purposes
|
|
||||||
items = s.split(delim)
|
|
||||||
if maxsplit < len(items):
|
|
||||||
items[:-maxsplit] = [delim.join(items[:-maxsplit])]
|
|
||||||
return items
|
|
||||||
@@ -278,8 +278,9 @@ relationships accept the following optional (keyword) arguments:
|
|||||||
| | reference the "local"/current entity's table. |
|
| | reference the "local"/current entity's table. |
|
||||||
+--------------------+--------------------------------------------------------+
|
+--------------------+--------------------------------------------------------+
|
||||||
| ``table`` | Use a manually created table. If this argument is |
|
| ``table`` | Use a manually created table. If this argument is |
|
||||||
| | used, Elixir won't generate a table for this |
|
| | used, Elixir will not generate a table for this |
|
||||||
| | relationship, and use the one given instead. |
|
| | relationship, and use the one given instead. This |
|
||||||
|
| | argument only accepts SQLAlchemy's Table objects. |
|
||||||
+--------------------+--------------------------------------------------------+
|
+--------------------+--------------------------------------------------------+
|
||||||
| ``order_by`` | Specify which field(s) should be used to sort the |
|
| ``order_by`` | Specify which field(s) should be used to sort the |
|
||||||
| | results given by accessing the relation field. |
|
| | results given by accessing the relation field. |
|
||||||
@@ -303,13 +304,6 @@ relationships accept the following optional (keyword) arguments:
|
|||||||
| ``table_kwargs`` | A dictionary holding any other keyword argument you |
|
| ``table_kwargs`` | A dictionary holding any other keyword argument you |
|
||||||
| | might want to pass to the underlying Table object. |
|
| | might want to pass to the underlying Table object. |
|
||||||
+--------------------+--------------------------------------------------------+
|
+--------------------+--------------------------------------------------------+
|
||||||
| ``column_format`` | DEPRECATED. Specify an alternate format string for |
|
|
||||||
| | naming the |
|
|
||||||
| | columns in the mapping table. The default value is |
|
|
||||||
| | defined in ``elixir.options.M2MCOL_NAMEFORMAT``. You |
|
|
||||||
| | will be passed ``tablename``, ``key``, and ``entity`` |
|
|
||||||
| | as arguments to the format string. |
|
|
||||||
+--------------------+--------------------------------------------------------+
|
|
||||||
|
|
||||||
|
|
||||||
================
|
================
|
||||||
@@ -493,6 +487,7 @@ class Relationship(Property):
|
|||||||
self.property = relation(self.target, **kwargs)
|
self.property = relation(self.target, **kwargs)
|
||||||
self.add_mapper_property(self.name, self.property)
|
self.add_mapper_property(self.name, self.property)
|
||||||
|
|
||||||
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
if not self._target:
|
if not self._target:
|
||||||
if isinstance(self.of_kind, basestring):
|
if isinstance(self.of_kind, basestring):
|
||||||
@@ -501,8 +496,8 @@ class Relationship(Property):
|
|||||||
else:
|
else:
|
||||||
self._target = self.of_kind
|
self._target = self.of_kind
|
||||||
return self._target
|
return self._target
|
||||||
target = property(target)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def inverse(self):
|
def inverse(self):
|
||||||
if not hasattr(self, '_inverse'):
|
if not hasattr(self, '_inverse'):
|
||||||
if self.inverse_name:
|
if self.inverse_name:
|
||||||
@@ -531,7 +526,6 @@ class Relationship(Property):
|
|||||||
inverse._inverse = self
|
inverse._inverse = self
|
||||||
|
|
||||||
return self._inverse
|
return self._inverse
|
||||||
inverse = property(inverse)
|
|
||||||
|
|
||||||
def match_type_of(self, other):
|
def match_type_of(self, other):
|
||||||
return False
|
return False
|
||||||
@@ -612,12 +606,12 @@ class ManyToOne(Relationship):
|
|||||||
def match_type_of(self, other):
|
def match_type_of(self, other):
|
||||||
return isinstance(other, (OneToMany, OneToOne))
|
return isinstance(other, (OneToMany, OneToOne))
|
||||||
|
|
||||||
|
@property
|
||||||
def target_table(self):
|
def target_table(self):
|
||||||
if isinstance(self.target, EntityMeta):
|
if isinstance(self.target, EntityMeta):
|
||||||
return self.target._descriptor.table
|
return self.target._descriptor.table
|
||||||
else:
|
else:
|
||||||
return class_mapper(self.target).local_table
|
return class_mapper(self.target).local_table
|
||||||
target_table = property(target_table)
|
|
||||||
|
|
||||||
def create_keys(self, pk):
|
def create_keys(self, pk):
|
||||||
'''
|
'''
|
||||||
@@ -634,7 +628,10 @@ class ManyToOne(Relationship):
|
|||||||
source_desc = self.entity._descriptor
|
source_desc = self.entity._descriptor
|
||||||
if isinstance(self.target, EntityMeta):
|
if isinstance(self.target, EntityMeta):
|
||||||
# make sure the target has all its pk set up
|
# make sure the target has all its pk set up
|
||||||
|
#FIXME: this is not enough when specifying target_column manually,
|
||||||
|
# on unique, non-pk col, see tests/test_m2o.py:test_non_pk_forward
|
||||||
self.target._descriptor.create_pk_cols()
|
self.target._descriptor.create_pk_cols()
|
||||||
|
|
||||||
#XXX: another option, instead of the FakeTable, would be to create an
|
#XXX: another option, instead of the FakeTable, would be to create an
|
||||||
# EntityDescriptor for the SA class.
|
# EntityDescriptor for the SA class.
|
||||||
target_table = self.target_table
|
target_table = self.target_table
|
||||||
@@ -655,6 +652,12 @@ class ManyToOne(Relationship):
|
|||||||
"Couldn't find a foreign key constraint in table "
|
"Couldn't find a foreign key constraint in table "
|
||||||
"'%s' using the following columns: %s."
|
"'%s' using the following columns: %s."
|
||||||
% (self.entity.table.name, colnames))
|
% (self.entity.table.name, colnames))
|
||||||
|
else:
|
||||||
|
# in this case we let SA handle everything.
|
||||||
|
# XXX: we might want to try to build join clauses anyway so
|
||||||
|
# that we know whether there is an ambiguity or not, and
|
||||||
|
# suggest using colname if there is one
|
||||||
|
pass
|
||||||
if self.field:
|
if self.field:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"'field' argument not allowed on autoloaded table "
|
"'field' argument not allowed on autoloaded table "
|
||||||
@@ -808,8 +811,8 @@ class OneToOne(Relationship):
|
|||||||
# useless because the remote_side is already setup in the other way
|
# useless because the remote_side is already setup in the other way
|
||||||
# (ManyToOne).
|
# (ManyToOne).
|
||||||
if self.entity.table is self.target.table:
|
if self.entity.table is self.target.table:
|
||||||
#FIXME: IF this code is of any use, it will probably break for
|
# When using a manual/autoloaded table, it will be assigned
|
||||||
# autoloaded tables
|
# an empty list, which doesn't seem to upset SQLAlchemy
|
||||||
kwargs['remote_side'] = self.inverse.foreign_key
|
kwargs['remote_side'] = self.inverse.foreign_key
|
||||||
|
|
||||||
# Contrary to ManyToMany relationships, we need to specify the join
|
# Contrary to ManyToMany relationships, we need to specify the join
|
||||||
@@ -839,7 +842,6 @@ class ManyToMany(Relationship):
|
|||||||
local_colname=None, remote_colname=None,
|
local_colname=None, remote_colname=None,
|
||||||
ondelete=None, onupdate=None,
|
ondelete=None, onupdate=None,
|
||||||
table=None, schema=None,
|
table=None, schema=None,
|
||||||
column_format=None,
|
|
||||||
filter=None,
|
filter=None,
|
||||||
table_kwargs=None,
|
table_kwargs=None,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
@@ -858,14 +860,9 @@ class ManyToMany(Relationship):
|
|||||||
self.table = table
|
self.table = table
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
|
|
||||||
if column_format:
|
#TODO: this can probably be simplified/moved elsewhere since the
|
||||||
warnings.warn("The 'column_format' argument on ManyToMany "
|
#argument disappeared
|
||||||
"relationships is deprecated. Please use the 'local_colname' "
|
self.column_format = options.M2MCOL_NAMEFORMAT
|
||||||
"and/or 'remote_colname' arguments if you want custom "
|
|
||||||
"column names for this table only, or modify "
|
|
||||||
"options.M2MCOL_NAMEFORMAT if you want a custom format for "
|
|
||||||
"all ManyToMany tables", DeprecationWarning, stacklevel=3)
|
|
||||||
self.column_format = column_format or options.M2MCOL_NAMEFORMAT
|
|
||||||
if not hasattr(self.column_format, '__call__'):
|
if not hasattr(self.column_format, '__call__'):
|
||||||
# we need to store the format in a variable so that the
|
# we need to store the format in a variable so that the
|
||||||
# closure of the lambda is correct
|
# closure of the lambda is correct
|
||||||
@@ -891,13 +888,6 @@ class ManyToMany(Relationship):
|
|||||||
|
|
||||||
super(ManyToMany, self).__init__(of_kind, *args, **kwargs)
|
super(ManyToMany, self).__init__(of_kind, *args, **kwargs)
|
||||||
|
|
||||||
def get_table(self):
|
|
||||||
warnings.warn("The secondary_table attribute on ManyToMany objects is "
|
|
||||||
"deprecated. You should rather use the table attribute.",
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
return self.table
|
|
||||||
secondary_table = property(get_table)
|
|
||||||
|
|
||||||
def match_type_of(self, other):
|
def match_type_of(self, other):
|
||||||
return isinstance(other, ManyToMany)
|
return isinstance(other, ManyToMany)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user