예제 #1
0
 def __init__(self, session: Session, taxon_ids: ClassifIDListT):
     tf = Taxonomy.__table__.alias('tf')
     # bind = None  # For portable SQL, no 'ilike'
     bind = session.get_bind()
     select_list = [
         tf.c.id, tf.c.display_name, tf.c.name, tf.c.nbrobj, tf.c.nbrobjcum
     ]
     select_list.extend([
         text("t%d.name" % level)  # type:ignore
         for level in range(1, TaxonomyBO.MAX_TAXONOMY_LEVELS)
     ])
     qry = select(select_list, bind=bind)
     # Inject the recursive query, for getting parents
     _dumm, qry = TaxonomyBO._add_recursive_query(qry, tf, do_concat=False)
     qry = qry.where(tf.c.id == any_(taxon_ids))
     # Add another join for getting children
     logger.info("Taxo query: %s with IDs %s", qry, taxon_ids)
     res: ResultProxy = session.execute(qry)
     self.taxa: List[TaxonBO] = []
     for a_rec in res.fetchall():
         lst_rec = list(a_rec)
         an_id, display_name, db_name, nbobj1, nbobj2 = lst_rec.pop(0), lst_rec.pop(0), lst_rec.pop(0), \
                                                        lst_rec.pop(0), lst_rec.pop(0)
         lineage = [db_name] + [name for name in lst_rec if name]
         self.taxa.append(
             TaxonBO(an_id, display_name, db_name, nbobj1, nbobj2,
                     lineage))  # type:ignore
     self.get_children(session, self.taxa)
예제 #2
0
    def generous_merge_into(cls, session: Session, dest_prj_id: int,
                            src_prj_id: int):
        """
            Merge privileges from source project into destination project.
        """
        # Each user who is present in both projects, gets the highest privilege from both projects.
        # TODO: Arguable
        sql = text("""
               UPDATE projectspriv ppdst
                  SET privilege = CASE WHEN 'Manage' IN (ppsrc.privilege, ppdst.privilege) 
                                           THEN 'Manage'
                                       WHEN 'Annotate' IN (ppsrc.privilege, ppdst.privilege) 
                                           THEN 'Annotate'
                                       ELSE 'View' 
                                  END
                 FROM projectspriv ppsrc
                WHERE ppsrc.projid = :src_prj 
                  AND ppdst.projid = :dst_prj 
                  AND ppsrc.member = ppdst.member""")
        session.execute(sql, {"dst_prj": dest_prj_id, "src_prj": src_prj_id})
        # Users who were only in source project get their privileges transferred into destination
        # TODO: Arguable
        sql = text("""
                UPDATE projectspriv
                   SET projid = :dst_prj 
                 WHERE projid = :src_prj 
                   AND member NOT IN (SELECT member 
                                        FROM projectspriv 
                                       WHERE projid = :dst_prj)""")

        session.execute(sql, {"dst_prj": dest_prj_id, "src_prj": src_prj_id})
예제 #3
0
    def _delete_chunk(session: Session,
                      a_chunk: ObjectIDListT) -> Tuple[int, int, List[str]]:
        """
            Delete a chunk from self's object list.
            Technical Note: We use SQLA Core as we don't want to fetch the rows
        """
        # Start with images which are not deleted via a CASCADE on DB side
        # This is maybe due to relationship cycle b/w ObjectHeader and Images @See comment in Image class
        img_del_qry: Delete = Image.__table__.delete()
        img_del_qry = img_del_qry.where(Image.objid == any_(a_chunk))
        img_del_qry = img_del_qry.returning(Image.file_name,
                                            Image.thumb_file_name)
        with CodeTimer("DELETE for %d images: " % len(a_chunk), logger):
            files_res = session.execute(img_del_qry)
            img_files = []
            nb_img_rows = 0
            for a_file_tuple in files_res:
                # We have main file and optionally the thumbnail one
                for a_file in a_file_tuple:
                    if a_file:
                        img_files.append(a_file)
                nb_img_rows += 1
            logger.info("Removed: %d rows, to remove: %d files", nb_img_rows,
                        len(img_files))

        obj_del_qry: Delete = ObjectHeader.__table__.delete()
        obj_del_qry = obj_del_qry.where(ObjectHeader.objid == any_(a_chunk))
        with CodeTimer("DELETE for %d objs: " % len(a_chunk), logger):
            nb_objs = session.execute(obj_del_qry).rowcount

        session.commit()
        return nb_objs, nb_img_rows, img_files
