Browse Source

Merge branch 'refs/heads/develop'

pull/381/merge
Ruud 13 years ago
parent
commit
d89130dc30
  1. 15
      couchpotato/core/helpers/variable.py
  2. 8
      couchpotato/core/notifications/core/main.py
  3. 2
      couchpotato/core/notifications/history/main.py
  4. 4
      couchpotato/core/plugins/file/main.py
  5. 6
      couchpotato/core/plugins/library/main.py
  6. 18
      couchpotato/core/plugins/movie/main.py
  7. 12
      couchpotato/core/plugins/profile/main.py
  8. 8
      couchpotato/core/plugins/quality/main.py
  9. 10
      couchpotato/core/plugins/release/main.py
  10. 13
      couchpotato/core/plugins/renamer/main.py
  11. 4
      couchpotato/core/plugins/scanner/main.py
  12. 3
      couchpotato/core/plugins/score/main.py
  13. 21
      couchpotato/core/plugins/searcher/main.py
  14. 8
      couchpotato/core/plugins/status/main.py
  15. 2
      couchpotato/core/plugins/subtitle/main.py
  16. 3
      couchpotato/core/plugins/userscript/template.js
  17. 3
      couchpotato/core/providers/metadata/xbmc/main.py
  18. 2
      couchpotato/core/providers/movie/_modifier/main.py
  19. 2
      couchpotato/core/providers/movie/couchpotatoapi/main.py
  20. 8
      couchpotato/core/providers/movie/imdbapi/main.py
  21. 8
      couchpotato/core/providers/movie/themoviedb/main.py
  22. 3
      couchpotato/core/providers/nzb/moovee/main.py
  23. 4
      couchpotato/core/providers/nzb/mysterbin/main.py
  24. 4
      couchpotato/core/providers/nzb/nzbclub/main.py
  25. 4
      couchpotato/core/providers/nzb/nzbindex/main.py
  26. 4
      couchpotato/core/providers/nzb/x264/main.py
  27. 4
      couchpotato/core/providers/torrent/kickasstorrents/main.py
  28. 6
      couchpotato/core/providers/trailer/hdtrailers/main.py
  29. 4
      couchpotato/core/settings/__init__.py
  30. 3
      couchpotato/environment.py
  31. 5
      couchpotato/runner.py
  32. 7
      libs/elixir/__init__.py
  33. 4
      libs/elixir/collection.py
  34. 197
      libs/elixir/entity.py
  35. 9
      libs/elixir/events.py
  36. 4
      libs/elixir/ext/associable.py
  37. 251
      libs/elixir/ext/list.py
  38. 11
      libs/elixir/options.py
  39. 73
      libs/elixir/py23compat.py
  40. 50
      libs/elixir/relationships.py

15
couchpotato/core/helpers/variable.py

