create exceptions.py and move more constants

This commit is contained in:
voussoir 2016-12-12 19:49:36 -08:00
parent 2b34854910
commit 1ecd1f979e
8 changed files with 343 additions and 300 deletions

21
README.md Normal file
View file

@ -0,0 +1,21 @@
Etiquette
=========
This is the readme file.
### Changelog
- **[addition]** A new feature was added.
- **[bugfix]** Incorrect behavior was fixed.
- **[change]** An existing feature was slightly modified or parameters were renamed.
- **[cleanup]** Code was improved, comments were added, or other changes with minor impact on the interface.
- **[removal]** An old feature was removed.
 
- 2016 11 28
- **[addition]** Added `etiquette_upgrader.py`. When an update causes the anatomy of the etiquette database to change, I will increment the `phototagger.DATABASE_VERSION` variable, and add a new function to this script that should automatically make all the necessary changes. Until the database is upgraded, phototagger will not start. Don't forget to make backups just in case.
- 2016 11 05
- **[addition]** Added the ability to download an album as a `.tar` file. No compression is used. I still need to do more experiments to make sure this is working perfectly.

View file

@ -1,5 +1,18 @@
import string
ALLOWED_ORDERBY_COLUMNS = [
'extension',
'width',
'height',
'ratio',
'area',
'duration',
'bytes',
'created',
'tagged_at',
'random',
]
# Errors and warnings
ERROR_DATABASE_OUTOFDATE = 'Database is out-of-date. {current} should be {new}. Please use etiquette_upgrader.py'
ERROR_INVALID_ACTION = 'Invalid action'
@ -21,7 +34,7 @@ VALID_TAG_CHARS = string.ascii_lowercase + string.digits + '_'
DEFAULT_ID_LENGTH = 12
DEFAULT_DBNAME = 'phototagger.db'
DEFAULT_THUMBDIR = '_etiquette\\site_thumbnails'
DEFAULT_DATADIR = '.\\_etiquette'
DEFAULT_DIGEST_EXCLUDE_FILES = [
DEFAULT_DBNAME,
'desktop.ini',

View file

@ -12,20 +12,15 @@ import warnings
import constants
import decorators
import exceptions
import helpers
import jsonify
import phototagger
try:
sys.path.append('C:\\git\\else\\Bytestring')
sys.path.append('C:\\git\\else\\WebstreamZip')
import bytestring
import webstreamzip
except ImportError:
# pip install
# https://raw.githubusercontent.com/voussoir/else/master/_voussoirkit/voussoirkit.zip
from vousoirkit import bytestring
from vousoirkit import webstreamzip
from voussoirkit import bytestring
from voussoirkit import webstreamzip
site = flask.Flask(__name__)
site.config.update(
@ -61,7 +56,7 @@ def delete_synonym(synonym):
synonym = phototagger.normalize_tagname(synonym)
try:
master_tag = P.get_tag(synonym)
except phototagger.NoSuchTag:
except exceptions.NoSuchTag:
flask.abort(404, 'That synonym doesnt exist')
if synonym not in master_tag.synonyms():
@ -79,19 +74,19 @@ def make_json_response(j, *args, **kwargs):
def P_album(albumid):
try:
return P.get_album(albumid)
except phototagger.NoSuchAlbum:
except exceptions.NoSuchAlbum:
flask.abort(404, 'That album doesnt exist')
def P_photo(photoid):
try:
return P.get_photo(photoid)
except phototagger.NoSuchPhoto:
except exceptions.NoSuchPhoto:
flask.abort(404, 'That photo doesnt exist')
def P_tag(tagname):
try:
return P.get_tag(tagname)
except phototagger.NoSuchTag as e:
except exceptions.NoSuchTag as e:
flask.abort(404, 'That tag doesnt exist: %s' % e)
def send_file(filepath):
@ -465,7 +460,7 @@ def get_static(filename):
def get_tags_core(specific_tag=None):
try:
tags = P.export_tags(phototagger.tag_export_easybake, specific_tag=specific_tag)
except phototagger.NoSuchTag:
except exceptions.NoSuchTag:
flask.abort(404, 'That tag doesnt exist')
tags = tags.split('\n')
tags = [t for t in tags if t != '']
@ -516,7 +511,7 @@ def post_edit_album(albumid):
tag = request.form[action].strip()
try:
tag = P_tag(tag)
except phototagger.NoSuchTag:
except exceptions.NoSuchTag:
response = {'error': 'That tag doesnt exist', 'tagname': tag}
return make_json_response(response, status=404)
recursive = request.form.get('recursive', False)
@ -552,7 +547,7 @@ def post_edit_photo(photoid):
try:
tag = P.get_tag(tag)
except phototagger.NoSuchTag:
except exceptions.NoSuchTag:
response = {'error': 'That tag doesnt exist', 'tagname': tag}
return make_json_response(response, status=404)
@ -595,11 +590,11 @@ def post_edit_tags():
status = 400
try:
response = method(tag)
except phototagger.TagTooShort:
except exceptions.TagTooShort:
response = {'error': constants.ERROR_TAG_TOO_SHORT, 'tagname': tag}
except phototagger.CantSynonymSelf:
except exceptions.CantSynonymSelf:
response = {'error': constants.ERROR_SYNONYM_ITSELF, 'tagname': tag}
except phototagger.NoSuchTag as e:
except exceptions.NoSuchTag as e:
response = {'error': constants.ERROR_NO_SUCH_TAG, 'tagname': tag}
except ValueError as e:
response = {'error': e.args[0], 'tagname': tag}

View file

@ -25,5 +25,5 @@ else:
)
print('Starting server')
print('Starting server on port %d' % port)
http.serve_forever()

46
exceptions.py Normal file
View file

@ -0,0 +1,46 @@
class CantSynonymSelf(Exception):
pass
class NoSuchAlbum(Exception):
pass
class NoSuchGroup(Exception):
pass
class NoSuchPhoto(Exception):
pass
class NoSuchSynonym(Exception):
pass
class NoSuchTag(Exception):
pass
class PhotoExists(Exception):
pass
class TagExists(Exception):
pass
class GroupExists(Exception):
pass
class TagTooLong(Exception):
pass
class TagTooShort(Exception):
pass
class NotExclusive(Exception):
'''
For when two or more mutually exclusive actions have been requested.
'''
pass
class OutOfOrder(Exception):
'''
For when a requested range (a, b) has b > a
'''
pass

View file

@ -1,6 +1,10 @@
import math
import mimetypes
import os
import exceptions
import constants
import warnings
def chunk_sequence(sequence, chunk_length, allow_incomplete=True):
'''
@ -67,6 +71,42 @@ def fit_into_bounds(image_width, image_height, frame_width, frame_height):
return (new_width, new_height)
def get_mimetype(filepath):
extension = os.path.splitext(filepath)[1].replace('.', '')
if extension in constants.ADDITIONAL_MIMETYPES:
return constants.ADDITIONAL_MIMETYPES[extension]
mimetype = mimetypes.guess_type(filepath)[0]
if mimetype is not None:
mimetype = mimetype.split('/')[0]
return mimetype
def hyphen_range(s):
'''
Given a string like '1-3', return ints (1, 3) representing lower
and upper bounds.
Supports bytestring.parsebytes and hh:mm:ss format.
'''
s = s.strip()
s = s.replace(' ', '')
if not s:
return (None, None)
parts = s.split('-')
parts = [part.strip() or None for part in parts]
if len(parts) == 1:
low = parts[0]
high = None
elif len(parts) == 2:
(low, high) = parts
else:
raise ValueError('Too many hyphens')
low = _unitconvert(low)
high = _unitconvert(high)
if low is not None and high is not None and low > high:
raise exceptions.OutOfOrder(s, low, high)
return low, high
def hms_to_seconds(hms):
'''
Convert hh:mm:ss string to an integer seconds.
@ -133,3 +173,114 @@ def truthystring(s):
if s in {'null', 'none'}:
return None
return False
#===============================================================================
def _minmax(key, value, minimums, maximums):
'''
When searching, this function dissects a hyphenated range string
and inserts the correct k:v pair into both minimums and maximums.
('area', '100-200', {}, {}) --> {'area': 100}, {'area': 200} (MODIFIED IN PLACE)
'''
if value is None:
return
if isinstance(value, (int, float)):
minimums[key] = value
return
try:
(low, high) = hyphen_range(value)
except ValueError:
warnings.warn(constants.WARNING_MINMAX_INVALID.format(field=key, value=value))
return
except exceptions.OutOfOrder as e:
warnings.warn(constants.WARNING_MINMAX_OOO.format(field=key, min=e.args[1], max=e.args[2]))
return
if low is not None:
minimums[key] = low
if high is not None:
maximums[key] = high
def _normalize_extensions(extensions):
'''
When searching, this function normalizes the list of inputted extensions.
'''
if isinstance(extensions, str):
extensions = extensions.split()
if extensions is None:
return set()
extensions = [e.lower().strip('.').strip() for e in extensions]
extensions = set(e for e in extensions if e)
return extensions
def _orderby(orderby):
'''
When searching, this function ensures that the user has entered a valid orderby
query, and normalizes the query text.
'random asc' --> ('random', 'asc')
'area' --> ('area', 'desc')
'''
orderby = orderby.lower().strip()
if orderby == '':
return None
orderby = orderby.split(' ')
if len(orderby) == 2:
(column, sorter) = orderby
elif len(orderby) == 1:
column = orderby[0]
sorter = 'desc'
else:
return None
#print(column, sorter)
if column not in constants.ALLOWED_ORDERBY_COLUMNS:
warnings.warn(constants.WARNING_ORDERBY_BADCOL.format(column=column))
return None
if column == 'random':
column = 'RANDOM()'
if sorter not in ['desc', 'asc']:
warnings.warn(constants.WARNING_ORDERBY_BADSORTER.format(column=column, sorter=sorter))
sorter = 'desc'
return (column, sorter)
def _setify_tags(photodb, tags, warn_bad_tags=False):
'''
When searching, this function converts the list of tag strings that the user
requested into Tag objects. If a tag doesn't exist we'll either raise an exception
or just issue a warning.
'''
if tags is None:
return set()
tagset = set()
for tag in tags:
tag = tag.strip()
if tag == '':
continue
try:
tag = photodb.get_tag(tag)
tagset.add(tag)
except NoSuchTag:
if warn_bad_tags:
warnings.warn(constants.WARNING_NO_SUCH_TAG.format(tag=tag))
continue
else:
raise
return tagset
def _unitconvert(value):
'''
When parsing hyphenated ranges, this function is used to convert
strings like "1k" to 1024 and "1:00" to 60.
'''
if value is None:
return None
if ':' in value:
return helpers.hms_to_seconds(value)
elif all(c in '0123456789.' for c in value):
return float(value)
else:
return bytestring.parsebytes(value)

View file

@ -17,16 +17,9 @@ import warnings
import constants
import decorators
import exceptions
import helpers
try:
sys.path.append('C:\\git\\else\\Bytestring')
sys.path.append('C:\\git\\else\\Pathclass')
sys.path.append('C:\\git\\else\\SpinalTap')
import bytestring
import pathclass
import spinal
except ImportError:
# pip install
# https://raw.githubusercontent.com/voussoir/else/master/_voussoirkit/voussoirkit.zip
from voussoirkit import bytestring
@ -185,164 +178,11 @@ CREATE INDEX IF NOT EXISTS index_grouprel_parentid on tag_group_rel(parentid);
CREATE INDEX IF NOT EXISTS index_grouprel_memberid on tag_group_rel(memberid);
'''.format(user_version=DATABASE_VERSION)
ALLOWED_ORDERBY_COLUMNS = [
'extension',
'width',
'height',
'ratio',
'area',
'duration',
'bytes',
'created',
'tagged_at',
'random',
]
def _helper_extension(ext):
'''
When searching, this function normalizes the list of permissible extensions.
'''
if isinstance(ext, str):
ext = [ext]
if ext is None:
return set()
ext = [e.lower().strip('.') for e in ext]
ext = [e for e in ext if e]
ext = set(ext)
return ext
def _helper_filenamefilter(subject, terms):
basename = subject.lower()
return all(term in basename for term in terms)
def _helper_minmax(key, value, minimums, maximums):
'''
When searching, this function dissects a hyphenated range string
and inserts the correct k:v pair into both minimums and maximums.
'''
if value is None:
return
if isinstance(value, (int, float)):
minimums[key] = value
return
try:
(low, high) = hyphen_range(value)
except ValueError:
warnings.warn(constants.WARNING_MINMAX_INVALID.format(field=key, value=value))
return
except OutOfOrder as e:
warnings.warn(constants.WARNING_MINMAX_OOO.format(field=key, min=e.args[1], max=e.args[2]))
return
if low is not None:
minimums[key] = low
if high is not None:
maximums[key] = high
def _helper_orderby(orderby):
'''
When searching, this function ensures that the user has entered a valid orderby
query, and normalizes the query text.
'''
orderby = orderby.lower().strip()
if orderby == '':
return None
orderby = orderby.split(' ')
if len(orderby) == 2:
(column, sorter) = orderby
elif len(orderby) == 1:
column = orderby[0]
sorter = 'desc'
else:
return None
#print(column, sorter)
if column not in ALLOWED_ORDERBY_COLUMNS:
warnings.warn(constants.WARNING_ORDERBY_BADCOL.format(column=column))
return None
if column == 'random':
column = 'RANDOM()'
if sorter not in ['desc', 'asc']:
warnings.warn(constants.WARNING_ORDERBY_BADSORTER.format(column=column, sorter=sorter))
sorter = 'desc'
return (column, sorter)
def _helper_setify(photodb, l, warn_bad_tags=False):
'''
When searching, this function converts the list of tag strings that the user
requested into Tag objects. If a tag doesn't exist we'll either raise an exception
or just issue a warning.
'''
if l is None:
return set()
s = set()
for tag in l:
tag = tag.strip()
if tag == '':
continue
try:
tag = photodb.get_tag(tag)
except NoSuchTag:
if not warn_bad_tags:
raise
warnings.warn(constants.WARNING_NO_SUCH_TAG.format(tag=tag))
continue
else:
s.add(tag)
return s
def _helper_unitconvert(value):
'''
When parsing hyphenated ranges, this function is used to convert
strings like "1k" to 1024 and "1:00" to 60.
'''
if value is None:
return None
if ':' in value:
return helpers.hms_to_seconds(value)
elif all(c in '0123456789.' for c in value):
return float(value)
else:
return bytestring.parsebytes(value)
def hyphen_range(s):
'''
Given a string like '1-3', return ints (1, 3) representing lower
and upper bounds.
Supports bytestring.parsebytes and hh:mm:ss format.
'''
s = s.strip()
s = s.replace(' ', '')
if not s:
return (None, None)
parts = s.split('-')
parts = [part.strip() or None for part in parts]
if len(parts) == 1:
low = parts[0]
high = None
elif len(parts) == 2:
(low, high) = parts
else:
raise ValueError('Too many hyphens')
low = _helper_unitconvert(low)
high = _helper_unitconvert(high)
if low is not None and high is not None and low > high:
raise OutOfOrder(s, low, high)
return low, high
def get_mimetype(filepath):
extension = os.path.splitext(filepath)[1].replace('.', '')
if extension in constants.ADDITIONAL_MIMETYPES:
return constants.ADDITIONAL_MIMETYPES[extension]
mimetype = mimetypes.guess_type(filepath)[0]
if mimetype is not None:
mimetype = mimetype.split('/')[0]
return mimetype
def getnow(timestamp=True):
'''
Return the current UTC timestamp or datetime object.
@ -376,9 +216,9 @@ def normalize_tagname(tagname):
tagname = ''.join(tagname)
if len(tagname) < constants.MIN_TAG_NAME_LENGTH:
raise TagTooShort(tagname)
raise exceptions.TagTooShort(tagname)
if len(tagname) > constants.MAX_TAG_NAME_LENGTH:
raise TagTooLong(tagname)
raise exceptions.TagTooLong(tagname)
return tagname
@ -437,9 +277,9 @@ def searchfilter_expression(photo_tags, expression, frozen_children, warn_bad_ta
value = any(option in photo_tags for option in frozen_children[token])
except KeyError:
if warn_bad_tags:
warnings.warn(constants.NO_SUCH_TAG.format(tag=token))
warnings.warn(constants.WARNING_NO_SUCH_TAG.format(tag=token))
else:
raise NoSuchTag(token)
raise exceptions.NoSuchTag(token)
return False
operand_stack.append(value)
if has_operand:
@ -573,50 +413,6 @@ def tag_export_totally_flat(tags):
result[synonym] = children
return result
####################################################################################################
####################################################################################################
class CantSynonymSelf(Exception):
pass
class NoSuchAlbum(Exception):
pass
class NoSuchGroup(Exception):
pass
class NoSuchPhoto(Exception):
pass
class NoSuchSynonym(Exception):
pass
class NoSuchTag(Exception):
pass
class PhotoExists(Exception):
pass
class TagExists(Exception):
pass
class GroupExists(Exception):
pass
class TagTooLong(Exception):
pass
class TagTooShort(Exception):
pass
class XORException(Exception):
pass
class OutOfOrder(Exception):
pass
####################################################################################################
####################################################################################################
@ -632,15 +428,16 @@ class PDBAlbumMixin:
'''
filepath = os.path.abspath(filepath)
self.cur.execute('SELECT * FROM albums WHERE associated_directory == ?', [filepath])
f = self.cur.fetchone()
if f is None:
raise NoSuchAlbum(filepath)
return self.get_album(f[SQL_ALBUM['id']])
fetch = self.cur.fetchone()
if fetch is None:
raise exceptions.NoSuchAlbum(filepath)
return self.get_album(fetch[SQL_ALBUM['id']])
def get_albums(self):
yield from self.get_things(thing_type='album')
def new_album(self,
def new_album(
self,
associated_directory=None,
commit=True,
description=None,
@ -691,7 +488,7 @@ class PDBPhotoMixin:
self.cur.execute('SELECT * FROM photos WHERE filepath == ?', [filepath])
fetch = self.cur.fetchone()
if fetch is None:
raise_no_such_thing(NoSuchPhoto, thing_name=filepath)
raise_no_such_thing(exceptions.NoSuchPhoto, thing_name=filepath)
photo = Photo(self, fetch)
return photo
@ -706,10 +503,10 @@ class PDBPhotoMixin:
temp_cur = self.sql.cursor()
temp_cur.execute('SELECT * FROM photos ORDER BY created DESC')
while True:
f = temp_cur.fetchone()
if f is None:
fetch = temp_cur.fetchone()
if fetch is None:
break
photo = Photo(self, f)
photo = Photo(self, fetch)
yield photo
@ -750,7 +547,7 @@ class PDBPhotoMixin:
database. Tags may be applied now or later.
If `allow_duplicates` is False, we will first check the database for any files
with the same path and raise PhotoExists if found.
with the same path and raise exceptions.PhotoExists if found.
Returns the Photo object.
'''
@ -759,10 +556,10 @@ class PDBPhotoMixin:
if not allow_duplicates:
try:
existing = self.get_photo_by_path(filename)
except NoSuchPhoto:
except exceptions.NoSuchPhoto:
pass
else:
exc = PhotoExists(filename, existing)
exc = exceptions.PhotoExists(filename, existing)
exc.photo = existing
raise exc
@ -874,7 +671,7 @@ class PDBPhotoMixin:
QUERY OPTIONS
warn_bad_tags:
If a tag is not found, issue a warning but continue the search.
Otherwise, a NoSuchTag exception would be raised.
Otherwise, a exceptions.NoSuchTag exception would be raised.
limit:
The maximum number of *successful* results to yield.
@ -890,18 +687,18 @@ class PDBPhotoMixin:
start_time = time.time()
maximums = {}
minimums = {}
_helper_minmax('area', area, minimums, maximums)
_helper_minmax('created', created, minimums, maximums)
_helper_minmax('width', width, minimums, maximums)
_helper_minmax('height', height, minimums, maximums)
_helper_minmax('ratio', ratio, minimums, maximums)
_helper_minmax('bytes', bytes, minimums, maximums)
_helper_minmax('duration', duration, minimums, maximums)
helpers._minmax('area', area, minimums, maximums)
helpers._minmax('created', created, minimums, maximums)
helpers._minmax('width', width, minimums, maximums)
helpers._minmax('height', height, minimums, maximums)
helpers._minmax('ratio', ratio, minimums, maximums)
helpers._minmax('bytes', bytes, minimums, maximums)
helpers._minmax('duration', duration, minimums, maximums)
orderby = orderby or []
extension = _helper_extension(extension)
extension_not = _helper_extension(extension_not)
mimetype = _helper_extension(mimetype)
extension = helpers._normalize_extensions(extension)
extension_not = helpers._normalize_extensions(extension_not)
mimetype = helpers._normalize_extensions(mimetype)
if filename is not None:
if not isinstance(filename, str):
@ -909,14 +706,14 @@ class PDBPhotoMixin:
filename = set(term.lower() for term in filename.strip().split(' '))
if (tag_musts or tag_mays or tag_forbids) and tag_expression:
raise XORException('Expression filter cannot be used with musts, mays, forbids')
raise exceptions.NotExclusive('Expression filter cannot be used with musts, mays, forbids')
tag_musts = _helper_setify(self, tag_musts, warn_bad_tags=warn_bad_tags)
tag_mays = _helper_setify(self, tag_mays, warn_bad_tags=warn_bad_tags)
tag_forbids = _helper_setify(self, tag_forbids, warn_bad_tags=warn_bad_tags)
tag_musts = helpers._setify_tags(photodb=self, tags=tag_musts, warn_bad_tags=warn_bad_tags)
tag_mays = helpers._setify_tags(photodb=self, tags=tag_mays, warn_bad_tags=warn_bad_tags)
tag_forbids = helpers._setify_tags(photodb=self, tags=tag_forbids, warn_bad_tags=warn_bad_tags)
query = 'SELECT * FROM photos'
orderby = [_helper_orderby(o) for o in orderby]
orderby = [helpers._orderby(o) for o in orderby]
orderby = [o for o in orderby if o]
if orderby:
whereable_columns = [o[0] for o in orderby if o[0] != 'RANDOM()']
@ -1025,14 +822,14 @@ class PDBTagMixin:
Redirect to get_tag_by_id or get_tag_by_name after xor-checking the parameters.
'''
if not helpers.is_xor(id, name):
raise XORException('One and only one of `id`, `name` can be passed.')
raise exceptions.NotExclusive('One and only one of `id`, `name` can be passed.')
if id is not None:
return self.get_tag_by_id(id)
elif name is not None:
return self.get_tag_by_name(name)
else:
raise_no_such_thing(NoSuchTag, thing_id=id, thing_name=name)
raise_no_such_thing(exceptions.NoSuchTag, thing_id=id, thing_name=name)
def get_tag_by_id(self, id):
return self.get_thing_by_id('tag', thing_id=id)
@ -1055,7 +852,7 @@ class PDBTagMixin:
fetch = self.cur.fetchone()
if fetch is None:
# was not a top tag or synonym
raise_no_such_thing(NoSuchTag, thing_name=tagname)
raise_no_such_thing(exceptions.NoSuchTag, thing_name=tagname)
tagname = fetch[SQL_SYN['master']]
def get_tags(self):
@ -1068,10 +865,10 @@ class PDBTagMixin:
tagname = normalize_tagname(tagname)
try:
self.get_tag_by_name(tagname)
except NoSuchTag:
except exceptions.NoSuchTag:
pass
else:
raise TagExists(tagname)
raise exceptions.TagExists(tagname)
tagid = self.generate_id('tags')
self._cached_frozen_children = None
@ -1121,10 +918,17 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
'''
def __init__(
self,
databasename=constants.DEFAULT_DBNAME,
thumbnail_folder=constants.DEFAULT_THUMBDIR,
id_length=constants.DEFAULT_ID_LENGTH,
databasename=None,
data_directory=None,
id_length=None,
):
if databasename is None:
databasename = constants.DEFAULT_DBNAME
if data_directory is None:
data_directory = constants.DEFAULT_DATADIR
if id_length is None:
id_length = constants.DEFAULT_ID_LENGTH
self.databasename = databasename
self.database_abspath = os.path.abspath(databasename)
existing_database = os.path.exists(databasename)
@ -1143,8 +947,11 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
for statement in statements:
self.cur.execute(statement)
self.thumbnail_folder = os.path.abspath(thumbnail_folder)
os.makedirs(thumbnail_folder, exist_ok=True)
self.data_directory = data_directory
self.thumbnail_folder = os.path.join(data_directory, 'site_thumbnails')
self.thumbnail_folder = os.path.abspath(self.thumbnail_folder)
os.makedirs(self.thumbnail_folder, exist_ok=True)
self.id_length = id_length
@ -1189,7 +996,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
)
try:
album = self.get_album_by_path(directory.absolute_path)
except NoSuchAlbum:
except exceptions.NoSuchAlbum:
album = self.new_album(
associated_directory=directory.absolute_path,
commit=False,
@ -1202,7 +1009,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
if current_album is None:
try:
current_album = self.get_album_by_path(current_location.absolute_path)
except NoSuchAlbum:
except exceptions.NoSuchAlbum:
current_album = self.new_album(
associated_directory=current_location.absolute_path,
commit=False,
@ -1213,13 +1020,13 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
parent = albums[current_location.parent.absolute_path]
try:
parent.add(current_album, commit=False)
except GroupExists:
except exceptions.GroupExists:
pass
#print('Added to %s' % parent.title)
for filepath in files:
try:
photo = self.new_photo(filepath.absolute_path, commit=False)
except PhotoExists as e:
except exceptions.PhotoExists as e:
photo = e.photo
current_album.add_photo(photo, commit=False)
@ -1259,7 +1066,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
filepath = filepath.absolute_path
try:
photo = self.get_photo_by_path(filepath)
except NoSuchPhoto:
except exceptions.NoSuchPhoto:
pass
else:
continue
@ -1282,7 +1089,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
try:
item = self.get_tag(name)
note = ('existing_tag', item.qualified_name())
except NoSuchTag:
except exceptions.NoSuchTag:
item = self.new_tag(name)
note = ('new_tag', item.qualified_name())
output_notes.append(note)
@ -1330,7 +1137,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
lower.join_group(higher)
note = ('join_group', '%s.%s' % (higher.name, lower.name))
output_notes.append(note)
except GroupExists:
except exceptions.GroupExists:
pass
tag = tags[-1]
@ -1340,7 +1147,7 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
note = ('new_synonym', '%s+%s' % (tag.name, synonym))
output_notes.append(note)
print('New syn %s' % synonym)
except TagExists:
except exceptions.TagExists:
pass
return output_notes
@ -1405,19 +1212,19 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
'album':
{
'class': Album,
'exception': NoSuchAlbum,
'exception': exceptions.NoSuchAlbum,
'table': 'albums',
},
'tag':
{
'class': Tag,
'exception': NoSuchTag,
'exception': exceptions.NoSuchTag,
'table': 'tags',
},
'photo':
{
'class': Photo,
'exception': NoSuchPhoto,
'exception': exceptions.NoSuchPhoto,
'table': 'photos',
},
}[thing_type]
@ -1447,7 +1254,7 @@ class GroupableMixin:
'''
Add a Tag object to this group.
If that object is already a member of another group, a GroupExists is raised.
If that object is already a member of another group, a exceptions.GroupExists is raised.
'''
if not isinstance(member, type(self)):
raise TypeError('Member must be of type %s' % type(self))
@ -1459,7 +1266,7 @@ class GroupableMixin:
that_group = self
else:
that_group = self.group_getter(id=fetch[SQL_TAGGROUP['parentid']])
raise GroupExists('%s already in group %s' % (member.name, that_group.name))
raise exceptions.GroupExists('%s already in group %s' % (member.name, that_group.name))
self.photodb._cached_frozen_children = None
self.photodb.cur.execute('INSERT INTO tag_group_rel VALUES(?, ?)', [self.id, member.id])
@ -1740,7 +1547,7 @@ class Photo(ObjectBase):
for tag in other_photo.tags():
self.add_tag(tag)
def delete(self, commit=True):
def delete(self, delete_file=False, commit=True):
'''
Delete the Photo and its relation to any tags and albums.
'''
@ -1748,6 +1555,14 @@ class Photo(ObjectBase):
self.photodb.cur.execute('DELETE FROM photos WHERE id == ?', [self.id])
self.photodb.cur.execute('DELETE FROM photo_tag_rel WHERE photoid == ?', [self.id])
self.photodb.cur.execute('DELETE FROM album_photo_rel WHERE photoid == ?', [self.id])
if delete_file:
path = self.real_path.absolute_path
if commit:
os.remove(path)
else:
queue_action = {'action': os.remove, 'args': [path]}
self.photodb.on_commit_queue.append(queue_action)
if commit:
log.debug('Committing - delete photo')
self.photodb.commit()
@ -1856,7 +1671,7 @@ class Photo(ObjectBase):
return hopeful_filepath
def mimetype(self):
return get_mimetype(self.real_filepath)
return helpers.get_mimetype(self.real_filepath)
@decorators.time_me
def reload_metadata(self, commit=True):
@ -2036,11 +1851,11 @@ class Tag(ObjectBase, GroupableMixin):
raise ValueError('Cannot assign synonym to itself.')
try:
tag = self.photodb.get_tag_by_name(synname)
except NoSuchTag:
self.photodb.get_tag_by_name(synname)
except exceptions.NoSuchTag:
pass
else:
raise TagExists(synname)
raise exceptions.TagExists(synname)
self.photodb._cached_frozen_children = None
self.photodb.cur.execute('INSERT INTO tag_synonyms VALUES(?, ?)', [synname, self.name])
@ -2103,11 +1918,11 @@ class Tag(ObjectBase, GroupableMixin):
'''
if self._cached_qualified_name:
return self._cached_qualified_name
string = self.name
qualname = self.name
for parent in self.walk_parents():
string = parent.name + '.' + string
self._cached_qualified_name = string
return string
qualname = parent.name + '.' + qualname
self._cached_qualified_name = qualname
return qualname
def remove_synonym(self, synname, commit=True):
'''
@ -2137,10 +1952,10 @@ class Tag(ObjectBase, GroupableMixin):
try:
self.photodb.get_tag(new_name)
except NoSuchTag:
except exceptions.NoSuchTag:
pass
else:
raise TagExists(new_name)
raise exceptions.TagExists(new_name)
self._cached_qualified_name = None
self.photodb._cached_frozen_children = None

View file

@ -1,3 +1,5 @@
flask
gevent
pillow
https://raw.githubusercontent.com/voussoir/else/master/_voussoirkit/voussoirkit.zip
git+https://github.com/senko/python-video-converter.git