예제 #4
0
 def grant(session: Session, user: User, action: Action, prj: Project):
     """
         Grant the possibility to do this action on this project to this user.
     """
     privilege = ProjectPrivilege()
     privilege.privilege = ACTION_TO_PRIV[action]
     privilege.project = prj
     privilege.user = user
     session.add(privilege)
예제 #5
0
 def __init__(self, session: Session, taxon_ids: ClassifIDListT):
     tf = WoRMS.__table__.alias('tf')
     # bind = None  # Uncomment for portable SQL, no 'ilike'
     bind = session.get_bind()
     select_list = [tf.c.aphia_id, tf.c.scientificname]
     select_list.extend([
         text("t%d.aphia_id, t%d.scientificname" %
              (level, level))  # type:ignore
         for level in range(1, TaxonBOSetFromWoRMS.MAX_TAXONOMY_LEVELS)
     ])
     qry = select(select_list, bind=bind)
     # Inject a query on names and hierarchy
     # Produced SQL looks like:
     #       left join worms t1 on tf.parent_name_usage_id=t1.aphia_id
     #       left join worms t2 on t1.parent_name_usage_id=t2.aphia_id
     # ...
     #       left join worms t14 on t13.parent_name_usage_id=t14.aphia_id
     lev_alias = WoRMS.__table__.alias('t1')
     # Chain outer joins on Taxonomy, for parents
     # hook the first OJ to main select
     chained_joins = tf.join(
         lev_alias,
         lev_alias.c.aphia_id == tf.c.parent_name_usage_id,
         isouter=True)
     prev_alias = lev_alias
     for level in range(2, self.MAX_TAXONOMY_LEVELS):
         lev_alias = WoRMS.__table__.alias('t%d' % level)
         # hook each following OJ to previous one
         chained_joins = chained_joins.join(
             lev_alias,
             lev_alias.c.aphia_id == prev_alias.c.parent_name_usage_id,
             isouter=True)
         # Collect expressions
         prev_alias = lev_alias
     qry = qry.select_from(chained_joins)
     qry = qry.where(tf.c.aphia_id == any_(taxon_ids))
     logger.info("Taxo query: %s with IDs %s", qry, taxon_ids)
     res: Result = session.execute(qry)
     self.taxa = []
     for a_rec in res.fetchall():
         lst_rec = list(a_rec)
         lineage_id = [an_id for an_id in lst_rec[0::2] if an_id]
         lineage = [name for name in lst_rec[1::2] if name]
         biota_pos = lineage.index('Biota') + 1
         lineage = lineage[:biota_pos]
         lineage_id = lineage_id[:biota_pos]
         self.taxa.append(
             TaxonBO('P', lineage[0], 0, 0, lineage,
                     lineage_id))  # type:ignore
     self.get_children(session, self.taxa)
예제 #6
0
 def __init__(self, session: Session, taxon_ids: ClassifIDListT):
     tf = WoRMS.__table__.alias('tf')
     # bind = None  # For portable SQL, no 'ilike'
     bind = session.get_bind()
     select_list = [tf.c.aphia_id, tf.c.scientificname]
     select_list.extend([
         text("t%d.scientificname" % level)  # type:ignore
         for level in range(1, TaxonomyBO.MAX_TAXONOMY_LEVELS)
     ])
     qry = select(select_list, bind=bind)
     # Inject a query on names and hierarchy
     # Produced SQL looks like:
     #       left join worms t1 on tf.parent_name_usage_id=t1.aphia_id
     #       left join worms t2 on t1.parent_name_usage_id=t2.aphia_id
     # ...
     #       left join worms t14 on t13.parent_name_usage_id=t14.aphia_id
     lev_alias = WoRMS.__table__.alias('t1')
     # Chain outer joins on Taxonomy, for parents
     # hook the first OJ to main select
     chained_joins = tf.join(
         lev_alias,
         lev_alias.c.aphia_id == tf.c.parent_name_usage_id,
         isouter=True)
     prev_alias = lev_alias
     for level in range(2, self.MAX_TAXONOMY_LEVELS):
         lev_alias = WoRMS.__table__.alias('t%d' % level)
         # hook each following OJ to previous one
         chained_joins = chained_joins.join(
             lev_alias,
             lev_alias.c.aphia_id == prev_alias.c.parent_name_usage_id,
             isouter=True)
         # Collect expressions
         prev_alias = lev_alias
     qry = qry.select_from(chained_joins)
     qry = qry.where(tf.c.aphia_id == any_(taxon_ids))
     logger.info("Taxo query: %s with IDs %s", qry, taxon_ids)
     res: ResultProxy = session.execute(qry)
     self.taxa = []
     for a_rec in res.fetchall():
         lst_rec = list(a_rec)
         an_id, display_name = lst_rec.pop(0), lst_rec.pop(0)
         lineage = [name for name in lst_rec if name]
         # In WoRMS, the root is signaled by having itself as parent
         while lineage and lineage[-1] == lineage[-2]:
             lineage.pop(-1)
         self.taxa.append(
             TaxonBO(an_id, display_name, display_name, 0, 0,
                     lineage))  # type:ignore