@ -1,8 +1,11 @@
from couchpotato.core.logger import CPLog
import hashlib
import os.path
import platform
import re
log = CPLog(__name__)
def getDataDir():
# Windows
@ -102,3 +105,15 @@ def natsortKey(s):
def natcmp(a, 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

8
couchpotato/core/notifications/core/main.py

@ -65,7 +65,7 @@ class CoreNotifier(Notification):
q.update({Notif.read: True})
db.commit()
db.close()
#db.close()
return jsonified({
'success': True
@ -91,7 +91,7 @@ class CoreNotifier(Notification):
ndict['type'] = 'notification'
notifications.append(ndict)
db.close()
#db.close()
return jsonified({
'success': True,
'empty': len(notifications) == 0,
@ -116,7 +116,7 @@ class CoreNotifier(Notification):
ndict['time'] = time.time()
self.messages.append(ndict)
db.close()
#db.close()
return True
def frontend(self, type = 'notification', data = {}):
@ -146,7 +146,7 @@ class CoreNotifier(Notification):
ndict['type'] = 'notification'
messages.append(ndict)
db.close()
#db.close()
self.messages = []
return jsonified({

2
couchpotato/core/notifications/history/main.py

@ -22,6 +22,6 @@ class History(Notification):
)
db.add(history)
db.commit()
db.close()
#db.close()
return True

4
couchpotato/core/plugins/file/main.py

@ -87,7 +87,7 @@ class FileManager(Plugin):
db.commit()
type_dict = ft.to_dict()
db.close()
#db.close()
return type_dict
def getTypes(self):
@ -100,5 +100,5 @@ class FileManager(Plugin):
for type_object in results:
types.append(type_object.to_dict())
db.close()
#db.close()
return types

6
couchpotato/core/plugins/library/main.py

@ -53,7 +53,7 @@ class LibraryPlugin(Plugin):
library_dict = l.to_dict(self.default_dict)
db.close()
#db.close()
return library_dict
def update(self, identifier, default_title = '', force = False):
@ -130,7 +130,7 @@ class LibraryPlugin(Plugin):
fireEvent('library.update_finish', data = library_dict)
db.close()
#db.close()
return library_dict
def updateReleaseDate(self, identifier):
@ -144,7 +144,7 @@ class LibraryPlugin(Plugin):
db.commit()
dates = library.info.get('release_date', {})
db.close()
#db.close()
return dates

18
couchpotato/core/plugins/movie/main.py

@ -113,7 +113,7 @@ class MoviePlugin(Plugin):
if m:
results = m.to_dict(self.default_dict)
db.close()
#db.close()
return results
def list(self, status = ['active'], limit_offset = None, starts_with = None, search = None):
@ -177,7 +177,7 @@ class MoviePlugin(Plugin):
})
movies.append(temp)
db.close()
#db.close()
return movies
def availableChars(self, status = ['active']):
@ -203,7 +203,7 @@ class MoviePlugin(Plugin):
if char not in chars:
chars += char
db.close()
#db.close()
return chars
def listView(self):
@ -250,7 +250,7 @@ class MoviePlugin(Plugin):
fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True)
fireEventAsync('searcher.single', movie.to_dict(self.default_dict))
db.close()
#db.close()
return jsonified({
'success': True,
})
@ -324,7 +324,7 @@ class MoviePlugin(Plugin):
if (force_readd or do_search) and search_after:
fireEventAsync('searcher.single', movie_dict)
db.close()
#db.close()
return movie_dict
@ -371,7 +371,7 @@ class MoviePlugin(Plugin):
movie_dict = m.to_dict(self.default_dict)
fireEventAsync('searcher.single', movie_dict)
db.close()
#db.close()
return jsonified({
'success': True,
})
@ -426,7 +426,7 @@ class MoviePlugin(Plugin):
else:
fireEvent('movie.restatus', movie.id, single = True)
db.close()
#db.close()
return True
def restatus(self, movie_id):
@ -437,7 +437,7 @@ class MoviePlugin(Plugin):
db = get_session()
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.')
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')
db.commit()
db.close()
#db.close()
return True

12
couchpotato/core/plugins/profile/main.py

@ -47,7 +47,7 @@ class ProfilePlugin(Plugin):
for profile in profiles:
temp.append(profile.to_dict(self.to_dict))
db.close()
#db.close()
return temp
def save(self):
@ -84,7 +84,7 @@ class ProfilePlugin(Plugin):
profile_dict = p.to_dict(self.to_dict)
db.close()
#db.close()
return jsonified({
'success': True,
'profile': profile_dict
@ -95,7 +95,7 @@ class ProfilePlugin(Plugin):
db = get_session()
default = db.query(Profile).first()
default_dict = default.to_dict(self.to_dict)
db.close()
#db.close()
return default_dict
@ -113,7 +113,7 @@ class ProfilePlugin(Plugin):
order += 1
db.commit()
db.close()
#db.close()
return jsonified({
'success': True
@ -138,7 +138,7 @@ class ProfilePlugin(Plugin):
message = 'Failed deleting Profile: %s' % e
log.error(message)
db.close()
#db.close()
return jsonified({
'success': success,
@ -187,5 +187,5 @@ class ProfilePlugin(Plugin):
order += 1
db.close()
#db.close()
return True

8
couchpotato/core/plugins/quality/main.py

@ -68,7 +68,7 @@ class QualityPlugin(Plugin):
q = mergeDicts(self.getQuality(quality.identifier), quality.to_dict())
temp.append(q)
db.close()
#db.close()
return temp
def single(self, identifier = ''):
@ -80,7 +80,7 @@ class QualityPlugin(Plugin):
if quality:
quality_dict = dict(self.getQuality(quality.identifier), **quality.to_dict())
db.close()
#db.close()
return quality_dict
def getQuality(self, identifier):
@ -100,7 +100,7 @@ class QualityPlugin(Plugin):
setattr(quality, params.get('value_type'), params.get('value'))
db.commit()
db.close()
#db.close()
return jsonified({
'success': True
})
@ -152,7 +152,7 @@ class QualityPlugin(Plugin):
order += 1
db.commit()
db.close()
#db.close()
return True
def guess(self, files, extra = {}):

10
couchpotato/core/plugins/release/main.py

@ -83,7 +83,7 @@ class Release(Plugin):
fireEvent('movie.restatus', movie.id)
db.close()
#db.close()
return True
@ -109,7 +109,7 @@ class Release(Plugin):
rel.delete()
db.commit()
db.close()
#db.close()
return jsonified({
'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')
db.commit()
db.close()
#db.close()
return jsonified({
'success': True
})
@ -153,14 +153,14 @@ class Release(Plugin):
'files': {}
}), manual = True)
db.close()
#db.close()
return jsonified({
'success': True
})
else:
log.error('Couldn\'t find release with id: %s' % id)
db.close()
#db.close()
return jsonified({
'success': False
})

13
couchpotato/core/plugins/renamer/main.py

@ -3,7 +3,7 @@ from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
from couchpotato.core.helpers.encoding import toUnicode
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.plugins.base import Plugin
from couchpotato.core.settings.model import Library, File, Profile
@ -82,8 +82,10 @@ class Renamer(Plugin):
remove_files = []
remove_releases = []
movie_title = getTitle(group['library'])
# Add _UNKNOWN_ if no library item is connected
if not group['library']:
if not group['library'] or not movie_title:
if group['dirname']:
rename_files[group['parentdir']] = group['parentdir'].replace(group['dirname'], '_UNKNOWN_%s' % group['dirname'])
else: # Add it to filename
@ -100,12 +102,13 @@ class Renamer(Plugin):
continue
library = group['library']
movie_title = getTitle(library)
# Find subtitle for renaming
fireEvent('renamer.before', group)
# 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
name_the = movie_name
@ -369,14 +372,14 @@ class Renamer(Plugin):
fireEventAsync('renamer.after', group)
# 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)
# Break if CP wants to shut down
if self.shuttingDown():
break
db.close()
#db.close()
self.renaming_started = False
def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = ''):

4
couchpotato/core/plugins/scanner/main.py

@ -8,7 +8,7 @@ from couchpotato.core.settings.model import File
from couchpotato.environment import Env
from enzyme.exceptions import NoParserError, ParseError
from guessit import guess_movie_info
from subliminal.videos import scan, Video
from subliminal.videos import Video
import enzyme
import os
import re
@ -455,7 +455,7 @@ class Scanner(Plugin):
break
except:
pass
db.close()
#db.close()
# Search based on OpenSubtitleHash
if not imdb_id and not group['is_dvd']:

3
couchpotato/core/plugins/score/main.py

@ -1,5 +1,6 @@
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import getTitle
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.plugins.score.scores import nameScore, nameRatioScore, \
@ -35,6 +36,6 @@ class Score(Plugin):
score += providerScore(nzb['provider'])
# Duplicates in name
score += duplicateScore(nzb['name'], movie['library']['titles'][0]['title'])
score += duplicateScore(nzb['name'], getTitle(movie['library']))
return score

21
couchpotato/core/plugins/searcher/main.py

@ -1,7 +1,7 @@
from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEvent
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.plugins.base import Plugin
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
@ -61,7 +61,7 @@ class Searcher(Plugin):
if self.shuttingDown():
break
db.close()
#db.close()
self.in_progress = False
def single(self, movie):
@ -78,7 +78,10 @@ class Searcher(Plugin):
release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = 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']:
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))
@ -101,6 +104,10 @@ class Searcher(Plugin):
if len(sorted_results) == 0:
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
for nzb in sorted_results:
@ -148,7 +155,7 @@ class Searcher(Plugin):
if self.shuttingDown():
break
db.close()
#db.close()
return False
def download(self, data, movie, manual = False):
@ -164,7 +171,7 @@ class Searcher(Plugin):
rls.status_id = snatched_status.get('id')
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)
log.info(snatch_message)
fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
@ -191,7 +198,7 @@ class Searcher(Plugin):
except Exception, e:
log.error('Failed marking movie finished: %s %s' % (e, traceback.format_exc()))
db.close()
#db.close()
return True
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']):
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
def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}, single_category = False):

8
couchpotato/core/plugins/status/main.py

@ -49,7 +49,7 @@ class StatusPlugin(Plugin):
db = get_session()
status = db.query(Status).filter_by(id = id).first()
status_dict = status.to_dict()
db.close()
#db.close()
return status_dict
@ -64,7 +64,7 @@ class StatusPlugin(Plugin):
s = status.to_dict()
temp.append(s)
db.close()
#db.close()
return temp
def add(self, identifier):
@ -82,7 +82,7 @@ class StatusPlugin(Plugin):
status_dict = s.to_dict()
db.close()
#db.close()
return status_dict
def fill(self):
@ -102,5 +102,5 @@ class StatusPlugin(Plugin):
s.label = toUnicode(label)
db.commit()
db.close()
#db.close()

2
couchpotato/core/plugins/subtitle/main.py

@ -38,7 +38,7 @@ class Subtitle(Plugin):
# get subtitles for those files
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):

