def _get_cfg(keyname, can_raise=True): try: ns, key = [x.strip() for x in keyname.strip().split('.') if x] except ValueError: raise exceptions.APIError(utils.this_function(), "Invalid setting '{}'".format(keyname)) cfg = config.config ctx = utils.get_context() client_space = False if ns.lower() == 'this': client_space = True ns = ctx['name'] if ns.lower in config.ConfigNode.default_namespaces: raise exceptions.APIError( utils.this_function(), "Client name '{}' coincides with default namespace '{}'. Please use a different client name" .format(ns, ns.lower())) with cfg.tmp_config(ns, ctx['config']): if not cfg.key_exists(ns, key): if not client_space or (client_space and can_raise): raise exceptions.SettingsError( utils.this_function(), "Setting with key '{}' does not exist".format(keyname)) return ns, key
def update_item(item_type: enums.ItemType = enums.ItemType.Gallery, item: dict = {}, options: dict = {}): """ Update an existing item Args: item_type: type of item to create item: item messeage object Returns: bool indicating whether item was updated """ if not item: raise exceptions.APIError(utils.this_function(), "Item must be a message object") if not item.get('id'): raise exceptions.APIError(utils.this_function(), "Item must have a valid id") item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model() db_obj = db_msg.from_json(item, ignore_empty=False, skip_updating_existing=False) status = database_cmd.UpdateItem().main(db_obj, options=options) return message.Identity('status', status)
def new_item(item_type: enums.ItemType = enums.ItemType.Gallery, item: dict = {}, options: dict = {}): """ Create a new item and add it to the database Args: item_type: type of item to create item: item messeage object Returns: [] |async command| """ if not item: raise exceptions.APIError(utils.this_function(), "item must be a message object") if item.get('id', False) and not constants.dev: raise exceptions.APIError(utils.this_function(), "cannot create item with an id") item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model() db_obj = db_msg.from_json(item) cmd_id = database_cmd.AddItem(services.AsyncService.generic).run( db_obj, options=options) return message.Identity('command_id', cmd_id)
def translate(t_id: str, locale: str = None, default: str = None, placeholder: str = {}, count: int = None): """ Get a translation by translation id. Raises error if a default value was not provided and no translation was found. You can find more about translations :ref:`here <Translations>`. Args: t_id: translation id locale: locale to get translations from (will override default locale) default: default text when no translation was found placeholder: ? count: pluralization Returns: string .. seealso:: :func:`.get_locales` """ kwargs = {} trs = default kwargs["locale"] = helpers._get_locale(locale).lower() if placeholder: kwargs.update(placeholder), if count is not None: kwargs["count"] = count if default: kwargs["default"] = default if not t_id and default is None: raise exceptions.APIError(utils.this_function(), "Invalid translation id: {}".format(t_id)) elif t_id: try: trs = i18n.t(t_id, **kwargs) except KeyError as e: if default is None: raise exceptions.APIError( utils.this_function(), "Translation id '{}' not found".format(t_id)) except i18n.loaders.loader.I18nFileLoadError as e: if default is None: log.exception( "Failed to load translation file '{}' with key '{}'". format( locale if locale else config.translation_locale.value, t_id)) raise exceptions.APIError( utils.this_function(), "Failed to load translation file: {}".format(e.args)) return message.Identity("translation", trs)
def get_related_items( item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, related_type: enums.ItemType = enums.ItemType.Page, limit: int = 100, offset: int = None, ): """ Get item related to given item Args: item_type: parent item item_id: id of parent item related_type: child item limit: limit the amount of items returned offset: offset the results by n items Returns: .. code-block:: guess [ related item message object, ... ] """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) related_type = enums.ItemType.get(related_type) _, parent_model = item_type._msg_and_model() child_msg, child_model = related_type._msg_and_model() col = db.relationship_column(parent_model, child_model) if not col: raise exceptions.APIError( utils.this_function(), "{} has no relationship with {}".format(related_type, item_type)) s = constants.db_session() q = s.query(child_model.id).join(col).filter(parent_model.id == item_id) if offset: q = q.offset(offset) item_ids = q.limit(limit).all() items = database_cmd.GetModelItems().run(child_model, {x[0] for x in item_ids}) item_list = message.List(db.model_name(child_model), child_msg) [item_list.append(child_msg(x)) for x in items] return item_list
def update_metatags(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, metatags: dict = {}): """ Update metatags for an item Args: item_type: possible items are :py:attr:`.ItemType.Gallery`, :py:attr:`.ItemType.Page`, :py:attr:`.ItemType.Artist`, :py:attr:`.ItemType.Collection` item_id: id of item metatag: a dict of ``{ metatag_name : bool }`` Returns: bool indicating whether metatags were updated """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) _, db_item = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Page, enums.ItemType.Artist)) t = database_cmd.GetModelItems().run(db_item, {item_id}) if not t: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "{} with item id '{}' not found".format(item_type, item_id)) t = t[0] mtags = {} anames = db.MetaTag.all_names() for m, v in metatags.items(): if m not in anames: raise exceptions.APIError(utils.this_function(), f"Metatag name '{m}' does not exist") mtags[m] = v st = True if t: t.update("metatags", mtags) db.object_session(t).commit() else: st = False return message.Identity('status', st)
def get_common_tags(item_type: enums.ItemType = enums.ItemType.Collection, item_id: int = 0, limit: int = 10): """ Get the most common tags for item Args: item_type: possible items are :attr:`.ItemType.Artist`, :attr:`.ItemType.Grouping`, :attr:`.ItemType.Collection` item_id: id of item to fetch tags for limit: limit amount of tags returned Returns: .. code-block:: guess { namespace : [ tag message object, ...], ... } """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) _, db_item = item_type._msg_and_model( (enums.ItemType.Artist, enums.ItemType.Collection, enums.ItemType.Grouping)) nstags = database_cmd.MostCommonTags().run(db_item, item_id, limit) msg = _contruct_tags_msg(nstags) return message.Identity('tags', msg)
def get_related_count(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, related_type: enums.ItemType = enums.ItemType.Page): """ Get count of items related to given item Args: item_type: parent item item_id: id of parent item related_type: child item Returns: ``` { 'id': int, 'count': int } ``` """ item_type = enums.ItemType.get(item_type) related_type = enums.ItemType.get(related_type) _, parent_model = item_type._msg_and_model() child_msg, child_model = related_type._msg_and_model() col = db.relationship_column(parent_model, child_model) if not col: raise exceptions.APIError( utils.this_function(), "{} has no relationship with {}".format(related_type, item_type)) s = constants.db_session() count = s.query( child_model.id).join(col).filter(parent_model.id == item_id).count() return message.Identity('count', {'id': item_id, 'count': count})
def set_config(cfg: dict): """ Set/update configuration Args: cfg: a dict containing ``namespace.key``:``value`` Returns: Status """ client_cfg = utils.get_context()['config'] for set_key in cfg: ns, key = _get_cfg(set_key, False) default_ns = ns.lower() in config.ConfigNode.default_namespaces if default_ns: t = config.ConfigNode.get_type(ns, key) if not isinstance(cfg[set_key], t): raise exceptions.APIError( utils.this_function(), "Setting '{}' expected '{}' but got '{}'".format( set_key, t, type(cfg[set_key]))) if config.ConfigNode.get_isolation_level( ns, key) == config.ConfigIsolation.client: client_cfg.setdefault(config.config.format_namespace(ns), {})[key.lower()] = cfg[set_key] continue with config.config.namespace(ns): config.config.update(key, cfg[set_key], create=not default_ns) return message.Message("updated")
def get_item(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0): """ Get item Args: item_type: type of item to get item_id: id of item Returns: item message object """ if not item_id: raise exceptions.APIError( utils.this_function(), f"A valid item id is required, not {item_id}") item_type = enums.ItemType.get(item_type) db_msg, db_model = item_type._msg_and_model() item = database_cmd.GetModelItems().run(db_model, {item_id})[0] if not item: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format(item_type.name, item_id)) return db_msg(item)
def search_tags( search_query: str = "", search_options: dict = {}, only_namespace: bool = False, only_tag: bool = False, sort_by: enums.ItemSort = None, sort_desc: bool = False, limit: int = 100, offset: int = None, ): """ Search for tags Args: search_query: search string search_options: options to apply when filtering, see :ref:`Settings` for available search options only_namespace: only search for matching namespace <not implemented yet> only_tag: only search for matching tag <not implemented yet> sort_by: either a :py:class:`.ItemSort` or a sort index sort_desc: order descending (default is ascending) limit: limit the amount of items returned offset: offset the results by n items Returns: .. code-block:: guess { namespace : [ tag message object, ...], ... } """ if search_options: search_option_names = [ x.name for x in search_cmd._get_search_options() ] for n in search_options: if n not in search_option_names: raise exceptions.APIError( utils.this_function(), "Invalid search option name '{}'".format(n)) db_model = db.NamespaceTags model_ids = search_cmd.ModelFilter().run(db_model, search_query, search_options) order_exp, group_exp, join_exp = helpers._sort_helper( sort_by, sort_desc, db_model) items = database_cmd.GetModelItems().run(db_model, model_ids, limit=limit, offset=offset, join=join_exp, order_by=order_exp, group_by=group_exp) msg = _contruct_tags_msg(items) return message.Identity('tags', msg)
def get_tags(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int = 0, raw: bool = False): """ Get tags for item Args: item_type: possible items are :attr:`.ItemType.Gallery`, :attr:`.ItemType.Page`, :attr:`.ItemType.Grouping`, :attr:`.ItemType.Collection` item_id: id of item to fetch tags for raw: if true, tags from descendant ItemType's will not be included (this only makes sense when ItemType is :attr:`.ItemType.Gallery`) Returns: .. code-block:: guess { namespace : [ tag message object, ...], ... } """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) _, db_item = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping, enums.ItemType.Page)) db_obj = database_cmd.GetModelItems().run(db_item, {item_id}) if db_obj: db_obj = db_obj[0] nstags = [] if db_obj: g_objs = [] if issubclass(db_item, db.TaggableMixin): nstags = db_obj.tags.all() if not raw and isinstance(db_obj, db.Gallery): g_objs.append(db_obj) else: for g in db_obj.galleries.all(): nstags.extend(g.tags.all()) if not raw: g_objs.append(g) for g_obj in g_objs: for p in g_obj.pages.all(): # TODO: we only need tags nstags.extend(p.tags.all()) msg = _contruct_tags_msg(nstags) return message.Identity('tags', msg)
def get_settings(settings: list = [], ctx=None): """ Set settings Send empty list to get all key:values Args: set_list: a list of setting keys Returns: ``` { 'key.name': value } ``` """ utils.require_context(ctx) values = {} if settings: for set_key in settings: try: ns, key = [x.strip() for x in set_key.strip().split('.') if x] except ValueError: raise exceptions.APIError( utils.this_function(), "Invalid setting: '{}'".format(set_key)) if constants.config.key_exists(ns, key): values[set_key] = constants.config.get(ns, key) elif ns.lower() == 'self' and ctx.config and ctx.config.key_exists( ns, key): values[set_key] = ctx.config.get(ns, key) raise NotImplementedError else: raise exceptions.APIError( utils.this_function(), "Setting doesn't exist: '{}'".format(set_key)) else: raise NotImplementedError return message.Identity('settings', values)
def get_page(page_id: int = None, gallery_id: int = None, number: int = None, prev: bool = False): """ Get next/prev page by either gallery or page id Args: page_id: id of page gallery_id: id of gallery number: retrieve specific page number prev: by default next page is retrieved, to retrieve prev page set this to true Returns: Page object """ if not (gallery_id or page_id): raise exceptions.APIError( utils.this_function(), "Either a gallery id or page id is required") if number is None: number = 0 item = None if page_id: p = database_cmd.GetModelItems().run(db.Page, {page_id})[0] if number and p and number == p.number: item = p elif p: number = number or p.number gallery_id = p.gallery_id if not item: f = db.Page.number < number if prev else db.Page.number == number f = db.and_op(f, db.Page.gallery_id == gallery_id) item = database_cmd.GetModelItems().run( db.Page, order_by=db.Page.number.desc() if prev else db.Page.number, filter=f, limit=1) if item: item = item[0] return message.Page(item) if item else None
def _msg_and_model(item_type, allowed=tuple(), error=True): """ Get the equivalent Message and Database object classes for ItemType member Args: allowed: a tuple of ItemType members which are allowed, empty tuple for all members error: raise error if equivalent is not found, else return generic message object class """ if allowed and repr(item_type) not in (repr(x) for x in allowed): raise exceptions.APIError( utils.this_function(), "ItemType must be on of {} not '{}'".format( allowed, repr(item_type))) db_model = None try: db_model = getattr(db, item_type.name) except AttributeError: if error: raise exceptions.CoreError( utils.this_function(), "Equivalent database object class for {} was not found". format(item_type)) obj = None try: obj = getattr(message, item_type.name) except AttributeError: try: if db_model and issubclass(db_model, db.NameMixin): obj = getattr(message, db.NameMixin.__name__) except AttributeError: pass if not obj: if error: raise exceptions.CoreError( utils.this_function(), "Equivalent Message object class for {} was not found". format(item_type)) obj = message.DatabaseMessage return obj, db_model
def _get_cfg(keyname, ctx): try: ns, key = [x.strip() for x in keyname.strip().split('.') if x] except ValueError: raise exceptions.APIError( utils.this_function(), "Invalid setting keyname: '{}'".format(keyname)) cfg = constants.config if ns.lower() == 'this': pass if not cfg.key_exists(ns, key): raise exceptions.SettingsError( utils.this_function(), "Setting doesn't exist: '{}'".format(keyname)) return ns, key, cfg
def add_to_filter(gallery_id: int = 0, item_id: int = 0, item: dict = {}): """ Add a gallery to a galleryfilter Args: gallery_id: id of gallery item_id: id of existing galleryfilter, mutually exclusive with ``item`` parameter item: filter message object, mutually exclusive with ``item_id`` parameter Returns: bool whether gallery was added to filter or not """ if not gallery_id: raise exceptions.APIError(utils.this_function(), "gallery_id must be a valid gallery id") g = database_cmd.GetModelItems().run(db.Gallery, {gallery_id}) if not g: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format( enums.ItemType.Gallery.name, gallery_id)) g = g[0] if item_id: p = database_cmd.GetModelItems().run(db.GalleryFilter, {item_id}) if not p: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format( enums.ItemType.GalleryFilter.name, item_id)) p = p[0] elif item: p = message.GalleryFilter.from_json(item) g.filters.append(p) s = constants.db_session() s.add(g) s.commit() return message.Identity("status", True)
def update_item_tags(item_type: enums.ItemType = enums.ItemType.Gallery, item_id: int=0, tags: dict={}): """ Update tags on an item Args: item_type: possible items are :attr:`.ItemType.Gallery`, :attr:`.ItemType.Page`, :attr:`.ItemType.Grouping`, :attr:`.ItemType.Collection` item_id: id of item to update tags for tags: tags Returns: bool whether tags were updated or not """ if not item_id: raise exceptions.APIError(utils.this_function(), "item_id must be a valid item id") item_type = enums.ItemType.get(item_type) _, db_item = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping, enums.ItemType.Page)) db_obj = database_cmd.GetModelItems().run(db_item, {item_id}) if not db_obj: raise exceptions.DatabaseItemNotFoundError(utils.this_function(), "'{}' with id '{}' was not found".format(item_type.name, item_id)) db_obj = db_obj[0] tags = _decontruct_tags_msg(tags) s = constants.db_session() with db.no_autoflush(s): s.add(db_obj) db_obj.tags = tags s.commit() return message.Identity("status", True)
def remove_from_filter(gallery_id: int = 0, item_id: int = 0): """ Remove a gallery from a galleryfilter Args: gallery_id: id of gallery item_id: id of existing galleryfilter Returns: bool whether gallery was removed from filter or not """ if not gallery_id: raise exceptions.APIError(utils.this_function(), "gallery_id must be a valid gallery id") g = database_cmd.GetModelItems().run(db.Gallery, {gallery_id}) if not g: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format( enums.ItemType.Gallery.name, gallery_id)) g = g[0] p = database_cmd.GetModelItems().run(db.GalleryFilter, {item_id}) if not p: raise exceptions.DatabaseItemNotFoundError( utils.this_function(), "'{}' with id '{}' was not found".format( enums.ItemType.GalleryFilter.name, item_id)) p = p[0] g.filters.remove(p) s = constants.db_session() s.add(g) s.commit() return message.Identity("status", True)
def _view_helper(item_type: enums.ItemType=enums.ItemType.Gallery, search_query: str = "", filter_id: int = None, view_filter: enums.ViewType = enums.ViewType.Library, item_id: int = None, related_type: enums.ItemType = None, search_options: dict = {}, ): if view_filter is not None: view_filter = enums.ViewType.get(view_filter) if related_type is not None: related_type = enums.ItemType.get(related_type) item_type = enums.ItemType.get(item_type) if search_options: search_option_names = [x.name for x in search_cmd._get_search_options()] for n in search_options: if n not in search_option_names: raise exceptions.APIError(utils.this_function(), "Invalid search option name '{}'".format(n)) filter_op = [] join_exp = [] parent_model = None db_msg, db_model = item_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Collection, enums.ItemType.Grouping)) if related_type: parent_model = db_model db_msg, db_model = related_type._msg_and_model( (enums.ItemType.Gallery, enums.ItemType.Page)) col = db.relationship_column(parent_model, db_model) if not col: raise exceptions.APIError( utils.this_function(), "{} has no relationship with {}".format( related_type, item_type)) if item_id is None: raise exceptions.APIError(utils.this_function(), "Missing id of parent item") if filter_id: if db_model != db.Gallery: g_col = db.relationship_column(db_model, db.Gallery) if not g_col: raise exceptions.APIError( utils.this_function(), "Cannot use {} because {} has no relationship with {}".format( enums.ItemType.GalleryFilter, related_type if related_type else item_type, enums.ItemType.Gallery)) join_exp.append(g_col) join_exp.append(db.relationship_column(db.Gallery, db.GalleryFilter)) filter_op.append(db.GalleryFilter.id == filter_id) model_ids = None if not db_model == db.Page: model_ids = search_cmd.ModelFilter().run(db_model, search_query, search_options) metatag_name = None if view_filter == enums.ViewType.Favorite: metatag_name = db.MetaTag.names.favorite elif view_filter == enums.ViewType.Inbox: metatag_name = db.MetaTag.names.inbox elif view_filter == enums.ViewType.Trash: metatag_name = db.MetaTag.names.trash if metatag_name: if hasattr(db_model, "metatags"): filter_op.append(db.MetaTag.name == metatag_name) join_exp.append(db_model.metatags) elif view_filter == enums.ViewType.Library: if hasattr(db_model, "metatags"): filter_op.append(~db_model.metatags.any(db.MetaTag.name == db.MetaTag.names.inbox)) filter_op.append(~db_model.metatags.any(db.MetaTag.name == db.MetaTag.names.trash)) elif view_filter == enums.ViewType.All: if hasattr(db_model, "metatags"): filter_op.append(~db_model.metatags.any(db.MetaTag.name == db.MetaTag.names.trash)) if related_type: filter_op.append(parent_model.id == item_id) join_exp.append(col) if len(filter_op) > 1: filter_op = db.and_op(*filter_op) elif filter_op: filter_op = filter_op[0] else: filter_op = None return view_filter, item_type, db_msg, db_model, model_ids, filter_op, join_exp, metatag_name
def search_item( item_type: enums.ItemType = enums.ItemType.Gallery, search_query: str = "", search_options: dict = {}, sort_by: enums.ItemSort = None, sort_desc: bool = False, full_search: bool = True, limit: int = 100, offset: int = None, ): """ Search for item Args: item_type: all of :py:attr:`.ItemType` except :py:attr:`.ItemType.Page` and :py:attr:`.ItemType.GalleryFilter` search_query: filter item by search terms search_options: options to apply when filtering, see :ref:`Settings` for available search options sort_by: either a :py:class:`.ItemSort` or a sort index sort_desc: order descending (default is ascending) limit: amount of items offset: offset the results by n items Returns: .. code-block:: guess [ item message object, ... ] .. seealso:: :func:`.get_sort_indexes` """ item_type = enums.ItemType.get(item_type) if search_options: search_option_names = [ x.name for x in search_cmd._get_search_options() ] for n in search_options: if n not in search_option_names: raise exceptions.APIError( utils.this_function(), "Invalid search option name '{}'".format(n)) if item_type in (enums.ItemType.Page, enums.ItemType.GalleryFilter): raise exceptions.APIError(utils.this_function(), "Unsupported itemtype {}".format(item_type)) db_msg, db_model = item_type._msg_and_model() model_ids = set() if full_search: model_ids = search_cmd.ModelFilter().run(db_model, search_query, search_options) items = message.List("items", db_msg) order_exp, group_exp, join_exp = helpers._sort_helper( sort_by, sort_desc, db_model) [ items.append(db_msg(x)) for x in database_cmd.GetModelItems().run(db_model, model_ids, limit=limit, offset=offset, join=join_exp, order_by=order_exp, group_by=group_exp) ] return items