예제 #7
0
 def resolve_taxa(session: Session, taxo_found, taxon_lower_list):
     """
         Match taxa in taxon_lower_list and return the matched ones in taxo_found.
     """
     res: ResultProxy = session.execute(
         """SELECT t.id, lower(t.name) AS name, lower(t.display_name) AS display_name, 
                   lower(t.name)||'<'||lower(p.name) AS computedchevronname 
              FROM taxonomy t
             LEFT JOIN taxonomy p on t.parent_id = p.id
             WHERE lower(t.name) = ANY(:nms) OR lower(t.display_name) = ANY(:dms) 
                 OR lower(t.name)||'<'||lower(p.name) = ANY(:chv) """, {
             "nms": taxon_lower_list,
             "dms": taxon_lower_list,
             "chv": taxon_lower_list
         })
     for rec_taxon in res:
         for found_k, found_v in taxo_found.items():
             if ((found_k == rec_taxon['name'])
                     or (found_k == rec_taxon['display_name'])
                     or (found_k == rec_taxon['computedchevronname'])
                     or (('alterdisplayname' in found_v) and
                         (found_v['alterdisplayname']
                          == rec_taxon['display_name']))):
                 taxo_found[found_k]['nbr'] += 1
                 taxo_found[found_k]['id'] = rec_taxon['id']
예제 #8
0
 def find_ids(session: Session, classif_id_seen: List):
     """
         Return input IDs for the existing ones.
     """
     sql = text("SELECT id " "  FROM taxonomy " " WHERE id = ANY (:een)")
     res: Result = session.execute(sql, {"een": list(classif_id_seen)})
     return {int(r['id']) for r in res}
예제 #9
0
 def recent_projects(self, session: Session):
     """
         Return display information for last used projects.
     """
     mru = self.prefs.get(self.RECENT_PROJECTS_KEY, [])
     qry: Query = session.query(Project.projid, Project.title)
     qry = qry.filter(Project.projid.in_(mru))
     qry = qry.order_by(Project.title)
     return [{"projid": projid, "title": title} for projid, title in qry.all()]
예제 #10
0
 def keep_phylo(session: Session, classif_id_seen: ClassifIDListT):
     """
         Return input IDs, for the existing ones with 'P' type.
     """
     sql = text("SELECT id "
                "  FROM taxonomy "
                " WHERE id = ANY (:een) AND taxotype = 'P'")
     res: Result = session.execute(sql, {"een": list(classif_id_seen)})
     return {an_id for an_id, in res}
예제 #11
0
 def get_cardinalities(self, session: Session):
     # Enrich TaxonBOs with number of objects. Due to ecotaxa/ecotaxa_dev#648, pick data from projects stats.
     bos_per_id = {a_bo.id: a_bo for a_bo in self.taxa}
     qry: Query = session.query(ProjectTaxoStat.id,
                                func.sum(ProjectTaxoStat.nbr_v))
     qry = qry.filter(ProjectTaxoStat.id == any_(list(bos_per_id.keys())))
     qry = qry.group_by(ProjectTaxoStat.id)
     for an_id, a_sum in qry.all():
         bos_per_id[an_id].nb_objects = a_sum
예제 #12
0
 def get_children(self, session: Session, taxa_list: List[TaxonBO]):
     # Enrich TaxonBOs with children
     bos_per_id = {a_bo.id: a_bo for a_bo in taxa_list}
     tch = Taxonomy.__table__.alias('tch')
     qry: Query = session.query(Taxonomy.id, tch.c.id)
     qry = qry.join(tch, tch.c.parent_id == Taxonomy.id)
     qry = qry.filter(Taxonomy.id == any_(list(bos_per_id.keys())))
     for an_id, a_child_id in qry.all():
         bos_per_id[an_id].children.append(a_child_id)
예제 #13
0
 def user_has_role(session: Session, user_id: int, role: str) -> User:
     """
         Check user role. Should be temporary until a proper action is defined, e.g. refresh taxo tree.
     """
     # Load ORM entity
     user: User = session.query(User).get(user_id)
     # Check
     assert user.has_role(role), NOT_AUTHORIZED
     return user