3
couchpotato/core/plugins/userscript/template.js

@ -12,6 +12,9 @@
// ==/UserScript==
if (window.top != window.self) // Only run on top window
return;
var version = {{version}},
host = '{{host}}',
api = '{{api}}';

3
couchpotato/core/providers/metadata/xbmc/main.py

@ -1,4 +1,5 @@
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import getTitle
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.metadata.base import MetaDataBase
from xml.etree.ElementTree import Element, SubElement, tostring
@ -32,7 +33,7 @@ class XBMC(MetaDataBase):
# Title
try:
el = SubElement(nfoxml, 'title')
el.text = toUnicode(data['library']['titles'][0]['title'])
el.text = toUnicode(getTitle(data['library']))
except:
pass

2
couchpotato/core/providers/movie/_modifier/main.py

@ -63,7 +63,7 @@ class MovieResultModifier(Plugin):
except:
log.error('Tried getting more info on searched movies: %s' % traceback.format_exc())
db.close()
#db.close()
return temp
def checkLibrary(self, result):

2
couchpotato/core/providers/movie/couchpotatoapi/main.py

@ -59,7 +59,7 @@ class CouchPotatoApi(MovieProvider):
db = get_session()
active_movies = db.query(Movie).filter(Movie.status.has(identifier = 'active')).all()
movies = [x.library.identifier for x in active_movies]
db.close()
#db.close()
suggestions = self.suggest(movies, ignore)

