diff --git a/etiquette/constants.py b/etiquette/constants.py index 21fb58e..776474f 100644 --- a/etiquette/constants.py +++ b/etiquette/constants.py @@ -150,35 +150,52 @@ DEFAULT_THUMBDIR = 'site_thumbnails' DEFAULT_CONFIGURATION = { 'log_level': logging.DEBUG, - 'cache_size_album': 1000, - 'cache_size_photo': 100000, - 'cache_size_tag': 1000, - 'cache_size_user': 200, + 'cache_size': { + 'album': 1000, + 'photo': 100000, + 'tag': 1000, + 'user': 200, + }, - 'enable_album_edit': True, - 'enable_login': True, - 'enable_new_album': True, - 'enable_new_bookmark': True, - 'enable_new_photo': True, - 'enable_new_tag': True, - 'enable_new_user': True, - 'enable_bookmark_edit': True, - 'enable_photo_add_remove_tag': True, - 'enable_photo_edit': True, - 'enable_photo_generate_thumbnail': True, - 'enable_photo_reload_metadata': True, - 'enable_tag_edit': True, + 'enable_feature': { + 'album': { + 'edit': True, + 'new': True, + }, + 'bookmark': { + 'edit': True, + 'new': True, + }, + 'photo': { + 'add_remove_tag': True, + 'new': True, + 'edit': True, + 'generate_thumbnail': True, + 'reload_metadata': True, + }, + 'tag': { + 'edit': True, + 'new': True, + }, + 'user': { + 'login': True, + 'new': True, + }, + }, - 'min_tag_name_length': 1, - 'max_tag_name_length': 32, - 'valid_tag_chars': string.ascii_lowercase + string.digits + '_()', + 'tag': { + 'min_length': 1, + 'max_length': 32, + 'valid_chars': string.ascii_lowercase + string.digits + '_()', + }, - 'min_username_length': 2, - 'max_username_length': 24, - 'valid_username_chars': string.ascii_letters + string.digits + '~!@#$%^*()[]{}:;,.<>/\\-_+=', - 'min_password_length': 6, + 'user': { + 'min_length': 2, + 'min_password_length': 6, + 'max_length': 24, + 'valid_chars': string.ascii_letters + string.digits + '~!@#$%^*()[]{}:;,.<>/\\-_+=', + }, - 'id_length': 12, 'digest_exclude_files': [ 'phototagger.db', 'desktop.ini', @@ -188,9 +205,10 @@ DEFAULT_CONFIGURATION = { '_site_thumbnails', ], - 'file_read_chunk': 2 ** 20, + 'id_length': 12, 'thumbnail_width': 400, 'thumbnail_height': 400, + 'motd_strings': [ 'Good morning, Paul. What will your first sequence of the day be?', ], diff --git a/etiquette/decorators.py b/etiquette/decorators.py index 86ca384..3df0e09 100644 --- a/etiquette/decorators.py +++ b/etiquette/decorators.py @@ -22,8 +22,20 @@ def required_feature(features): else: config = self.config - if not all(config[key] for key in features): - raise exceptions.FeatureDisabled(function.__name__) + config = config['enable_feature'] + + # Using the received string like "photo.new", try to navigate the + # config and wind up at a True. + # Allow KeyErrors to raise themselves. + for feature in features: + cfg = config + pieces = feature.split('.') + for piece in pieces: + cfg = cfg[piece] + if cfg is False: + raise exceptions.FeatureDisabled(function.__qualname__) + if cfg is not True: + raise ValueError('Bad required_feature "%s" led to %s' % (feature, cfg)) return function(self, *args, **kwargs) return wrapped diff --git a/etiquette/objects.py b/etiquette/objects.py index a2b7391..0e8fcfb 100644 --- a/etiquette/objects.py +++ b/etiquette/objects.py @@ -241,11 +241,11 @@ class Album(ObjectBase, GroupableMixin): self._sum_bytes_photos = None self._sum_bytes_albums = None - @decorators.required_feature('enable_album_edit') + @decorators.required_feature('album.edit') def add(self, *args, **kwargs): return super().add(*args, **kwargs) - @decorators.required_feature('enable_album_edit') + @decorators.required_feature('album.edit') @decorators.transaction def add_associated_directory(self, filepath, *, commit=True): filepath = pathclass.Path(filepath) @@ -277,7 +277,7 @@ class Album(ObjectBase, GroupableMixin): self.photodb.log.debug('Committing - add associated directory') self.photodb.commit() - @decorators.required_feature('enable_album_edit') + @decorators.required_feature('album.edit') @decorators.transaction def add_photo(self, photo, *, commit=True): if self.photodb != photo.photodb: @@ -325,7 +325,7 @@ class Album(ObjectBase, GroupableMixin): directories = [pathclass.Path(x) for x in directories] return directories - @decorators.required_feature('enable_album_edit') + @decorators.required_feature('album.edit') @decorators.transaction def delete(self, *, delete_children=False, commit=True): self.photodb.log.debug('Deleting album {album:r}'.format(album=self)) @@ -346,7 +346,7 @@ class Album(ObjectBase, GroupableMixin): else: return self.id - @decorators.required_feature('enable_album_edit') + @decorators.required_feature('album.edit') @decorators.transaction def edit(self, title=None, description=None, *, commit=True): ''' @@ -378,11 +378,11 @@ class Album(ObjectBase, GroupableMixin): ) return cur.fetchone() is not None - @decorators.required_feature('enable_album_edit') + @decorators.required_feature('album.edit') def join_group(self, *args, **kwargs): return super().join_group(*args, **kwargs) - @decorators.required_feature('enable_album_edit') + @decorators.required_feature('album.edit') def leave_group(self, *args, **kwargs): return super().leave_group(*args, **kwargs) @@ -400,7 +400,7 @@ class Album(ObjectBase, GroupableMixin): photos.sort(key=lambda x: x.basename.lower()) return photos - @decorators.required_feature('enable_album_edit') + @decorators.required_feature('album.edit') @decorators.transaction def remove_photo(self, photo, *, commit=True): if not self.has_photo(photo): @@ -454,7 +454,7 @@ class Bookmark(ObjectBase): def __repr__(self): return 'Bookmark:{id}'.format(id=self.id) - @decorators.required_feature('enable_bookmark_edit') + @decorators.required_feature('bookmark.edit') @decorators.transaction def delete(self, *, commit=True): cur = self.photodb.sql.cursor() @@ -462,7 +462,7 @@ class Bookmark(ObjectBase): if commit: self.photodb.commit() - @decorators.required_feature('enable_bookmark_edit') + @decorators.required_feature('bookmark.edit') @decorators.transaction def edit(self, title=None, url=None, *, commit=True): if title is None and url is None: @@ -547,7 +547,7 @@ class Photo(ObjectBase): def _uncache(self): self.photodb.caches['photo'].remove(self.id) - @decorators.required_feature('enable_photo_add_remove_tag') + @decorators.required_feature('photo.add_remove_tag') @decorators.transaction def add_tag(self, tag, *, commit=True): tag = self.photodb.get_tag(tag) @@ -599,7 +599,7 @@ class Photo(ObjectBase): return bytestring.bytestring(self.bytes) return '??? b' - @decorators.required_feature('enable_photo_add_remove_tag') + @decorators.required_feature('photo.add_remove_tag') def copy_tags(self, other_photo): ''' Take all of the tags owned by other_photo and apply them to this photo. @@ -607,7 +607,7 @@ class Photo(ObjectBase): for tag in other_photo.tags(): self.add_tag(tag) - @decorators.required_feature('enable_photo_edit') + @decorators.required_feature('photo.edit') @decorators.transaction def delete(self, *, delete_file=False, commit=True): ''' @@ -638,7 +638,7 @@ class Photo(ObjectBase): return helpers.seconds_to_hms(self.duration) #@decorators.time_me - @decorators.required_feature('enable_photo_generate_thumbnail') + @decorators.required_feature('photo.generate_thumbnail') @decorators.transaction def generate_thumbnail(self, *, commit=True, **special): ''' @@ -773,7 +773,7 @@ class Photo(ObjectBase): return hopeful_filepath #@decorators.time_me - @decorators.required_feature('enable_photo_reload_metadata') + @decorators.required_feature('photo.reload_metadata') @decorators.transaction def reload_metadata(self, *, commit=True): ''' @@ -830,7 +830,7 @@ class Photo(ObjectBase): self.photodb.log.debug('Committing - reload metadata') self.photodb.commit() - @decorators.required_feature('enable_photo_edit') + @decorators.required_feature('photo.edit') @decorators.transaction def relocate(self, new_filepath, *, allow_duplicates=False, commit=True): ''' @@ -864,7 +864,7 @@ class Photo(ObjectBase): self.photodb.log.debug('Commit - relocate photo') self.photodb.commit() - @decorators.required_feature('enable_photo_add_remove_tag') + @decorators.required_feature('photo.add_remove_tag') @decorators.transaction def remove_tag(self, tag, *, commit=True): tag = self.photodb.get_tag(tag) @@ -884,7 +884,7 @@ class Photo(ObjectBase): self.photodb.log.debug('Committing - remove photo tag') self.photodb.commit() - @decorators.required_feature('enable_photo_edit') + @decorators.required_feature('photo.edit') @decorators.transaction def rename_file(self, new_filename, *, move=False, commit=True): ''' @@ -1007,11 +1007,11 @@ class Tag(ObjectBase, GroupableMixin): self.photodb.caches['tag'].remove(self.id) self._cached_qualified_name = None - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') def add(self, *args, **kwargs): return super().add(*args, **kwargs) - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') @decorators.transaction def add_synonym(self, synname, *, commit=True): synname = self.photodb.normalize_tagname(synname) @@ -1037,7 +1037,7 @@ class Tag(ObjectBase, GroupableMixin): return synname - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') @decorators.transaction def convert_to_synonym(self, mastertag, *, commit=True): ''' @@ -1087,7 +1087,7 @@ class Tag(ObjectBase, GroupableMixin): self.photodb.log.debug('Committing - convert to synonym') self.photodb.commit() - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') @decorators.transaction def delete(self, *, delete_children=False, commit=True): self.photodb.log.debug('Deleting tag {tag:r}'.format(tag=self)) @@ -1102,7 +1102,7 @@ class Tag(ObjectBase, GroupableMixin): self.photodb.log.debug('Committing - delete tag') self.photodb.commit() - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') @decorators.transaction def edit(self, description=None, *, commit=True): ''' @@ -1121,11 +1121,11 @@ class Tag(ObjectBase, GroupableMixin): self.photodb.log.debug('Committing - edit tag') self.photodb.commit() - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') def join_group(self, *args, **kwargs): return super().join_group(*args, **kwargs) - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') def leave_group(self, *args, **kwargs): return super().leave_group(*args, **kwargs) @@ -1164,7 +1164,7 @@ class Tag(ObjectBase, GroupableMixin): return qualname - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') @decorators.transaction def remove_synonym(self, synname, *, commit=True): ''' @@ -1191,7 +1191,7 @@ class Tag(ObjectBase, GroupableMixin): self.photodb.log.debug('Committing - remove synonym') self.photodb.commit() - @decorators.required_feature('enable_tag_edit') + @decorators.required_feature('tag.edit') @decorators.transaction def rename(self, new_name, *, apply_to_synonyms=True, commit=True): ''' diff --git a/etiquette/photodb.py b/etiquette/photodb.py index 2414b4a..7054599 100644 --- a/etiquette/photodb.py +++ b/etiquette/photodb.py @@ -215,7 +215,7 @@ class PDBAlbumMixin: if album.parent() is None: yield album - @decorators.required_feature('enable_new_album') + @decorators.required_feature('album.new') @decorators.transaction def new_album( self, @@ -283,7 +283,7 @@ class PDBBookmarkMixin: def get_bookmarks(self): yield from self.get_things(thing_type='bookmark') - @decorators.required_feature('enable_new_bookmark') + @decorators.required_feature('bookmark.new') @decorators.transaction def new_bookmark(self, url, title=None, *, author=None, commit=True): if not url: @@ -356,7 +356,7 @@ class PDBPhotoMixin: if count <= 0: break - @decorators.required_feature('enable_new_photo') + @decorators.required_feature('photo.new') @decorators.transaction def new_photo( self, @@ -894,7 +894,7 @@ class PDBTagMixin: if tag.parent() is None: yield tag - @decorators.required_feature('enable_new_tag') + @decorators.required_feature('tag.new') @decorators.transaction def new_tag(self, tagname, description=None, *, commit=True): ''' @@ -936,13 +936,13 @@ class PDBTagMixin: tagname = tagname.lower() tagname = tagname.replace('-', '_') tagname = tagname.replace(' ', '_') - tagname = (c for c in tagname if c in self.config['valid_tag_chars']) + tagname = (c for c in tagname if c in self.config['tag']['valid_chars']) tagname = ''.join(tagname) - if len(tagname) < self.config['min_tag_name_length']: + if len(tagname) < self.config['tag']['min_length']: raise exceptions.TagTooShort(original_tagname) - elif len(tagname) > self.config['max_tag_name_length']: + elif len(tagname) > self.config['tag']['max_length']: raise exceptions.TagTooLong(tagname) else: @@ -1006,7 +1006,7 @@ class PDBUserMixin: author_id = None return author_id - @decorators.required_feature('enable_login') + @decorators.required_feature('user.login') def login(self, user_id, password): cur = self.sql.cursor() cur.execute('SELECT * FROM users WHERE id == ?', [user_id]) @@ -1026,30 +1026,30 @@ class PDBUserMixin: return objects.User(self, fetch) - @decorators.required_feature('enable_new_user') + @decorators.required_feature('user.new') @decorators.transaction def register_user(self, username, password, commit=True): - if len(username) < self.config['min_username_length']: + if len(username) < self.config['user']['min_length']: raise exceptions.UsernameTooShort( username=username, - min_length=self.config['min_username_length'] + min_length=self.config['user']['min_length'] ) - if len(username) > self.config['max_username_length']: + if len(username) > self.config['user']['max_length']: raise exceptions.UsernameTooLong( username=username, - max_length=self.config['max_username_length'] + max_length=self.config['user']['max_length'] ) - badchars = [c for c in username if c not in self.config['valid_username_chars']] + badchars = [c for c in username if c not in self.config['user']['valid_chars']] if badchars: raise exceptions.InvalidUsernameChars(username=username, badchars=badchars) if not isinstance(password, bytes): password = password.encode('utf-8') - if len(password) < self.config['min_password_length']: - raise exceptions.PasswordTooShort(min_length=self.config['min_password_length']) + if len(password) < self.config['user']['min_password_length']: + raise exceptions.PasswordTooShort(min_length=self.config['user']['min_password_length']) try: existing_user = self.get_user(username=username) @@ -1141,10 +1141,10 @@ class PhotoDB(PDBAlbumMixin, PDBBookmarkMixin, PDBPhotoMixin, PDBTagMixin, PDBUs self.on_commit_queue = [] self._cached_frozen_children = None - self._album_cache.maxlen = self.config['cache_size_album'] - self._photo_cache.maxlen = self.config['cache_size_photo'] - self._tag_cache.maxlen = self.config['cache_size_tag'] - self._user_cache.maxlen = self.config['cache_size_user'] + self._album_cache.maxlen = self.config['cache_size']['album'] + self._photo_cache.maxlen = self.config['cache_size']['photo'] + self._tag_cache.maxlen = self.config['cache_size']['tag'] + self._user_cache.maxlen = self.config['cache_size']['user'] self.caches = { 'album': self._album_cache, 'photo': self._photo_cache,