예제 #14
0
 def __init__(self, session: Session, taxon_ids: ClassifIDListT):
     tf = Taxonomy.__table__.alias('tf')
     # bind = None  # For portable SQL, no 'ilike'
     bind = session.get_bind()
     select_list = [
         tf.c.taxotype,
         tf.c.nbrobj,
         tf.c.nbrobjcum,
         tf.c.display_name,
         tf.c.id,
         tf.c.name,
     ]
     select_list.extend([
         text("t%d.id, t%d.name" % (level, level))  # type:ignore
         for level in range(1, TaxonomyBO.MAX_TAXONOMY_LEVELS)
     ])
     qry = select(select_list, bind=bind)
     # Inject the recursive query, for getting parents
     _dumm, qry = TaxonomyBO._add_recursive_query(qry, tf, do_concat=False)
     qry = qry.where(tf.c.id == any_(taxon_ids))
     # Add another join for getting children
     logger.info("Taxo query: %s with IDs %s", qry, taxon_ids)
     res: Result = session.execute(qry)
     self.taxa: List[TaxonBO] = []
     for a_rec in res.fetchall():
         lst_rec = list(a_rec)
         cat_type, nbobj1, nbobj2, display_name = lst_rec.pop(
             0), lst_rec.pop(0), lst_rec.pop(0), lst_rec.pop(0)
         lineage_id = [an_id for an_id in lst_rec[0::2] if an_id]
         lineage = [name for name in lst_rec[1::2] if name]
         # assert lineage_id[-1] in (1, 84960, 84959), "Unexpected root %s" % str(lineage_id[-1])
         self.taxa.append(
             TaxonBO(
                 cat_type,
                 display_name,
                 nbobj1,
                 nbobj2,  # type:ignore
                 lineage,
                 lineage_id  # type:ignore
             ))
     self.get_children(session)
     self.get_cardinalities(session)
예제 #15
0
 def user_wants_create_project(session: Session, user_id: int) \
         -> User:
     """
         Check rights for the user to do this specific action.
     """
     # Load ORM entity
     user: User = session.query(User).get(user_id)
     assert user is not None, NOT_AUTHORIZED
     # Check
     assert Action.CREATE_PROJECT in RightsBO.allowed_actions(
         user), NOT_AUTHORIZED
     return user
예제 #16
0
 def user_wants(session: Session, user_id: int, action: Action, prj_id: int) \
         -> Tuple[User, Project]:
     """
         Check rights for the user to do this specific action onto this project.
     """
     # Load ORM entities
     user: User = session.query(User).get(user_id)
     project = session.query(Project).get(prj_id)
     assert project is not None, NOT_FOUND
     # Check
     if user.has_role(Role.APP_ADMINISTRATOR):
         # King of the world
         pass
     else:
         a_priv: ProjectPrivilege
         # Collect privileges for user on project
         # noinspection PyTypeChecker
         rights_on_proj = {
             a_priv.privilege
             for a_priv in user.privs_on_projects if a_priv.projid == prj_id
         }
         if action == Action.ADMINISTRATE:
             assert ProjectPrivilegeBO.MANAGE in rights_on_proj, NOT_AUTHORIZED
         elif action == Action.ANNOTATE:
             # TODO: Bah, not nice
             assert ProjectPrivilegeBO.ANNOTATE in rights_on_proj \
                    or ProjectPrivilegeBO.MANAGE in rights_on_proj, NOT_AUTHORIZED
         elif action == Action.READ:
             # TODO: Bah, not nice either
             assert project.visible \
                    or ProjectPrivilegeBO.VIEW in rights_on_proj \
                    or ProjectPrivilegeBO.ANNOTATE in rights_on_proj \
                    or ProjectPrivilegeBO.MANAGE in rights_on_proj, NOT_AUTHORIZED
         else:
             raise Exception("Not implemented")
     # Keep the last accessed projects
     if Preferences(user).add_recent_project(prj_id):
         session.commit()
     return user, project
예제 #17
0
 def anonymous_wants(session: Session, action: Action, prj_id: int) \
         -> Project:
     """
         Check rights for an anonymous user to do this action.
     """
     # Load ORM entities
     project: Optional[Project] = session.query(Project).get(prj_id)
     # Check
     if project and action == Action.READ:
         assert project.visible, NOT_AUTHORIZED
     else:
         assert False, NOT_AUTHORIZED
     return project