8
couchpotato/core/providers/movie/imdbapi/main.py

@ -64,8 +64,12 @@ class IMDBAPI(MovieProvider):
movie_data = {}
try:
if isinstance(movie, (str, unicode)):
movie = json.loads(movie)
try:
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':
return movie_data

8
couchpotato/core/providers/movie/themoviedb/main.py

@ -69,13 +69,7 @@ class TheMovieDb(MovieProvider):
try:
nr = 0
# Sort on returned score first when year is in q
if re.search('\s\d{4}', q):
movies = sorted(raw, key = lambda k: k['score'], reverse = True)
else:
movies = raw
for movie in movies:
for movie in raw:
results.append(self.parseMovie(movie))
nr += 1

3
couchpotato/core/providers/nzb/moovee/main.py

@ -1,5 +1,6 @@
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.variable import getTitle
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.nzb.base import NZBProvider
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):
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)
cache_key = 'moovee.%s' % q

4
couchpotato/core/providers/nzb/mysterbin/main.py

@ -1,7 +1,7 @@
from BeautifulSoup import BeautifulSoup
from couchpotato.core.event import fireEvent
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.providers.nzb.base import NZBProvider
from couchpotato.environment import Env
@ -25,7 +25,7 @@ class Mysterbin(NZBProvider):
if self.isDisabled() or not self.isAvailable(self.urls['search']):
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(','):
q = '%s -%s' % (q, ignored.strip())

4
couchpotato/core/providers/nzb/nzbclub/main.py