예제 #18
0
 def query(cls, session: Session, restrict_to: ClassifIDListT,
           priority_set: ClassifIDListT, display_name_filter: str,
           name_filters: List[str]):
     """
     :param session:
     :param restrict_to: If not None, limit the query to given IDs.
     :param priority_set: Regardless of MAX_MATCHES, these IDs must appear in the result if they match.
     :param display_name_filter:
     :param name_filters:
     :return:
     """
     tf = Taxonomy.__table__.alias('tf')
     # bind = None  # For portable SQL, no 'ilike'
     bind = session.get_bind()
     # noinspection PyTypeChecker
     priority = case([(tf.c.id == any_(priority_set), text('0'))],
                     else_=text('1')).label('prio')
     qry = select([tf.c.taxotype, tf.c.id, tf.c.display_name, priority],
                  bind=bind)
     if len(name_filters) > 0:
         # Add to the query enough to get the full hierarchy for filtering
         concat_all, qry = cls._add_recursive_query(qry, tf, do_concat=True)
         # Below is quite expensive
         taxo_lineage = func.concat(*concat_all)
         name_filter = "%<" + "".join(
             name_filters)  # i.e. anywhere consecutively in the lineage
         qry = qry.where(taxo_lineage.ilike(name_filter))
     if restrict_to is not None:
         qry = qry.where(tf.c.id == any_(restrict_to))
     # We have index IS_TaxonomyDispNameLow so this lower() is for free
     qry = qry.where(
         func.lower(tf.c.display_name).like(display_name_filter))
     qry = qry.order_by(priority, func.lower(tf.c.display_name))
     qry = qry.limit(cls.MAX_MATCHES)
     logger.info("Taxo query: %s with params %s and %s ", qry,
                 display_name_filter, name_filters)
     res: Result = session.execute(qry)
     return res.fetchall()
예제 #19
0
 def names_with_parent_for(session: Session,
                           id_coll: ClassifIDCollT) -> ClassifSetInfoT:
     """
         Get taxa names from id list.
     """
     ret = {}
     res: ResultProxy = session.execute(
         """SELECT t.id, t.name, p.name AS parent_name
              FROM taxonomy t
             LEFT JOIN taxonomy p ON t.parent_id = p.id
             WHERE t.id = ANY(:ids) """, {"ids": list(id_coll)})
     for rec_taxon in res:
         ret[rec_taxon['id']] = (rec_taxon['name'],
                                 rec_taxon['parent_name'])
     return ret
예제 #20
0
 def children_of(session: Session, id_list: List[int]) -> Set[int]:
     """
         Get id and children taxa ids for given id.
     """
     res: ResultProxy = session.execute(
         """WITH RECURSIVE rq(id) 
             AS (SELECT id 
                   FROM taxonomy 
                  WHERE id = ANY(:ids)
                  UNION
                 SELECT t.id 
                   FROM rq 
                   JOIN taxonomy t ON rq.id = t.parent_id )
            SELECT id FROM rq """, {"ids": id_list})
     return {int(r['id']) for r in res}
예제 #21
0
 def __init__(self, session: Session, object_ids: Any,
              obj_mapping: TableMapping):
     needed_cols = obj_mapping.real_cols_to_tsv.keys()
     # noinspection PyPep8Naming
     ReducedObjectFields = minimal_model_of(MetaData(), ObjectFields,
                                            set(needed_cols))
     qry: Query = session.query(ObjectHeader, ReducedObjectFields)
     qry = qry.filter(ObjectHeader.objid.in_(object_ids))
     # noinspection PyUnresolvedReferences
     qry = qry.join(
         ReducedObjectFields,
         ObjectHeader.objid == ReducedObjectFields.objfid)  # type:ignore
     qry = qry.options(joinedload(ObjectHeader.all_images))
     self.all = [
         ObjectBO(session, 0, an_obj, its_fields)
         for an_obj, its_fields in qry.all()
     ]
예제 #22
0
    def strict_match(
            session: Session,
            used_taxo_ids: ClassifIDListT) -> List[Tuple[Taxonomy, WoRMS]]:
        """
            Candidate match until a better is found.
                We match Phylo types taxa on one side.
                    using name <-> scientificname, case insensitive
                And a _single_ accepted taxon on the other.
        """
        subqry = TaxonomyChangeService.strict_match_subquery(
            session, used_taxo_ids, phylo_or_morpho="P")

        qry: Query = session.query(Taxonomy, WoRMS)
        qry = qry.join(subqry, subqry.c.id == Taxonomy.id)
        qry = qry.join(WoRMS, subqry.c.aphia_id == WoRMS.aphia_id)
        logger.info("matching qry:%s", str(qry))
        res = qry.all()
        return res
예제 #23
0
 def _get_result(self, session: Session):
     res = session.execute(self.qry)
     ret = [tuple(a_row) for a_row in res]
     return set(ret)