@ -2,7 +2,7 @@ from BeautifulSoup import BeautifulSoup
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
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.providers.nzb.base import NZBProvider
from couchpotato.environment import Env
@ -27,7 +27,7 @@ class NZBClub(NZBProvider, RSS):
if self.isDisabled() or not self.isAvailable(self.urls['search']):
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(','):
q = '%s -%s' % (q, ignored.strip())

4
couchpotato/core/providers/nzb/nzbindex/main.py

@ -2,7 +2,7 @@ from BeautifulSoup import BeautifulSoup
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.encoding import toUnicode, tryUrlencode
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.providers.nzb.base import NZBProvider
from couchpotato.environment import Env
@ -29,7 +29,7 @@ class NzbIndex(NZBProvider, RSS):
if self.isDisabled() or not self.isAvailable(self.urls['api']):
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({
'q': q,
'age': Env.setting('retention', 'nzb'),

4
couchpotato/core/providers/nzb/x264/main.py

@ -1,6 +1,6 @@
from couchpotato.core.event import fireEvent
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.providers.nzb.base import NZBProvider
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):
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)
cache_key = 'x264.%s.%s' % (movie['library']['identifier'], quality.get('identifier'))

4
couchpotato/core/providers/torrent/kickasstorrents/main.py

@ -1,6 +1,6 @@
from BeautifulSoup import BeautifulSoup
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.providers.torrent.base import TorrentProvider
import StringIO
@ -38,7 +38,7 @@ class KickAssTorrents(TorrentProvider):
return results
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:
cat_ids = self.getCatId(quality['identifier'])

6
couchpotato/core/providers/trailer/hdtrailers/main.py

@ -1,6 +1,6 @@
from BeautifulSoup import SoupStrainer, BeautifulSoup
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.providers.trailer.base import TrailerProvider
from string import letters, digits
@ -19,7 +19,7 @@ class HDTrailers(TrailerProvider):
def search(self, group):
movie_name = group['library']['titles'][0]['title']
movie_name = getTitle(group['library'])
url = self.urls['api'] % self.movieUrlName(movie_name)
data = self.getCache('hdtrailers.%s' % group['library']['identifier'], url)
@ -44,7 +44,7 @@ class HDTrailers(TrailerProvider):
def findViaAlternative(self, group):
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}))
data = self.getCache('hdtrailers.alt.%s' % group['library']['identifier'], url)

4
couchpotato/core/settings/__init__.py

@ -204,7 +204,7 @@ class Settings(object):
except:
pass
db.close()
#db.close()
return prop
def setProperty(self, identifier, value = ''):
@ -221,4 +221,4 @@ class Settings(object):
p.value = toUnicode(value)
db.commit()
db.close()
#db.close()

3
couchpotato/environment.py

@ -12,7 +12,6 @@ class Env(object):
''' Environment variables '''
_encoding = ''
_uses_git = False
_debug = False
_dev = False
_settings = Settings()
@ -66,7 +65,7 @@ class Env(object):
@staticmethod
def getEngine():
return create_engine(Env.get('db_path'), echo = False)
return create_engine(Env.get('db_path'), echo = False, pool_recycle = 30)
@staticmethod
def setting(attr, section = 'core', value = None, default = '', type = None):

5
couchpotato/runner.py

@ -28,8 +28,6 @@ def getOptions(base_path, args):
dest = 'console_log', help = "Log to console")
parser.add_argument('--quiet', action = 'store_true',
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',
dest = 'daemon', help = 'Daemonize the app')
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
Env.set('encoding', encoding)
Env.set('uses_git', not options.nogit)
Env.set('app_dir', base_path)
Env.set('data_dir', data_dir)
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 = 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
logger.setLevel(level)

7
libs/elixir/__init__.py

@ -38,7 +38,7 @@ from elixir.statements import Statement
from elixir.collection import EntityCollection, GlobalEntityCollection
__version__ = '0.7.1'
__version__ = '0.8.0dev'
__all__ = ['Entity', 'EntityBase', 'EntityMeta', 'EntityCollection',
'entities',
@ -85,11 +85,6 @@ def drop_all(*args, **kwargs):
def setup_all(create_tables=False, *args, **kwargs):
'''Setup the table and mapper of all entities in the default entity
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)

4
libs/elixir/collection.py

@ -4,8 +4,6 @@ Default entity collection implementation
import sys
import re
from elixir.py23compat import rsplit
class BaseCollection(list):
def __init__(self, entities=None):
list.__init__(self)
@ -24,7 +22,7 @@ class BaseCollection(list):
root = entity._descriptor.resolve_root
if root:
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]
res = getattr(module, classname, None)
if res is None:

197
libs/elixir/entity.py

@ -3,8 +3,6 @@ This module provides the ``Entity`` base class, as well as its metaclass
``EntityMeta``.
'''
from py23compat import sorted
import sys
import types
import warnings
@ -25,11 +23,6 @@ from elixir import options
from elixir.properties import Property
DEBUG = False
try:
from sqlalchemy.orm import EXT_PASS
SA05orlater = False
except ImportError:
SA05orlater = True
__doc_all__ = ['Entity', 'EntityMeta']
@ -205,6 +198,7 @@ class EntityDescriptor(object):
parent_desc = self.parent._descriptor
tablename = parent_desc.table_fullname
join_clauses = []
fk_columns = []
for pk_col in parent_desc.primary_keys:
colname = options.MULTIINHERITANCECOL_NAMEFORMAT % \
{'entity': self.parent.__name__.lower(),
@ -214,12 +208,14 @@ class EntityDescriptor(object):
# a real column object when said column is not yet
# attached to a table
pk_col_name = "%s.%s" % (tablename, pk_col.key)
fk = ForeignKey(pk_col_name, ondelete='cascade')
col = Column(colname, pk_col.type, fk,
primary_key=True)
col = Column(colname, pk_col.type, primary_key=True)
fk_columns.append(col)
self.add_column(col)
join_clauses.append(col == pk_col)
self.join_condition = and_(*join_clauses)
self.add_constraint(
ForeignKeyConstraint(fk_columns,
parent_desc.primary_keys, ondelete='CASCADE'))
elif self.inheritance == 'concrete':
# Copy primary key columns from the parent.
for col in self.parent._descriptor.columns:
@ -286,7 +282,7 @@ class EntityDescriptor(object):
self.add_constraint(
ForeignKeyConstraint(
[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
onupdate=con.onupdate, ondelete=con.ondelete,
use_alter=con.use_alter))
@ -370,6 +366,11 @@ class EntityDescriptor(object):
order = []
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('-'))
if colname.startswith('-'):
col = desc(col)
@ -493,17 +494,13 @@ class EntityDescriptor(object):
(col.key, self.entity.__name__))
else:
del self._columns[col.key]
# are indexed on col.key
self._columns.add(col)
if col.primary_key:
self.has_pk = True
# Autosetup triggers shouldn't be active anymore at this point, so we
# 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')
table = self.entity.table
if table is not None:
if check_duplicate and col.key in table.columns.keys():
raise Exception("Column '%s' already exist in table '%s' ! " %
@ -595,6 +592,7 @@ class EntityDescriptor(object):
#------------------------
# some useful properties
@property
def table_fullname(self):
'''
Complete name of the table for the related entity.
@ -605,8 +603,8 @@ class EntityDescriptor(object):
return "%s.%s" % (schema, self.tablename)
else:
return self.tablename
table_fullname = property(table_fullname)
@property
def columns(self):
if self.entity.table is not None:
return self.entity.table.columns
@ -615,8 +613,8 @@ class EntityDescriptor(object):
# return the parent entity's columns (for example for order_by
# using a column defined in the parent.
return self._columns
columns = property(columns)
@property
def primary_keys(self):
"""
Returns the list of primary key columns of the entity.
@ -630,15 +628,15 @@ class EntityDescriptor(object):
return self.parent._descriptor.primary_keys
else:
return [col for col in self.columns if col.primary_key]
primary_keys = property(primary_keys)
@property
def table(self):
if self.entity.table is not None:
return self.entity.table
else:
return FakeTable(self)
table = property(table)
@property
def primary_key_properties(self):
"""
Returns the list of (mapper) properties corresponding to the primary
@ -653,30 +651,32 @@ class EntityDescriptor(object):
for prop in mapper.iterate_properties:
if isinstance(prop, ColumnProperty):
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:
col_to_prop[col] = prop
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]
return self._pk_props
primary_key_properties = property(primary_key_properties)
class FakePK(object):
def __init__(self, descriptor):
self.descriptor = descriptor
@property
def columns(self):
return self.descriptor.primary_keys
columns = property(columns)
class FakeTable(object):
def __init__(self, descriptor):
self.descriptor = descriptor
self.primary_key = FakePK(descriptor)
@property
def columns(self):
return self.descriptor.columns
columns = property(columns)
@property
def fullname(self):
'''
Complete name of the table for the related entity.
@ -687,45 +687,7 @@ class FakeTable(object):
return "%s.%s" % (schema, self.descriptor.tablename)
else:
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):
"""
@ -805,13 +767,6 @@ def instrument_class(cls):
# setup misc options here (like tablename etc.)
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):
"""
@ -823,11 +778,6 @@ class EntityMeta(type):
def __init__(cls, name, bases, dict_):
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):
if isinstance(value, Property):
if hasattr(cls, '_setup_done'):
@ -839,84 +789,6 @@ class EntityMeta(type):
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):
'''Setup all entities in the list passed as argument'''
@ -928,9 +800,6 @@ def setup_entities(entities):
if isinstance(attr, Property):
delattr(entity, name)
if entity._descriptor.autosetup:
_cleanup_autosetup_triggers(entity)
for method_name in (
'setup_autoload_table', 'create_pk_cols', 'setup_relkeys',
'before_table', 'setup_table', 'setup_reltables', 'after_table',
@ -955,8 +824,7 @@ def setup_entities(entities):
def cleanup_entities(entities):
"""
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
autosetup entities as we need to remove the autosetup triggers.
they had just before their setup phase.
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
@ -968,8 +836,6 @@ def cleanup_entities(entities):
"""
for entity in entities:
desc = entity._descriptor
if desc.autosetup:
_cleanup_autosetup_triggers(entity)
if hasattr(entity, '_setup_done'):
del entity._setup_done
@ -1007,6 +873,7 @@ class EntityBase(object):
for key, value in kwargs.iteritems():
setattr(self, key, value)
@classmethod
def update_or_create(cls, data, surrogate=True):
pk_props = cls._descriptor.primary_key_properties
@ -1016,17 +883,16 @@ class EntityBase(object):
record = cls.query.get(pk_tuple)
if record is None:
if surrogate:
raise Exception("cannot create surrogate with pk")
raise Exception("Cannot create surrogate with pk")
else:
record = cls()
else:
if surrogate:
record = cls()
else:
raise Exception("cannot create non surrogate without pk")
raise Exception("Cannot create non surrogate without pk")
record.from_dict(data)
return record
update_or_create = classmethod(update_or_create)
def from_dict(self, data):
"""
@ -1105,10 +971,11 @@ class EntityBase(object):
# This bunch of session methods, along with all the query methods below
# only make sense when using a global/scoped/contextual session.
@property
def _global_session(self):
return self._descriptor.session.registry()
_global_session = property(_global_session)
#FIXME: remove all deprecated methods, possibly all of these
def 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)
# query methods
@classmethod
def get_by(cls, *args, **kwargs):
"""
Returns the first instance of this class matching the given criteria.
@ -1133,8 +1001,8 @@ class EntityBase(object):
session.query(MyClass).filter_by(...).first()
"""
return cls.query.filter_by(*args, **kwargs).first()
get_by = classmethod(get_by)
@classmethod
def get(cls, *args, **kwargs):
"""
Return the instance of this class based on the given identifier,
@ -1142,7 +1010,6 @@ class EntityBase(object):
session.query(MyClass).get(...)
"""
return cls.query.get(*args, **kwargs)
get = classmethod(get)
class Entity(EntityBase):

9
libs/elixir/events.py

@ -1,3 +1,5 @@
from sqlalchemy.orm import reconstructor
__all__ = [
'before_insert',
'after_insert',
@ -22,9 +24,4 @@ before_update = create_decorator('before_update')
after_update = create_decorator('after_update')
before_delete = create_decorator('before_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')

4
libs/elixir/ext/associable.py

@ -222,12 +222,12 @@ def associable(assoc_entity, plural_name=None, lazy=True):
# add helper methods
def select_by(cls, **kwargs):
return cls.query.join([attr_name, 'targets']) \
return cls.query.join(attr_name, 'targets') \
.filter_by(**kwargs).all()
setattr(entity, 'select_by_%s' % self.name, classmethod(select_by))
def select(cls, *args, **kwargs):
return cls.query.join([attr_name, 'targets']) \
return cls.query.join(attr_name, 'targets') \
.filter(*args, **kwargs).all()
setattr(entity, 'select_%s' % self.name, classmethod(select))

251
libs/elixir/ext/list.py

@ -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)

11
libs/elixir/options.py

@ -124,16 +124,6 @@ The list of supported arguments are as follows:
| | module by setting the ``__session__`` attribute of |
| | 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. |
| | By default, Elixir forbids you to add a column to an |
| | entity's table which already exist in that table. If |
@ -225,7 +215,6 @@ MIGRATION_TO_07_AID = False
#
options_defaults = dict(
abstract=False,
autosetup=False,
inheritance='single',
polymorphic=True,
identity=None,

73
libs/elixir/py23compat.py

@ -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

50
libs/elixir/relationships.py

@ -278,8 +278,9 @@ relationships accept the following optional (keyword) arguments:
| | reference the "local"/current entity's table. |
+--------------------+--------------------------------------------------------+
| ``table`` | Use a manually created table. If this argument is |
| | used, Elixir won't generate a table for this |
| | relationship, and use the one given instead. |
| | used, Elixir will not generate a table for this |
| | 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 |
| | 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 |
| | 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.add_mapper_property(self.name, self.property)
@property
def target(self):
if not self._target:
if isinstance(self.of_kind, basestring):
@ -501,8 +496,8 @@ class Relationship(Property):
else:
self._target = self.of_kind
return self._target
target = property(target)
@property
def inverse(self):
if not hasattr(self, '_inverse'):
if self.inverse_name:
@ -531,7 +526,6 @@ class Relationship(Property):
inverse._inverse = self
return self._inverse
inverse = property(inverse)
def match_type_of(self, other):
return False
@ -612,12 +606,12 @@ class ManyToOne(Relationship):
def match_type_of(self, other):
return isinstance(other, (OneToMany, OneToOne))
@property
def target_table(self):
if isinstance(self.target, EntityMeta):
return self.target._descriptor.table
else:
return class_mapper(self.target).local_table
target_table = property(target_table)
def create_keys(self, pk):
'''
@ -634,7 +628,10 @@ class ManyToOne(Relationship):
source_desc = self.entity._descriptor
if isinstance(self.target, EntityMeta):
# 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()
#XXX: another option, instead of the FakeTable, would be to create an
# EntityDescriptor for the SA class.
target_table = self.target_table
@ -655,6 +652,12 @@ class ManyToOne(Relationship):
"Couldn't find a foreign key constraint in table "
"'%s' using the following columns: %s."
% (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:
raise NotImplementedError(
"'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
# (ManyToOne).
if self.entity.table is self.target.table:
#FIXME: IF this code is of any use, it will probably break for
# autoloaded tables
# When using a manual/autoloaded table, it will be assigned
# an empty list, which doesn't seem to upset SQLAlchemy
kwargs['remote_side'] = self.inverse.foreign_key
# Contrary to ManyToMany relationships, we need to specify the join
@ -839,7 +842,6 @@ class ManyToMany(Relationship):
local_colname=None, remote_colname=None,
ondelete=None, onupdate=None,
table=None, schema=None,
column_format=None,
filter=None,
table_kwargs=None,
*args, **kwargs):
@ -858,14 +860,9 @@ class ManyToMany(Relationship):
self.table = table
self.schema = schema
if column_format:
warnings.warn("The 'column_format' argument on ManyToMany "
"relationships is deprecated. Please use the 'local_colname' "
"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
#TODO: this can probably be simplified/moved elsewhere since the
#argument disappeared
self.column_format = options.M2MCOL_NAMEFORMAT
if not hasattr(self.column_format, '__call__'):
# we need to store the format in a variable so that the
# closure of the lambda is correct
@ -891,13 +888,6 @@ class ManyToMany(Relationship):
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):
return isinstance(other, ManyToMany)

Loading…
Cancel
Save