From 4a27ca916ea33c650d8a25d133e19cb63d66ba53 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Thu, 7 Oct 2021 12:37:43 -0400 Subject: [PATCH 01/55] Initial setup for declarative mapping --- lib/tool_shed/webapp/model/mapping.py | 45 ++++++++++++++------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 54e664b53a76..0cddd4f5037e 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -4,8 +4,8 @@ """ import logging -from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, MetaData, not_, String, Table, TEXT, true, UniqueConstraint -from sqlalchemy.orm import backref, mapper, relation +from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, not_, String, Table, TEXT, true, UniqueConstraint +from sqlalchemy.orm import backref, registry, relation import tool_shed.webapp.model import tool_shed.webapp.util.shed_statistics as shed_statistics @@ -22,8 +22,9 @@ from tool_shed.webapp.security import CommunityRBACAgent log = logging.getLogger(__name__) -metadata = MetaData() +mapper_registry = registry() +metadata = mapper_registry.metadata APIKeys.table = Table("api_keys", metadata, Column("id", Integer, primary_key=True), @@ -198,20 +199,20 @@ UniqueConstraint("name")) # With the tables defined we can define the mappers and setup the relationships between the model objects. -mapper(User, User.table, +mapper_registry.map_imperatively(User, User.table, properties=dict(active_repositories=relation(Repository, primaryjoin=((Repository.table.c.user_id == User.table.c.id) & (not_(Repository.table.c.deleted))), order_by=(Repository.table.c.name)), galaxy_sessions=relation(GalaxySession, order_by=desc(GalaxySession.table.c.update_time)), api_keys=relation(APIKeys, backref="user", order_by=desc(APIKeys.table.c.create_time)))) -mapper(PasswordResetToken, PasswordResetToken.table, +mapper_registry.map_imperatively(PasswordResetToken, PasswordResetToken.table, properties=dict(user=relation(User, backref="reset_tokens"))) -mapper(APIKeys, APIKeys.table, properties={}) +mapper_registry.map_imperatively(APIKeys, APIKeys.table, properties={}) -mapper(Group, Group.table, +mapper_registry.map_imperatively(Group, Group.table, properties=dict(users=relation(UserGroupAssociation))) -mapper(Role, Role.table, +mapper_registry.map_imperatively(Role, Role.table, properties=dict( repositories=relation(RepositoryRoleAssociation, primaryjoin=((Role.table.c.id == RepositoryRoleAssociation.table.c.role_id) & (RepositoryRoleAssociation.table.c.repository_id == Repository.table.c.id))), @@ -220,16 +221,16 @@ groups=relation(GroupRoleAssociation, primaryjoin=((Role.table.c.id == GroupRoleAssociation.table.c.role_id) & (GroupRoleAssociation.table.c.group_id == Group.table.c.id))))) -mapper(RepositoryRoleAssociation, RepositoryRoleAssociation.table, +mapper_registry.map_imperatively(RepositoryRoleAssociation, RepositoryRoleAssociation.table, properties=dict( repository=relation(Repository), role=relation(Role))) -mapper(UserGroupAssociation, UserGroupAssociation.table, +mapper_registry.map_imperatively(UserGroupAssociation, UserGroupAssociation.table, properties=dict(user=relation(User, backref="groups"), group=relation(Group, backref="members"))) -mapper(UserRoleAssociation, UserRoleAssociation.table, +mapper_registry.map_imperatively(UserRoleAssociation, UserRoleAssociation.table, properties=dict( user=relation(User, backref="roles"), non_private_roles=relation(User, @@ -237,24 +238,24 @@ primaryjoin=((User.table.c.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.table.c.id) & not_(Role.table.c.name == User.table.c.email))), role=relation(Role))) -mapper(GroupRoleAssociation, GroupRoleAssociation.table, +mapper_registry.map_imperatively(GroupRoleAssociation, GroupRoleAssociation.table, properties=dict( group=relation(Group, backref="roles"), role=relation(Role))) -mapper(GalaxySession, GalaxySession.table, +mapper_registry.map_imperatively(GalaxySession, GalaxySession.table, properties=dict(user=relation(User))) -mapper(Tag, Tag.table, +mapper_registry.map_imperatively(Tag, Tag.table, properties=dict(children=relation(Tag, backref=backref('parent', remote_side=[Tag.table.c.id])))) -mapper(Category, Category.table, +mapper_registry.map_imperatively(Category, Category.table, properties=dict(repositories=relation(RepositoryCategoryAssociation, secondary=Repository.table, primaryjoin=(Category.table.c.id == RepositoryCategoryAssociation.table.c.category_id), secondaryjoin=(RepositoryCategoryAssociation.table.c.repository_id == Repository.table.c.id)))) -mapper(Repository, Repository.table, +mapper_registry.map_imperatively(Repository, Repository.table, properties=dict( categories=relation(RepositoryCategoryAssociation), ratings=relation(RepositoryRatingAssociation, order_by=desc(RepositoryRatingAssociation.table.c.update_time), backref="repositories"), @@ -272,13 +273,13 @@ primaryjoin=(Repository.table.c.id == RepositoryReview.table.c.repository_id), secondaryjoin=(RepositoryReview.table.c.user_id == User.table.c.id)))) -mapper(RepositoryMetadata, RepositoryMetadata.table, +mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict(repository=relation(Repository), reviews=relation(RepositoryReview, foreign_keys=[RepositoryMetadata.table.c.repository_id, RepositoryMetadata.table.c.changeset_revision], primaryjoin=((RepositoryMetadata.table.c.repository_id == RepositoryReview.table.c.repository_id) & (RepositoryMetadata.table.c.changeset_revision == RepositoryReview.table.c.changeset_revision))))) -mapper(RepositoryReview, RepositoryReview.table, +mapper_registry.map_imperatively(RepositoryReview, RepositoryReview.table, properties=dict(repository=relation(Repository, primaryjoin=(RepositoryReview.table.c.repository_id == Repository.table.c.id)), # Take care when using the mapper below! It should be used only when a new review is being created for a repository change set revision. @@ -294,17 +295,17 @@ private_component_reviews=relation(ComponentReview, primaryjoin=((RepositoryReview.table.c.id == ComponentReview.table.c.repository_review_id) & (ComponentReview.table.c.deleted == false()) & (ComponentReview.table.c.private == true()))))) -mapper(ComponentReview, ComponentReview.table, +mapper_registry.map_imperatively(ComponentReview, ComponentReview.table, properties=dict(repository_review=relation(RepositoryReview), component=relation(Component, primaryjoin=(ComponentReview.table.c.component_id == Component.table.c.id)))) -mapper(Component, Component.table) +mapper_registry.map_imperatively(Component, Component.table) -mapper(RepositoryRatingAssociation, RepositoryRatingAssociation.table, +mapper_registry.map_imperatively(RepositoryRatingAssociation, RepositoryRatingAssociation.table, properties=dict(repository=relation(Repository), user=relation(User))) -mapper(RepositoryCategoryAssociation, RepositoryCategoryAssociation.table, +mapper_registry.map_imperatively(RepositoryCategoryAssociation, RepositoryCategoryAssociation.table, properties=dict( category=relation(Category), repository=relation(Repository))) From 7fd5099e13be1718eefc0154436913df2a5fe10f Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Thu, 7 Oct 2021 13:00:27 -0400 Subject: [PATCH 02/55] Setup testing infrastructure --- test/unit/shed_unit/model/test_mapping.py | 187 ++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 test/unit/shed_unit/model/test_mapping.py diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py new file mode 100644 index 000000000000..ccce87e04299 --- /dev/null +++ b/test/unit/shed_unit/model/test_mapping.py @@ -0,0 +1,187 @@ +from contextlib import contextmanager +from uuid import uuid4 + +import pytest +from sqlalchemy import ( + delete, + select, + UniqueConstraint, +) + +import tool_shed.webapp.model.mapping as mapping + + +class BaseTest: + @pytest.fixture + def cls_(self, model): + """ + Return class under test. + Assumptions: if the class under test is Foo, then the class grouping + the tests should be a subclass of BaseTest, named TestFoo. + """ + prefix = len('Test') + class_name = self.__class__.__name__[prefix:] + return getattr(model, class_name) + + +class TestUser(BaseTest): + pass # TODO + + +class TestPasswordResetToken(BaseTest): + pass # TODO + + +class TestAPIKeys(BaseTest): + pass # TODO + + +class TestGroup(BaseTest): + pass # TODO + + +class TestRole(BaseTest): + pass # TODO + + +class TestRepositoryRoleAssociation(BaseTest): + pass # TODO + + +class TestUserGroupAssociation(BaseTest): + pass # TODO + + +class TestUserRoleAssociation(BaseTest): + pass # TODO + + +class TestGroupRoleAssociation(BaseTest): + pass # TODO + + +class TestGalaxySession(BaseTest): + pass # TODO + + +class TestTag(BaseTest): + pass # TODO + + +class TestCategory(BaseTest): + pass # TODO + + +class TestRepository(BaseTest): + pass # TODO + + +class TestRepositoryMetadata(BaseTest): + pass # TODO + + +class TestRepositoryReview(BaseTest): + pass # TODO + + +class TestComponentReview(BaseTest): + pass # TODO + + +class TestComponent(BaseTest): + pass # TODO + + +class TestRepositoryRatingAssociation(BaseTest): + pass # TODO + + +class TestRepositoryCategoryAssociation(BaseTest): + pass # TODO + + +# Misc. helper fixtures. + +@pytest.fixture(scope='module') +def model(): + db_uri = 'sqlite:///:memory:' + return mapping.init('/tmp', db_uri, create_tables=True) + + +@pytest.fixture +def session(model): + Session = model.session + yield Session() + Session.remove() # Ensures we get a new session for each test + + +# Test utilities + +def dbcleanup_wrapper(session, obj, where_clause=None): + with dbcleanup(session, obj, where_clause): + yield obj + + +@contextmanager +def dbcleanup(session, obj, where_clause=None): + """ + Use the session to store obj in database; delete from database on exit, bypassing the session. + + If obj does not have an id field, a SQLAlchemy WHERE clause should be provided to construct + a custom select statement. + """ + return_id = where_clause is None + + try: + obj_id = persist(session, obj, return_id) + yield obj_id + finally: + table = obj.table + if where_clause is None: + where_clause = _get_default_where_clause(type(obj), obj_id) + stmt = delete(table).where(where_clause) + session.execute(stmt) + + +def persist(session, obj, return_id=True): + """ + Use the session to store obj in database, then remove obj from session, + so that on a subsequent load from the database we get a clean instance. + """ + session.add(obj) + session.flush() + obj_id = obj.id if return_id else None # save this before obj is expunged + session.expunge(obj) + return obj_id + + +def get_stored_obj(session, cls, obj_id=None, where_clause=None, unique=False): + # Either obj_id or where_clause must be provided, but not both + assert bool(obj_id) ^ (where_clause is not None) + if where_clause is None: + where_clause = _get_default_where_clause(cls, obj_id) + stmt = select(cls).where(where_clause) + result = session.execute(stmt) + # unique() is required if result contains joint eager loads against collections + # https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/2253 + if unique: + result = result.unique() + return result.scalar_one() + + +def _get_default_where_clause(cls, obj_id): + where_clause = cls.table.c.id == obj_id + return where_clause + + +def has_unique_constraint(table, fields): + for constraint in table.constraints: + if isinstance(constraint, UniqueConstraint): + col_names = {c.name for c in constraint.columns} + if set(fields) == col_names: + return True + + +def get_unique_value(): + """Generate unique values to accommodate unique constraints.""" + return uuid4().hex From 55daac70281abbd56a2b13fdc8922d5034843b82 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Thu, 7 Oct 2021 16:40:28 -0400 Subject: [PATCH 03/55] Add table and column tests for all models --- test/unit/shed_unit/model/test_mapping.py | 572 +++++++++++++++++++++- 1 file changed, 552 insertions(+), 20 deletions(-) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index ccce87e04299..72ad78f34055 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -1,4 +1,5 @@ from contextlib import contextmanager +from datetime import datetime, timedelta from uuid import uuid4 import pytest @@ -24,80 +25,562 @@ def cls_(self, model): return getattr(model, class_name) +class TestAPIKeys(BaseTest): + + def test_table(self, cls_): + assert cls_.table.name == 'api_keys' + + def test_columns(self, session, cls_, user): + create_time, user_id, key = datetime.now(), user.id, get_unique_value() + obj = cls_(user_id=user_id, key=key, create_time=create_time) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.user_id == user_id + assert stored_obj.key == key + + class TestUser(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'galaxy_user' + + def test_columns(self, session, cls_): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + email = get_unique_value() + username = get_unique_value() + password = 'c' + external = True + new_repo_alert = True + deleted = True + purged = True + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.email = email + obj.username = username + obj.password = password + obj.external = external + obj.new_repo_alert = new_repo_alert + obj.deleted = deleted + obj.purged = purged + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.email == email + assert stored_obj.username == username + assert stored_obj.password == password + assert stored_obj.external == external + assert stored_obj.new_repo_alert == new_repo_alert + assert stored_obj.deleted == deleted + assert stored_obj.purged == purged class TestPasswordResetToken(BaseTest): - pass # TODO + def test_table(self, cls_): + assert cls_.table.name == 'password_reset_token' -class TestAPIKeys(BaseTest): - pass # TODO + def test_columns_and_relationships(self, session, cls_, user): + token = get_unique_value() + expiration_time = datetime.now() + obj = cls_(user, token) + obj.expiration_time = expiration_time + + where_clause = cls_.token == token + + with dbcleanup(session, obj, where_clause): + stored_obj = get_stored_obj(session, cls_, where_clause=where_clause) + # test columns + assert stored_obj.token == token + assert stored_obj.expiration_time == expiration_time + assert stored_obj.user_id == user.id + # test relationships + assert stored_obj.user.id == user.id class TestGroup(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'galaxy_group' + + def test_columns(self, session, cls_): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + name = get_unique_value() + deleted = True + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.name = name + obj.deleted = deleted + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.name == name + assert stored_obj.deleted == deleted class TestRole(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'role' + + def test_columns(self, session, cls_): + name, description, type_, deleted = get_unique_value(), 'b', cls_.types.SYSTEM, True + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + obj = cls_(name, description, type_, deleted) + obj.create_time = create_time + obj.update_time = update_time + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.name == name + assert stored_obj.description == description + assert stored_obj.type == type_ + assert stored_obj.deleted == deleted class TestRepositoryRoleAssociation(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'repository_role_association' + + def test_columns(self, session, cls_, repository, role): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + obj = cls_(repository, role) + obj.create_time = create_time + obj.update_time = update_time + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.repository_id == repository.id + assert stored_obj.role_id == role.id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time class TestUserGroupAssociation(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'user_group_association' + + def test_columns(self, session, cls_, user, group): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + obj = cls_(user, group) + obj.create_time = create_time + obj.update_time = update_time + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.user_id == user.id + assert stored_obj.group_id == group.id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time class TestUserRoleAssociation(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'user_role_association' + + def test_columns(self, session, cls_, user, role): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + obj = cls_(user, role) + obj.create_time = create_time + obj.update_time = update_time + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.user_id == user.id + assert stored_obj.role_id == role.id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time class TestGroupRoleAssociation(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'group_role_association' + + def test_columns(self, session, cls_, group, role): + obj = cls_(group, role) + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + obj.create_time = create_time + obj.update_time = update_time + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.group_id == group.id + assert stored_obj.role_id == role.id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time class TestGalaxySession(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'galaxy_session' + + def test_columns(self, session, cls_, user, galaxy_session): + + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + remote_host = 'a' + remote_addr = 'b' + referer = 'c' + session_key = get_unique_value() + is_valid = True + last_action = update_time + timedelta(hours=1) + + obj = cls_(user=user, prev_session_id=galaxy_session.id) + + obj.create_time = create_time + obj.update_time = update_time + obj.remote_host = remote_host + obj.remote_addr = remote_addr + obj.referer = referer + obj.session_key = session_key + obj.is_valid = is_valid + obj.last_action = last_action + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.user_id == user.id + assert stored_obj.remote_host == remote_host + assert stored_obj.remote_addr == remote_addr + assert stored_obj.referer == referer + assert stored_obj.session_key == session_key + assert stored_obj.is_valid == is_valid + assert stored_obj.prev_session_id == galaxy_session.id + assert stored_obj.last_action == last_action class TestTag(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'tag' + assert has_unique_constraint(cls_.table, ('name',)) + + def test_columns(self, session, cls_): + parent_tag = cls_() + type_, name = 1, get_unique_value() + obj = cls_(type=type_, name=name) + obj.parent = parent_tag + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.type == type_ + assert stored_obj.parent_id == parent_tag.id + assert stored_obj.name == name class TestCategory(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'category' + + def test_columns(self, session, cls_): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + name, description, deleted = get_unique_value(), 'b', True + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.name = name + obj.description = description + obj.deleted = deleted + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.name == name + assert stored_obj.description == description + assert stored_obj.deleted == deleted class TestRepository(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'repository' + + def test_columns(self, session, cls_, user): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + name = 'a' + type = 'b' + remote_repository_url = 'c' + homepage_url = 'd' + description = 'e' + long_description = 'f' + private = True + deleted = True + email_alerts = False + times_downloaded = 1 + deprecated = True + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.name = name + obj.type = type + obj.remote_repository_url = remote_repository_url + obj.homepage_url = homepage_url + obj.description = description + obj.long_description = long_description + obj.user = user + obj.private = private + obj.deleted = deleted + obj.email_alerts = email_alerts + obj.times_downloaded = times_downloaded + obj.deprecated = deprecated + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.name == name + assert stored_obj.type == type + assert stored_obj.remote_repository_url == remote_repository_url + assert stored_obj.homepage_url == homepage_url + assert stored_obj.description == description + assert stored_obj.long_description == long_description + assert stored_obj.user_id == user.id + assert stored_obj.private == private + assert stored_obj.deleted == deleted + assert stored_obj.email_alerts == email_alerts + assert stored_obj.times_downloaded == times_downloaded + assert stored_obj.deprecated == deprecated + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.name == name + assert stored_obj.description == description + assert stored_obj.deleted == deleted class TestRepositoryMetadata(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'repository_metadata' + + def test_columns(self, session, cls_, repository): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + changeset_revision = 'a' + numeric_revision = 1 + metadata = 'b' + tool_versions = 'c' + malicious = True + downloadable = False + missing_test_components = True + has_repository_dependencies = True + includes_datatypes = True + includes_tools = True + includes_tool_dependencies = True + includes_workflows = True + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.repository_id = repository.id + obj.changeset_revision = changeset_revision + obj.numeric_revision = numeric_revision + obj.metadata = metadata + obj.tool_versions = tool_versions + obj.malicious = malicious + obj.downloadable = downloadable + obj.missing_test_components = missing_test_components + obj.has_repository_dependencies = has_repository_dependencies + obj.includes_datatypes = includes_datatypes + obj.includes_tools = includes_tools + obj.includes_tool_dependencies = includes_tool_dependencies + obj.includes_workflows = includes_workflows + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.repository_id == repository.id + assert stored_obj.changeset_revision == changeset_revision + assert stored_obj.numeric_revision == numeric_revision + assert stored_obj.metadata == metadata + assert stored_obj.tool_versions == tool_versions + assert stored_obj.malicious == malicious + assert stored_obj.downloadable == downloadable + assert stored_obj.missing_test_components == missing_test_components + assert stored_obj.has_repository_dependencies == has_repository_dependencies + assert stored_obj.includes_datatypes == includes_datatypes + assert stored_obj.includes_tools == includes_tools + assert stored_obj.includes_tool_dependencies == includes_tool_dependencies + assert stored_obj.includes_workflows == includes_workflows class TestRepositoryReview(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'repository_review' + + def test_columns(self, session, cls_, repository, user): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + changeset_revision = 'a' + approved = 'b' + rating = 1 + deleted = True + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.repository = repository + obj.changeset_revision = changeset_revision + obj.user = user + obj.approved = approved + obj.rating = rating + obj.deleted = deleted + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.repository_id == repository.id + assert stored_obj.changeset_revision == changeset_revision + assert stored_obj.user_id == user.id + assert stored_obj.approved == approved + assert stored_obj.rating == rating + assert stored_obj.deleted == deleted class TestComponentReview(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'component_review' + + def test_columns(self, session, cls_, repository_review, component): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + comment = 'a' + private = True + approved = 'b' + rating = 1 + deleted = True + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.repository_review = repository_review + obj.component = component + obj.comment = comment + obj.private = private + obj.approved = approved + obj.rating = rating + obj.deleted = deleted + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.repository_review_id == repository_review.id + assert stored_obj.component_id == component.id + assert stored_obj.comment == comment + assert stored_obj.private == private + assert stored_obj.approved == approved + assert stored_obj.rating == rating + assert stored_obj.deleted == deleted class TestComponent(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'component' + + def test_columns(self, session, cls_): + name, description = 'a', 'b' + obj = cls_(name=name, description=description) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.name == name + assert stored_obj.description == description class TestRepositoryRatingAssociation(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'repository_rating_association' + + def test_columns(self, session, cls_, repository, user): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + rating = 1 + comment = 'a' + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.repository = repository + obj.user = user + obj.rating = rating + obj.comment = comment + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.repository_id == repository.id + assert stored_obj.user_id == user.id + assert stored_obj.rating == rating + assert stored_obj.comment == comment class TestRepositoryCategoryAssociation(BaseTest): - pass # TODO + + def test_table(self, cls_): + assert cls_.table.name == 'repository_category_association' + + def test_columns(self, session, cls_, repository, category): + obj = cls_(repository=repository, category=category) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.repository_id == repository.id + assert stored_obj.category_id == category.id # Misc. helper fixtures. @@ -115,6 +598,55 @@ def session(model): Session.remove() # Ensures we get a new session for each test +@pytest.fixture +def category(model, session): + instance = model.Category(name=get_unique_value()) + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def component(model, session): + instance = model.Component() + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def galaxy_session(model, session): + instance = model.GalaxySession(session_key=get_unique_value()) + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def group(model, session): + instance = model.Group(name=get_unique_value()) + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def repository(model, session): + instance = model.Repository() + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def repository_review(model, session, user): + instance = model.RepositoryReview() + instance.user = user + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def role(model, session): + instance = model.Role(name=get_unique_value()) + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def user(model, session): + instance = model.User(email=get_unique_value(), password='password') + yield from dbcleanup_wrapper(session, instance) + + # Test utilities def dbcleanup_wrapper(session, obj, where_clause=None): From 80bcc4c66ead07e00c4dec4c8c84f56f9e0d7fa7 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Thu, 7 Oct 2021 18:34:24 -0400 Subject: [PATCH 04/55] TMP adding rel tests --- test/unit/shed_unit/model/test_mapping.py | 297 +++++++++++++++++++++- 1 file changed, 296 insertions(+), 1 deletion(-) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 72ad78f34055..009c4d3ef77f 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -82,6 +82,76 @@ def test_columns(self, session, cls_): assert stored_obj.deleted == deleted assert stored_obj.purged == purged + def test_relationships( + self, + session, + cls_, + repository, + galaxy_session, + api_keys, + repository_review, + role, + group, + password_reset_token, + ): + obj = cls_() + obj.email = get_unique_value() + obj.password = 'a' + + + +# obj.addresses.append(user_address) +# obj.cloudauthz.append(cloud_authz) +# obj.custos_auth.append(custos_authnz_token) +# obj.default_permissions.append(default_user_permissions) +# obj.groups.append(user_group_association) +# obj.histories.append(history1) +# obj.histories.append(history2) +# obj.galaxy_sessions.append(galaxy_session) +# obj.quotas.append(user_quota_association) +# obj.social_auth.append(user_authnz_token) +# +# _private_role = role_factory(name=obj.email) +# private_user_role = user_role_association_factory(obj, _private_role) +# obj.roles.append(private_user_role) +# +# _non_private_role = role_factory(name='a') +# non_private_user_role = user_role_association_factory(obj, _non_private_role) +# obj.roles.append(non_private_user_role) +# +# swme = stored_workflow_menu_entry_factory() +# swme.stored_workflow = stored_workflow +# swme.user = obj +# +# user_preference.name = 'a' +# obj._preferences.set(user_preference) +# +# obj.api_keys.append(api_keys) +# obj.data_manager_histories.append(data_manager_history_association) +# obj.stored_workflows.append(stored_workflow) +# +# with dbcleanup(session, obj) as obj_id: +# stored_obj = get_stored_obj(session, cls_, obj_id) +# assert stored_obj.values.id == form_values.id +# assert stored_obj.addresses == [user_address] +# assert stored_obj.cloudauthz == [cloud_authz] +# assert stored_obj.custos_auth == [custos_authnz_token] +# assert stored_obj.default_permissions == [default_user_permissions] +# assert stored_obj.groups == [user_group_association] +# assert are_same_entity_collections(stored_obj.histories, [history1, history2]) +# assert stored_obj.active_histories == [history1] +# assert stored_obj.galaxy_sessions == [galaxy_session] +# assert stored_obj.quotas == [user_quota_association] +# assert stored_obj.social_auth == [user_authnz_token] +# assert stored_obj.stored_workflow_menu_entries == [swme] +# assert user_preference in stored_obj._preferences.values() +# assert stored_obj.api_keys == [api_keys] +# assert stored_obj.data_manager_histories == [data_manager_history_association] +# assert are_same_entity_collections(stored_obj.roles, [private_user_role, non_private_user_role]) +# assert stored_obj.non_private_roles == [non_private_user_role] +# assert stored_obj.stored_workflows == [stored_workflow] +# +# delete_from_database(session, [history1, history2, swme, private_user_role, non_private_user_role]) class TestPasswordResetToken(BaseTest): @@ -131,6 +201,23 @@ def test_columns(self, session, cls_): assert stored_obj.name == name assert stored_obj.deleted == deleted + def test_relationships( + self, + session, + cls_, + group_role_association, + user_group_association, + ): + obj = cls_(name=get_unique_value()) + obj.roles.append(group_role_association) + obj.users.append(user_group_association) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.roles == [group_role_association] + assert stored_obj.users == [user_group_association] + class TestRole(BaseTest): @@ -155,6 +242,26 @@ def test_columns(self, session, cls_): assert stored_obj.type == type_ assert stored_obj.deleted == deleted + def test_relationships( + self, + session, + cls_, + repository_role_association, + user_role_association, + group_role_association, + ): + name, description, type_ = get_unique_value(), 'b', cls_.types.SYSTEM + obj = cls_(name, description, type_) + obj.repositories.append(repository_role_association) + obj.users.append(user_role_association) + obj.groups.append(group_role_association) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repositories == [repository_role_association] + assert stored_obj.users == [user_role_association] + #assert stored_obj.groups == [group_role_association] # TODO broken + class TestRepositoryRoleAssociation(BaseTest): @@ -197,6 +304,13 @@ def test_columns(self, session, cls_, user, group): assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time + def test_relationships(self, session, cls_, user, group): + obj = cls_(user, group) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.user.id == user.id + assert stored_obj.group.id == group.id class TestUserRoleAssociation(BaseTest): @@ -218,6 +332,13 @@ def test_columns(self, session, cls_, user, role): assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time + def test_relationships(self, session, cls_, user, role): + obj = cls_(user, role) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.user.id == user.id + assert stored_obj.role.id == role.id class TestGroupRoleAssociation(BaseTest): @@ -239,6 +360,13 @@ def test_columns(self, session, cls_, group, role): assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time + def test_relationships(self, session, cls_, group, role): + obj = cls_(group, role) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.group.id == group.id + assert stored_obj.role.id == role.id class TestGalaxySession(BaseTest): @@ -281,6 +409,14 @@ def test_columns(self, session, cls_, user, galaxy_session): assert stored_obj.prev_session_id == galaxy_session.id assert stored_obj.last_action == last_action + def test_relationships(self, session, cls_, user): + obj = cls_(user=user) + obj.session_key = get_unique_value() + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.user.id == user.id + class TestTag(BaseTest): @@ -301,6 +437,26 @@ def test_columns(self, session, cls_): assert stored_obj.parent_id == parent_tag.id assert stored_obj.name == name + def test_relationships( + self, + session, + cls_, + ): + obj = cls_() + parent_tag = cls_() + child_tag = cls_() + obj.parent = parent_tag + obj.children.append(child_tag) + + def add_association(assoc_object, assoc_attribute): + assoc_object.tag = obj + getattr(obj, assoc_attribute).append(assoc_object) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.parent.id == parent_tag.id + assert stored_obj.children == [child_tag] + class TestCategory(BaseTest): @@ -328,6 +484,14 @@ def test_columns(self, session, cls_): assert stored_obj.description == description assert stored_obj.deleted == deleted + #def test_relationships(self, session, cls_, repository_category_association): + # obj = cls_() + # obj.name = get_unique_value() + # obj.repositories.append(repository_category_association) # TODO broken + + # with dbcleanup(session, obj) as obj_id: + # stored_obj = get_stored_obj(session, cls_, obj_id) + class TestRepository(BaseTest): @@ -388,6 +552,15 @@ def test_columns(self, session, cls_, user): assert stored_obj.description == description assert stored_obj.deleted == deleted +# def test_relationships(self, session, cls_, user): +# obj = cls_() +# +# with dbcleanup(session, obj) as obj_id: +# stored_obj = get_stored_obj(session, cls_, obj_id) +# assert stored_obj.user.id == user.id + + + class TestRepositoryMetadata(BaseTest): @@ -413,7 +586,7 @@ def test_columns(self, session, cls_, repository): obj = cls_() obj.create_time = create_time obj.update_time = update_time - obj.repository_id = repository.id + obj.repository = repository obj.changeset_revision = changeset_revision obj.numeric_revision = numeric_revision obj.metadata = metadata @@ -446,6 +619,18 @@ def test_columns(self, session, cls_, repository): assert stored_obj.includes_tool_dependencies == includes_tool_dependencies assert stored_obj.includes_workflows == includes_workflows + def test_relationships(self, session, cls_, repository, repository_review): + obj = cls_() + obj.repository = repository + obj.review.append(repository_review) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository.id == repository.id +# assert stored_obj.review == [repository_review] # TODO broken + + + class TestRepositoryReview(BaseTest): @@ -482,6 +667,27 @@ def test_columns(self, session, cls_, repository, user): assert stored_obj.rating == rating assert stored_obj.deleted == deleted + def test_relationships(self, session, cls_, repository, user, repository_metadata, component_review_factory): + component_review1 = component_review_factory() + component_review2 = component_review_factory() + obj = cls_() + obj.repository = repository + obj.user = user + #obj.repository_metadata.append(repository_metadata) # TODO broken + obj.component_reviews.append(component_review1) + obj.private_component_reviews.append(component_review2) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository.id == repository.id + assert stored_obj.user.id == user.id + # TODO broken + # assert stored_obj.repository_metadata == [repository_metadata] + #assert stored_obj.component_reviews == [component_review1] + #assert stored_obj.private_component_reviews == [component_review2] + + + class TestComponentReview(BaseTest): @@ -521,6 +727,18 @@ def test_columns(self, session, cls_, repository_review, component): assert stored_obj.rating == rating assert stored_obj.deleted == deleted + def test_relationships(self, session, cls_, repository_review, component): + obj = cls_() + obj.repository_review = repository_review + obj.component = component + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository_review.id == repository_review.id + assert stored_obj.component.id == component.id + + + class TestComponent(BaseTest): @@ -567,6 +785,15 @@ def test_columns(self, session, cls_, repository, user): assert stored_obj.rating == rating assert stored_obj.comment == comment +# def test_relationships(self, session, cls_, user): +# obj = cls_() +# +# with dbcleanup(session, obj) as obj_id: +# stored_obj = get_stored_obj(session, cls_, obj_id) +# assert stored_obj.user.id == user.id + + + class TestRepositoryCategoryAssociation(BaseTest): @@ -582,6 +809,15 @@ def test_columns(self, session, cls_, repository, category): assert stored_obj.repository_id == repository.id assert stored_obj.category_id == category.id +# def test_relationships(self, session, cls_, user): +# obj = cls_() +# +# with dbcleanup(session, obj) as obj_id: +# stored_obj = get_stored_obj(session, cls_, obj_id) +# assert stored_obj.user.id == user.id + + + # Misc. helper fixtures. @@ -598,6 +834,12 @@ def session(model): Session.remove() # Ensures we get a new session for each test +@pytest.fixture +def api_keys(model, session): + instance = model.APIKeys(key=get_unique_value()) + yield from dbcleanup_wrapper(session, instance) + + @pytest.fixture def category(model, session): instance = model.Category(name=get_unique_value()) @@ -622,12 +864,32 @@ def group(model, session): yield from dbcleanup_wrapper(session, instance) +@pytest.fixture +def group_role_association(model, session): + instance = model.GroupRoleAssociation(None, None) + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def password_reset_token(model, session, user): + token = get_unique_value() + instance = model.PasswordResetToken(user, token) + where_clause = model.PasswordResetToken.token == token + yield from dbcleanup_wrapper(session, instance, where_clause) + + @pytest.fixture def repository(model, session): instance = model.Repository() yield from dbcleanup_wrapper(session, instance) +@pytest.fixture +def repository_metadata(model, session): + instance = model.RepositoryMetadata() + yield from dbcleanup_wrapper(session, instance) + + @pytest.fixture def repository_review(model, session, user): instance = model.RepositoryReview() @@ -635,6 +897,18 @@ def repository_review(model, session, user): yield from dbcleanup_wrapper(session, instance) +@pytest.fixture +def repository_category_association(model, session, repository, category): + instance = model.RepositoryCategoryAssociation(repository, category) + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def repository_role_association(model, session, repository, role): + instance = model.RepositoryRoleAssociation(repository, role) + yield from dbcleanup_wrapper(session, instance) + + @pytest.fixture def role(model, session): instance = model.Role(name=get_unique_value()) @@ -647,6 +921,27 @@ def user(model, session): yield from dbcleanup_wrapper(session, instance) +@pytest.fixture +def user_group_association(model, session, user, group): + instance = model.UserGroupAssociation(user, group) + yield from dbcleanup_wrapper(session, instance) + + +@pytest.fixture +def user_role_association(model, session, user, role): + instance = model.UserRoleAssociation(user, role) + yield from dbcleanup_wrapper(session, instance) + + +# Fixtures yielding factory functions. + +@pytest.fixture +def component_review_factory(model): + def make_instance(*args, **kwds): + return model.ComponentReview(*args, **kwds) + return make_instance + + # Test utilities def dbcleanup_wrapper(session, obj, where_clause=None): From f45679e7a747dcb7554b61381f57003ba7e34f51 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Thu, 7 Oct 2021 23:04:48 -0400 Subject: [PATCH 05/55] Add relationship tests with some todos --- test/unit/shed_unit/model/test_mapping.py | 256 ++++++++++++++-------- 1 file changed, 161 insertions(+), 95 deletions(-) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 009c4d3ef77f..13f261448bd3 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -93,66 +93,31 @@ def test_relationships( role, group, password_reset_token, + user_group_association, + user_role_association, ): obj = cls_() obj.email = get_unique_value() obj.password = 'a' + obj.active_repositories.append(repository) + obj.galaxy_sessions.append(galaxy_session) + obj.api_keys.append(api_keys) + obj.reset_tokens.append(password_reset_token) + obj.groups.append(user_group_association) + obj.roles.append(user_role_association) + obj.repository_reviews.append(repository_review) + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.active_repositories == [repository] + assert stored_obj.galaxy_sessions == [galaxy_session] + assert stored_obj.api_keys == [api_keys] + assert stored_obj.reset_tokens == [password_reset_token] + assert stored_obj.groups == [user_group_association] + assert stored_obj.roles == [user_role_association] + assert stored_obj.repository_reviews == [repository_review] -# obj.addresses.append(user_address) -# obj.cloudauthz.append(cloud_authz) -# obj.custos_auth.append(custos_authnz_token) -# obj.default_permissions.append(default_user_permissions) -# obj.groups.append(user_group_association) -# obj.histories.append(history1) -# obj.histories.append(history2) -# obj.galaxy_sessions.append(galaxy_session) -# obj.quotas.append(user_quota_association) -# obj.social_auth.append(user_authnz_token) -# -# _private_role = role_factory(name=obj.email) -# private_user_role = user_role_association_factory(obj, _private_role) -# obj.roles.append(private_user_role) -# -# _non_private_role = role_factory(name='a') -# non_private_user_role = user_role_association_factory(obj, _non_private_role) -# obj.roles.append(non_private_user_role) -# -# swme = stored_workflow_menu_entry_factory() -# swme.stored_workflow = stored_workflow -# swme.user = obj -# -# user_preference.name = 'a' -# obj._preferences.set(user_preference) -# -# obj.api_keys.append(api_keys) -# obj.data_manager_histories.append(data_manager_history_association) -# obj.stored_workflows.append(stored_workflow) -# -# with dbcleanup(session, obj) as obj_id: -# stored_obj = get_stored_obj(session, cls_, obj_id) -# assert stored_obj.values.id == form_values.id -# assert stored_obj.addresses == [user_address] -# assert stored_obj.cloudauthz == [cloud_authz] -# assert stored_obj.custos_auth == [custos_authnz_token] -# assert stored_obj.default_permissions == [default_user_permissions] -# assert stored_obj.groups == [user_group_association] -# assert are_same_entity_collections(stored_obj.histories, [history1, history2]) -# assert stored_obj.active_histories == [history1] -# assert stored_obj.galaxy_sessions == [galaxy_session] -# assert stored_obj.quotas == [user_quota_association] -# assert stored_obj.social_auth == [user_authnz_token] -# assert stored_obj.stored_workflow_menu_entries == [swme] -# assert user_preference in stored_obj._preferences.values() -# assert stored_obj.api_keys == [api_keys] -# assert stored_obj.data_manager_histories == [data_manager_history_association] -# assert are_same_entity_collections(stored_obj.roles, [private_user_role, non_private_user_role]) -# assert stored_obj.non_private_roles == [non_private_user_role] -# assert stored_obj.stored_workflows == [stored_workflow] -# -# delete_from_database(session, [history1, history2, swme, private_user_role, non_private_user_role]) - class TestPasswordResetToken(BaseTest): def test_table(self, cls_): @@ -248,19 +213,24 @@ def test_relationships( cls_, repository_role_association, user_role_association, - group_role_association, + group_role_association_factory, + group, ): name, description, type_ = get_unique_value(), 'b', cls_.types.SYSTEM obj = cls_(name, description, type_) obj.repositories.append(repository_role_association) obj.users.append(user_role_association) - obj.groups.append(group_role_association) + + gra = group_role_association_factory(group, obj) + obj.groups.append(gra) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.repositories == [repository_role_association] assert stored_obj.users == [user_role_association] - #assert stored_obj.groups == [group_role_association] # TODO broken + assert stored_obj.groups == [gra] + + delete_from_database(session, gra) class TestRepositoryRoleAssociation(BaseTest): @@ -283,6 +253,14 @@ def test_columns(self, session, cls_, repository, role): assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time + def test_relationships(self, session, cls_, repository, role): + obj = cls_(repository, role) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository.id == repository.id + assert stored_obj.role.id == role.id + class TestUserGroupAssociation(BaseTest): @@ -312,6 +290,7 @@ def test_relationships(self, session, cls_, user, group): assert stored_obj.user.id == user.id assert stored_obj.group.id == group.id + class TestUserRoleAssociation(BaseTest): def test_table(self, cls_): @@ -340,6 +319,7 @@ def test_relationships(self, session, cls_, user, role): assert stored_obj.user.id == user.id assert stored_obj.role.id == role.id + class TestGroupRoleAssociation(BaseTest): def test_table(self, cls_): @@ -368,6 +348,7 @@ def test_relationships(self, session, cls_, group, role): assert stored_obj.group.id == group.id assert stored_obj.role.id == role.id + class TestGalaxySession(BaseTest): def test_table(self, cls_): @@ -484,13 +465,8 @@ def test_columns(self, session, cls_): assert stored_obj.description == description assert stored_obj.deleted == deleted - #def test_relationships(self, session, cls_, repository_category_association): - # obj = cls_() - # obj.name = get_unique_value() - # obj.repositories.append(repository_category_association) # TODO broken - - # with dbcleanup(session, obj) as obj_id: - # stored_obj = get_stored_obj(session, cls_, obj_id) + def test_relationships(self, session, cls_, repository_category_association): + pass # TODO: might be broken class TestRepository(BaseTest): @@ -552,14 +528,41 @@ def test_columns(self, session, cls_, user): assert stored_obj.description == description assert stored_obj.deleted == deleted -# def test_relationships(self, session, cls_, user): -# obj = cls_() -# -# with dbcleanup(session, obj) as obj_id: -# stored_obj = get_stored_obj(session, cls_, obj_id) -# assert stored_obj.user.id == user.id + def test_relationships( + self, + session, + cls_, + repository_category_association, + repository_rating_association, + repository_metadata_factory, + repository_role_association, + repository_review, + user_factory, + ): + metadata1 = repository_metadata_factory() + metadata2 = repository_metadata_factory() + user = user_factory() + reviewer = user_factory() + obj = cls_() + obj.user = user + obj.categories.append(repository_category_association) + obj.ratings.append(repository_rating_association) + obj.downloadable_revisions.append(metadata1) + obj.metadata_revisions.append(metadata2) + obj.roles.append(repository_role_association) + obj.reviews.append(repository_review) + obj.reviewers.append(reviewer) + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.user.id == user.id + assert stored_obj.categories == [repository_category_association] + assert stored_obj.ratings == [repository_rating_association] + assert stored_obj.roles == [repository_role_association] + # assert stored_obj.reviews == [repository_review] # TODO broken + # assert stored_obj.reviewers == [reviewer] # TODO + # TODO: metadata_revisions, downloadable_revisions class TestRepositoryMetadata(BaseTest): @@ -622,14 +625,11 @@ def test_columns(self, session, cls_, repository): def test_relationships(self, session, cls_, repository, repository_review): obj = cls_() obj.repository = repository - obj.review.append(repository_review) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.repository.id == repository.id -# assert stored_obj.review == [repository_review] # TODO broken - - + # TODO broken: has both review and reviews due to mistake in backref class TestRepositoryReview(BaseTest): @@ -669,24 +669,26 @@ def test_columns(self, session, cls_, repository, user): def test_relationships(self, session, cls_, repository, user, repository_metadata, component_review_factory): component_review1 = component_review_factory() + component_review1.deleted = False component_review2 = component_review_factory() + component_review2.deleted = False + component_review2.private = True obj = cls_() obj.repository = repository obj.user = user - #obj.repository_metadata.append(repository_metadata) # TODO broken obj.component_reviews.append(component_review1) obj.private_component_reviews.append(component_review2) + # obj.repository_metadata.append(repository_metadata) # TODO may be broken with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.repository.id == repository.id assert stored_obj.user.id == user.id - # TODO broken - # assert stored_obj.repository_metadata == [repository_metadata] - #assert stored_obj.component_reviews == [component_review1] - #assert stored_obj.private_component_reviews == [component_review2] - + # assert stored_obj.repository_metadata == [repository_metadata] # TODO + assert are_same_entity_collections(stored_obj.component_reviews, [component_review1, component_review2]) + assert stored_obj.private_component_reviews == [component_review2] + delete_from_database(session, [component_review1, component_review2]) class TestComponentReview(BaseTest): @@ -738,8 +740,6 @@ def test_relationships(self, session, cls_, repository_review, component): assert stored_obj.component.id == component.id - - class TestComponent(BaseTest): def test_table(self, cls_): @@ -785,14 +785,15 @@ def test_columns(self, session, cls_, repository, user): assert stored_obj.rating == rating assert stored_obj.comment == comment -# def test_relationships(self, session, cls_, user): -# obj = cls_() -# -# with dbcleanup(session, obj) as obj_id: -# stored_obj = get_stored_obj(session, cls_, obj_id) -# assert stored_obj.user.id == user.id - + def test_relationships(self, session, cls_, repository, user): + obj = cls_() + obj.repository = repository + obj.user = user + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository.id == repository.id + assert stored_obj.user.id == user.id class TestRepositoryCategoryAssociation(BaseTest): @@ -809,14 +810,13 @@ def test_columns(self, session, cls_, repository, category): assert stored_obj.repository_id == repository.id assert stored_obj.category_id == category.id -# def test_relationships(self, session, cls_, user): -# obj = cls_() -# -# with dbcleanup(session, obj) as obj_id: -# stored_obj = get_stored_obj(session, cls_, obj_id) -# assert stored_obj.user.id == user.id - + def test_relationships(self, session, cls_, repository, category): + obj = cls_(repository=repository, category=category) + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository.id == repository.id + assert stored_obj.category.id == category.id # Misc. helper fixtures. @@ -903,6 +903,12 @@ def repository_category_association(model, session, repository, category): yield from dbcleanup_wrapper(session, instance) +@pytest.fixture +def repository_rating_association(model, session): + instance = model.RepositoryRatingAssociation() + yield from dbcleanup_wrapper(session, instance) + + @pytest.fixture def repository_role_association(model, session, repository, role): instance = model.RepositoryRoleAssociation(repository, role) @@ -942,6 +948,30 @@ def make_instance(*args, **kwds): return make_instance +@pytest.fixture +def group_role_association_factory(model): + def make_instance(*args, **kwds): + return model.GroupRoleAssociation(*args, **kwds) + return make_instance + + +@pytest.fixture +def repository_metadata_factory(model): + def make_instance(*args, **kwds): + return model.RepositoryMetadata(*args, **kwds) + return make_instance + + +@pytest.fixture +def user_factory(model): + def make_instance(*args, **kwds): + user = model.User(*args, **kwds) + user.email = get_unique_value() + user.password = 'a' + return user + return make_instance + + # Test utilities def dbcleanup_wrapper(session, obj, where_clause=None): @@ -982,6 +1012,22 @@ def persist(session, obj, return_id=True): return obj_id +def delete_from_database(session, objects): + """ + Delete each object in objects from database. + May be called at the end of a test if use of a context manager is impractical. + (Assume all objects have the id field as their primary key.) + """ + # Ensure we have a list of objects (check for list explicitly: a model can be iterable) + if not isinstance(objects, list): + objects = [objects] + + for obj in objects: + table = obj.__table__ + stmt = delete(table).where(table.c.id == obj.id) + session.execute(stmt) + + def get_stored_obj(session, cls, obj_id=None, where_clause=None, unique=False): # Either obj_id or where_clause must be provided, but not both assert bool(obj_id) ^ (where_clause is not None) @@ -1012,3 +1058,23 @@ def has_unique_constraint(table, fields): def get_unique_value(): """Generate unique values to accommodate unique constraints.""" return uuid4().hex + + +def are_same_entity_collections(collection1, collection2): + """ + The 2 arguments are collections of instances of models that have an `id` + attribute as their primary key. + Returns `True` if collections are the same size and contain the same + instances. Instance equality is determined by the object's `id` attribute, + not its Python object identifier. + """ + if len(collection1) != len(collection2): + return False + + collection1.sort(key=lambda item: item.id) + collection2.sort(key=lambda item: item.id) + + for item1, item2 in zip(collection1, collection2): + if item1.id != item2.id: + return False + return True From 6e6d7d84b1e5f220504c509d19803ef58a74f408 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 11:40:32 -0400 Subject: [PATCH 06/55] Create registry, base class --- lib/tool_shed/webapp/model/__init__.py | 17 +++++++++++++++++ lib/tool_shed/webapp/model/mapping.py | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 328d42ea3225..dd84a8e4ab35 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -7,6 +7,9 @@ ) from typing import Any, Mapping, TYPE_CHECKING +from sqlalchemy.orm import registry +from sqlalchemy.orm.decl_api import DeclarativeMeta + import tool_shed.repository_types.util as rt_util from galaxy import util from galaxy.model.orm.now import now @@ -35,6 +38,20 @@ class _HasTable: _HasTable = object +mapper_registry = registry() + + +class Base(metaclass=DeclarativeMeta): + __abstract__ = True + registry = mapper_registry + metadata = mapper_registry.metadata + __init__ = mapper_registry.constructor + + @classmethod + def __declare_last__(cls): + cls.table = cls.__table__ + + class APIKeys(_HasTable): pass diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 0cddd4f5037e..e58127dcd14d 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -5,7 +5,7 @@ import logging from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, not_, String, Table, TEXT, true, UniqueConstraint -from sqlalchemy.orm import backref, registry, relation +from sqlalchemy.orm import backref, relation import tool_shed.webapp.model import tool_shed.webapp.util.shed_statistics as shed_statistics @@ -15,6 +15,7 @@ from galaxy.model.orm.now import now from tool_shed.webapp.model import APIKeys, Category, Component, ComponentReview from tool_shed.webapp.model import GalaxySession, Group, GroupRoleAssociation +from tool_shed.webapp.model import mapper_registry from tool_shed.webapp.model import PasswordResetToken, Repository, RepositoryCategoryAssociation from tool_shed.webapp.model import RepositoryMetadata, RepositoryRatingAssociation from tool_shed.webapp.model import RepositoryReview, RepositoryRoleAssociation, Role @@ -23,7 +24,6 @@ log = logging.getLogger(__name__) -mapper_registry = registry() metadata = mapper_registry.metadata APIKeys.table = Table("api_keys", metadata, From c7df119b713e77de6b73afd67837382303a2945d Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 11:54:24 -0400 Subject: [PATCH 07/55] Map APIKeys declaratively --- lib/tool_shed/webapp/model/__init__.py | 16 ++++++++++++++-- lib/tool_shed/webapp/model/mapping.py | 10 +--------- test/unit/shed_unit/model/test_mapping.py | 9 ++++++++- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index dd84a8e4ab35..44d41a7d789b 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -7,11 +7,18 @@ ) from typing import Any, Mapping, TYPE_CHECKING +from sqlalchemy import ( + Column, + DateTime, + ForeignKey, + Integer, +) from sqlalchemy.orm import registry from sqlalchemy.orm.decl_api import DeclarativeMeta import tool_shed.repository_types.util as rt_util from galaxy import util +from galaxy.model.custom_types import TrimmedString from galaxy.model.orm.now import now from galaxy.security.validate_user_input import validate_password_str from galaxy.util import unique_id @@ -52,8 +59,13 @@ def __declare_last__(cls): cls.table = cls.__table__ -class APIKeys(_HasTable): - pass +class APIKeys(Base, _HasTable): + __tablename__ = 'api_keys' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) + key = Column(TrimmedString(32), index=True, unique=True) class User(Dictifiable, _HasTable): diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index e58127dcd14d..6158d6dfec4d 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -26,12 +26,6 @@ metadata = mapper_registry.metadata -APIKeys.table = Table("api_keys", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), - Column("key", TrimmedString(32), index=True, unique=True)) - User.table = Table("galaxy_user", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -202,13 +196,11 @@ mapper_registry.map_imperatively(User, User.table, properties=dict(active_repositories=relation(Repository, primaryjoin=((Repository.table.c.user_id == User.table.c.id) & (not_(Repository.table.c.deleted))), order_by=(Repository.table.c.name)), galaxy_sessions=relation(GalaxySession, order_by=desc(GalaxySession.table.c.update_time)), - api_keys=relation(APIKeys, backref="user", order_by=desc(APIKeys.table.c.create_time)))) + api_keys=relation(APIKeys, backref="user", order_by=desc(APIKeys.create_time)))) mapper_registry.map_imperatively(PasswordResetToken, PasswordResetToken.table, properties=dict(user=relation(User, backref="reset_tokens"))) -mapper_registry.map_imperatively(APIKeys, APIKeys.table, properties={}) - mapper_registry.map_imperatively(Group, Group.table, properties=dict(users=relation(UserGroupAssociation))) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 13f261448bd3..3c0217d7ce53 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -28,7 +28,7 @@ def cls_(self, model): class TestAPIKeys(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'api_keys' + assert cls_.__tablename__ == 'api_keys' def test_columns(self, session, cls_, user): create_time, user_id, key = datetime.now(), user.id, get_unique_value() @@ -41,6 +41,13 @@ def test_columns(self, session, cls_, user): assert stored_obj.user_id == user_id assert stored_obj.key == key + def test_relationships(self, session, cls_, user): + obj = cls_(user_id=user.id, key=get_unique_value()) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.user.id == user.id + class TestUser(BaseTest): From 7d9cfe03015442adcc2e2b9db81455c78dcb56a2 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 12:02:59 -0400 Subject: [PATCH 08/55] Map PasswordResetToken declaratively --- lib/tool_shed/webapp/model/__init__.py | 15 +++++++++++++-- lib/tool_shed/webapp/model/mapping.py | 17 ++++++----------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 44d41a7d789b..cbb3948fd8b9 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -12,8 +12,12 @@ DateTime, ForeignKey, Integer, + String, +) +from sqlalchemy.orm import ( + registry, + relationship, ) -from sqlalchemy.orm import registry from sqlalchemy.orm.decl_api import DeclarativeMeta import tool_shed.repository_types.util as rt_util @@ -114,7 +118,14 @@ def set_password_cleartext(self, cleartext): self.password = new_secure_hash(text_type=cleartext) -class PasswordResetToken(_HasTable): +class PasswordResetToken(Base, _HasTable): + __tablename__ = 'password_reset_token' + + token = Column(String(32), primary_key=True, unique=True, index=True) + expiration_time = Column(DateTime) + user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) + user = relationship('User', back_populates='reset_tokens') + def __init__(self, user, token=None): if token: self.token = token diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 6158d6dfec4d..ef2051443f0f 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -38,11 +38,6 @@ Column("deleted", Boolean, index=True, default=False), Column("purged", Boolean, index=True, default=False)) -PasswordResetToken.table = Table("password_reset_token", metadata, - Column("token", String(32), primary_key=True, unique=True, index=True), - Column("expiration_time", DateTime), - Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True)) - Group.table = Table("galaxy_group", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -194,12 +189,12 @@ # With the tables defined we can define the mappers and setup the relationships between the model objects. mapper_registry.map_imperatively(User, User.table, - properties=dict(active_repositories=relation(Repository, primaryjoin=((Repository.table.c.user_id == User.table.c.id) & (not_(Repository.table.c.deleted))), order_by=(Repository.table.c.name)), - galaxy_sessions=relation(GalaxySession, order_by=desc(GalaxySession.table.c.update_time)), - api_keys=relation(APIKeys, backref="user", order_by=desc(APIKeys.create_time)))) - -mapper_registry.map_imperatively(PasswordResetToken, PasswordResetToken.table, - properties=dict(user=relation(User, backref="reset_tokens"))) + properties=dict( + active_repositories=relation(Repository, primaryjoin=((Repository.table.c.user_id == User.table.c.id) & (not_(Repository.table.c.deleted))), order_by=(Repository.table.c.name)), + galaxy_sessions=relation(GalaxySession, order_by=desc(GalaxySession.table.c.update_time)), + api_keys=relation(APIKeys, backref="user", order_by=desc(APIKeys.create_time)), + reset_tokens=relation(PasswordResetToken, back_populates='user'), + )) mapper_registry.map_imperatively(Group, Group.table, properties=dict(users=relation(UserGroupAssociation))) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 3c0217d7ce53..d605df91b604 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -128,7 +128,7 @@ def test_relationships( class TestPasswordResetToken(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'password_reset_token' + assert cls_.__tablename__ == 'password_reset_token' def test_columns_and_relationships(self, session, cls_, user): token = get_unique_value() From 80e71dd578d62918732d8b6ba3bbab23487721e0 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 12:27:38 -0400 Subject: [PATCH 09/55] Rearrang mapping tests alphabetically --- test/unit/shed_unit/model/test_mapping.py | 696 +++++++++++----------- 1 file changed, 348 insertions(+), 348 deletions(-) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index d605df91b604..f715159f9087 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -49,120 +49,21 @@ def test_relationships(self, session, cls_, user): assert stored_obj.user.id == user.id -class TestUser(BaseTest): - - def test_table(self, cls_): - assert cls_.table.name == 'galaxy_user' - - def test_columns(self, session, cls_): - create_time = datetime.now() - update_time = create_time + timedelta(hours=1) - email = get_unique_value() - username = get_unique_value() - password = 'c' - external = True - new_repo_alert = True - deleted = True - purged = True - - obj = cls_() - obj.create_time = create_time - obj.update_time = update_time - obj.email = email - obj.username = username - obj.password = password - obj.external = external - obj.new_repo_alert = new_repo_alert - obj.deleted = deleted - obj.purged = purged - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.id == obj_id - assert stored_obj.create_time == create_time - assert stored_obj.update_time == update_time - assert stored_obj.email == email - assert stored_obj.username == username - assert stored_obj.password == password - assert stored_obj.external == external - assert stored_obj.new_repo_alert == new_repo_alert - assert stored_obj.deleted == deleted - assert stored_obj.purged == purged - - def test_relationships( - self, - session, - cls_, - repository, - galaxy_session, - api_keys, - repository_review, - role, - group, - password_reset_token, - user_group_association, - user_role_association, - ): - obj = cls_() - obj.email = get_unique_value() - obj.password = 'a' - obj.active_repositories.append(repository) - obj.galaxy_sessions.append(galaxy_session) - obj.api_keys.append(api_keys) - obj.reset_tokens.append(password_reset_token) - obj.groups.append(user_group_association) - obj.roles.append(user_role_association) - obj.repository_reviews.append(repository_review) - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.active_repositories == [repository] - assert stored_obj.galaxy_sessions == [galaxy_session] - assert stored_obj.api_keys == [api_keys] - assert stored_obj.reset_tokens == [password_reset_token] - assert stored_obj.groups == [user_group_association] - assert stored_obj.roles == [user_role_association] - assert stored_obj.repository_reviews == [repository_review] - - -class TestPasswordResetToken(BaseTest): - - def test_table(self, cls_): - assert cls_.__tablename__ == 'password_reset_token' - - def test_columns_and_relationships(self, session, cls_, user): - token = get_unique_value() - expiration_time = datetime.now() - obj = cls_(user, token) - obj.expiration_time = expiration_time - - where_clause = cls_.token == token - - with dbcleanup(session, obj, where_clause): - stored_obj = get_stored_obj(session, cls_, where_clause=where_clause) - # test columns - assert stored_obj.token == token - assert stored_obj.expiration_time == expiration_time - assert stored_obj.user_id == user.id - # test relationships - assert stored_obj.user.id == user.id - - -class TestGroup(BaseTest): +class TestCategory(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'galaxy_group' + assert cls_.table.name == 'category' def test_columns(self, session, cls_): create_time = datetime.now() update_time = create_time + timedelta(hours=1) - name = get_unique_value() - deleted = True + name, description, deleted = get_unique_value(), 'b', True obj = cls_() obj.create_time = create_time obj.update_time = update_time obj.name = name + obj.description = description obj.deleted = deleted with dbcleanup(session, obj) as obj_id: @@ -171,189 +72,76 @@ def test_columns(self, session, cls_): assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time assert stored_obj.name == name + assert stored_obj.description == description assert stored_obj.deleted == deleted - def test_relationships( - self, - session, - cls_, - group_role_association, - user_group_association, - ): - obj = cls_(name=get_unique_value()) - obj.roles.append(group_role_association) - obj.users.append(user_group_association) - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.id == obj_id - assert stored_obj.roles == [group_role_association] - assert stored_obj.users == [user_group_association] + def test_relationships(self, session, cls_, repository_category_association): + pass # TODO: might be broken -class TestRole(BaseTest): +class TestComponent(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'role' + assert cls_.table.name == 'component' def test_columns(self, session, cls_): - name, description, type_, deleted = get_unique_value(), 'b', cls_.types.SYSTEM, True - create_time = datetime.now() - update_time = create_time + timedelta(hours=1) - obj = cls_(name, description, type_, deleted) - obj.create_time = create_time - obj.update_time = update_time + name, description = 'a', 'b' + obj = cls_(name=name, description=description) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.id == obj_id - assert stored_obj.create_time == create_time - assert stored_obj.update_time == update_time assert stored_obj.name == name assert stored_obj.description == description - assert stored_obj.type == type_ - assert stored_obj.deleted == deleted - - def test_relationships( - self, - session, - cls_, - repository_role_association, - user_role_association, - group_role_association_factory, - group, - ): - name, description, type_ = get_unique_value(), 'b', cls_.types.SYSTEM - obj = cls_(name, description, type_) - obj.repositories.append(repository_role_association) - obj.users.append(user_role_association) - - gra = group_role_association_factory(group, obj) - obj.groups.append(gra) - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.repositories == [repository_role_association] - assert stored_obj.users == [user_role_association] - assert stored_obj.groups == [gra] - - delete_from_database(session, gra) - - -class TestRepositoryRoleAssociation(BaseTest): - - def test_table(self, cls_): - assert cls_.table.name == 'repository_role_association' - - def test_columns(self, session, cls_, repository, role): - create_time = datetime.now() - update_time = create_time + timedelta(hours=1) - obj = cls_(repository, role) - obj.create_time = create_time - obj.update_time = update_time - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.id == obj_id - assert stored_obj.repository_id == repository.id - assert stored_obj.role_id == role.id - assert stored_obj.create_time == create_time - assert stored_obj.update_time == update_time - - def test_relationships(self, session, cls_, repository, role): - obj = cls_(repository, role) - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.repository.id == repository.id - assert stored_obj.role.id == role.id - - -class TestUserGroupAssociation(BaseTest): - - def test_table(self, cls_): - assert cls_.table.name == 'user_group_association' - - def test_columns(self, session, cls_, user, group): - create_time = datetime.now() - update_time = create_time + timedelta(hours=1) - obj = cls_(user, group) - obj.create_time = create_time - obj.update_time = update_time - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.id == obj_id - assert stored_obj.user_id == user.id - assert stored_obj.group_id == group.id - assert stored_obj.create_time == create_time - assert stored_obj.update_time == update_time - - def test_relationships(self, session, cls_, user, group): - obj = cls_(user, group) - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.user.id == user.id - assert stored_obj.group.id == group.id - -class TestUserRoleAssociation(BaseTest): +class TestComponentReview(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'user_role_association' + assert cls_.table.name == 'component_review' - def test_columns(self, session, cls_, user, role): + def test_columns(self, session, cls_, repository_review, component): create_time = datetime.now() update_time = create_time + timedelta(hours=1) - obj = cls_(user, role) - obj.create_time = create_time - obj.update_time = update_time - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.id == obj_id - assert stored_obj.user_id == user.id - assert stored_obj.role_id == role.id - assert stored_obj.create_time == create_time - assert stored_obj.update_time == update_time - - def test_relationships(self, session, cls_, user, role): - obj = cls_(user, role) - - with dbcleanup(session, obj) as obj_id: - stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.user.id == user.id - assert stored_obj.role.id == role.id - - -class TestGroupRoleAssociation(BaseTest): - - def test_table(self, cls_): - assert cls_.table.name == 'group_role_association' + comment = 'a' + private = True + approved = 'b' + rating = 1 + deleted = True - def test_columns(self, session, cls_, group, role): - obj = cls_(group, role) - create_time = datetime.now() - update_time = create_time + timedelta(hours=1) + obj = cls_() obj.create_time = create_time obj.update_time = update_time + obj.repository_review = repository_review + obj.component = component + obj.comment = comment + obj.private = private + obj.approved = approved + obj.rating = rating + obj.deleted = deleted with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.id == obj_id - assert stored_obj.group_id == group.id - assert stored_obj.role_id == role.id assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time + assert stored_obj.repository_review_id == repository_review.id + assert stored_obj.component_id == component.id + assert stored_obj.comment == comment + assert stored_obj.private == private + assert stored_obj.approved == approved + assert stored_obj.rating == rating + assert stored_obj.deleted == deleted - def test_relationships(self, session, cls_, group, role): - obj = cls_(group, role) + def test_relationships(self, session, cls_, repository_review, component): + obj = cls_() + obj.repository_review = repository_review + obj.component = component with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.group.id == group.id - assert stored_obj.role.id == role.id + assert stored_obj.repository_review.id == repository_review.id + assert stored_obj.component.id == component.id class TestGalaxySession(BaseTest): @@ -406,74 +194,99 @@ def test_relationships(self, session, cls_, user): assert stored_obj.user.id == user.id -class TestTag(BaseTest): +class TestGroup(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'tag' - assert has_unique_constraint(cls_.table, ('name',)) + assert cls_.table.name == 'galaxy_group' def test_columns(self, session, cls_): - parent_tag = cls_() - type_, name = 1, get_unique_value() - obj = cls_(type=type_, name=name) - obj.parent = parent_tag + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + name = get_unique_value() + deleted = True + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.name = name + obj.deleted = deleted with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.id == obj_id - assert stored_obj.type == type_ - assert stored_obj.parent_id == parent_tag.id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time assert stored_obj.name == name + assert stored_obj.deleted == deleted def test_relationships( self, session, cls_, + group_role_association, + user_group_association, ): - obj = cls_() - parent_tag = cls_() - child_tag = cls_() - obj.parent = parent_tag - obj.children.append(child_tag) - - def add_association(assoc_object, assoc_attribute): - assoc_object.tag = obj - getattr(obj, assoc_attribute).append(assoc_object) + obj = cls_(name=get_unique_value()) + obj.roles.append(group_role_association) + obj.users.append(user_group_association) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.parent.id == parent_tag.id - assert stored_obj.children == [child_tag] + assert stored_obj.id == obj_id + assert stored_obj.roles == [group_role_association] + assert stored_obj.users == [user_group_association] -class TestCategory(BaseTest): +class TestGroupRoleAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'category' + assert cls_.table.name == 'group_role_association' - def test_columns(self, session, cls_): + def test_columns(self, session, cls_, group, role): + obj = cls_(group, role) create_time = datetime.now() update_time = create_time + timedelta(hours=1) - name, description, deleted = get_unique_value(), 'b', True - - obj = cls_() obj.create_time = create_time obj.update_time = update_time - obj.name = name - obj.description = description - obj.deleted = deleted with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.id == obj_id + assert stored_obj.group_id == group.id + assert stored_obj.role_id == role.id assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time - assert stored_obj.name == name - assert stored_obj.description == description - assert stored_obj.deleted == deleted - def test_relationships(self, session, cls_, repository_category_association): - pass # TODO: might be broken + def test_relationships(self, session, cls_, group, role): + obj = cls_(group, role) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.group.id == group.id + assert stored_obj.role.id == role.id + + +class TestPasswordResetToken(BaseTest): + + def test_table(self, cls_): + assert cls_.__tablename__ == 'password_reset_token' + + def test_columns_and_relationships(self, session, cls_, user): + token = get_unique_value() + expiration_time = datetime.now() + obj = cls_(user, token) + obj.expiration_time = expiration_time + + where_clause = cls_.token == token + + with dbcleanup(session, obj, where_clause): + stored_obj = get_stored_obj(session, cls_, where_clause=where_clause) + # test columns + assert stored_obj.token == token + assert stored_obj.expiration_time == expiration_time + assert stored_obj.user_id == user.id + # test relationships + assert stored_obj.user.id == user.id class TestRepository(BaseTest): @@ -572,6 +385,29 @@ def test_relationships( # TODO: metadata_revisions, downloadable_revisions +class TestRepositoryCategoryAssociation(BaseTest): + + def test_table(self, cls_): + assert cls_.table.name == 'repository_category_association' + + def test_columns(self, session, cls_, repository, category): + obj = cls_(repository=repository, category=category) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.repository_id == repository.id + assert stored_obj.category_id == category.id + + def test_relationships(self, session, cls_, repository, category): + obj = cls_(repository=repository, category=category) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository.id == repository.id + assert stored_obj.category.id == category.id + + class TestRepositoryMetadata(BaseTest): def test_table(self, cls_): @@ -629,14 +465,54 @@ def test_columns(self, session, cls_, repository): assert stored_obj.includes_tool_dependencies == includes_tool_dependencies assert stored_obj.includes_workflows == includes_workflows - def test_relationships(self, session, cls_, repository, repository_review): + def test_relationships(self, session, cls_, repository, repository_review): + obj = cls_() + obj.repository = repository + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository.id == repository.id + # TODO broken: has both review and reviews due to mistake in backref + + +class TestRepositoryRatingAssociation(BaseTest): + + def test_table(self, cls_): + assert cls_.table.name == 'repository_rating_association' + + def test_columns(self, session, cls_, repository, user): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + rating = 1 + comment = 'a' + + obj = cls_() + obj.create_time = create_time + obj.update_time = update_time + obj.repository = repository + obj.user = user + obj.rating = rating + obj.comment = comment + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + assert stored_obj.repository_id == repository.id + assert stored_obj.user_id == user.id + assert stored_obj.rating == rating + assert stored_obj.comment == comment + + def test_relationships(self, session, cls_, repository, user): obj = cls_() obj.repository = repository + obj.user = user with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.repository.id == repository.id - # TODO broken: has both review and reviews due to mistake in backref + assert stored_obj.user.id == user.id class TestRepositoryReview(BaseTest): @@ -698,132 +574,256 @@ def test_relationships(self, session, cls_, repository, user, repository_metadat delete_from_database(session, [component_review1, component_review2]) -class TestComponentReview(BaseTest): +class TestRepositoryRoleAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'component_review' + assert cls_.table.name == 'repository_role_association' - def test_columns(self, session, cls_, repository_review, component): + def test_columns(self, session, cls_, repository, role): create_time = datetime.now() update_time = create_time + timedelta(hours=1) - comment = 'a' - private = True - approved = 'b' - rating = 1 - deleted = True + obj = cls_(repository, role) + obj.create_time = create_time + obj.update_time = update_time - obj = cls_() + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.repository_id == repository.id + assert stored_obj.role_id == role.id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + + def test_relationships(self, session, cls_, repository, role): + obj = cls_(repository, role) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repository.id == repository.id + assert stored_obj.role.id == role.id + + +class TestRole(BaseTest): + + def test_table(self, cls_): + assert cls_.table.name == 'role' + + def test_columns(self, session, cls_): + name, description, type_, deleted = get_unique_value(), 'b', cls_.types.SYSTEM, True + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + obj = cls_(name, description, type_, deleted) obj.create_time = create_time obj.update_time = update_time - obj.repository_review = repository_review - obj.component = component - obj.comment = comment - obj.private = private - obj.approved = approved - obj.rating = rating - obj.deleted = deleted with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.id == obj_id assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time - assert stored_obj.repository_review_id == repository_review.id - assert stored_obj.component_id == component.id - assert stored_obj.comment == comment - assert stored_obj.private == private - assert stored_obj.approved == approved - assert stored_obj.rating == rating + assert stored_obj.name == name + assert stored_obj.description == description + assert stored_obj.type == type_ assert stored_obj.deleted == deleted - def test_relationships(self, session, cls_, repository_review, component): - obj = cls_() - obj.repository_review = repository_review - obj.component = component + def test_relationships( + self, + session, + cls_, + repository_role_association, + user_role_association, + group_role_association_factory, + group, + ): + name, description, type_ = get_unique_value(), 'b', cls_.types.SYSTEM + obj = cls_(name, description, type_) + obj.repositories.append(repository_role_association) + obj.users.append(user_role_association) + + gra = group_role_association_factory(group, obj) + obj.groups.append(gra) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.repository_review.id == repository_review.id - assert stored_obj.component.id == component.id + assert stored_obj.repositories == [repository_role_association] + assert stored_obj.users == [user_role_association] + assert stored_obj.groups == [gra] + + delete_from_database(session, gra) -class TestComponent(BaseTest): +class TestTag(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'component' + assert cls_.table.name == 'tag' + assert has_unique_constraint(cls_.table, ('name',)) def test_columns(self, session, cls_): - name, description = 'a', 'b' - obj = cls_(name=name, description=description) + parent_tag = cls_() + type_, name = 1, get_unique_value() + obj = cls_(type=type_, name=name) + obj.parent = parent_tag with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.id == obj_id + assert stored_obj.type == type_ + assert stored_obj.parent_id == parent_tag.id assert stored_obj.name == name - assert stored_obj.description == description + def test_relationships( + self, + session, + cls_, + ): + obj = cls_() + parent_tag = cls_() + child_tag = cls_() + obj.parent = parent_tag + obj.children.append(child_tag) + + def add_association(assoc_object, assoc_attribute): + assoc_object.tag = obj + getattr(obj, assoc_attribute).append(assoc_object) -class TestRepositoryRatingAssociation(BaseTest): + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.parent.id == parent_tag.id + assert stored_obj.children == [child_tag] + + +class TestUser(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'repository_rating_association' + assert cls_.table.name == 'galaxy_user' - def test_columns(self, session, cls_, repository, user): + def test_columns(self, session, cls_): create_time = datetime.now() update_time = create_time + timedelta(hours=1) - rating = 1 - comment = 'a' + email = get_unique_value() + username = get_unique_value() + password = 'c' + external = True + new_repo_alert = True + deleted = True + purged = True obj = cls_() obj.create_time = create_time obj.update_time = update_time - obj.repository = repository - obj.user = user - obj.rating = rating - obj.comment = comment + obj.email = email + obj.username = username + obj.password = password + obj.external = external + obj.new_repo_alert = new_repo_alert + obj.deleted = deleted + obj.purged = purged with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.id == obj_id assert stored_obj.create_time == create_time assert stored_obj.update_time == update_time - assert stored_obj.repository_id == repository.id - assert stored_obj.user_id == user.id - assert stored_obj.rating == rating - assert stored_obj.comment == comment + assert stored_obj.email == email + assert stored_obj.username == username + assert stored_obj.password == password + assert stored_obj.external == external + assert stored_obj.new_repo_alert == new_repo_alert + assert stored_obj.deleted == deleted + assert stored_obj.purged == purged - def test_relationships(self, session, cls_, repository, user): + def test_relationships( + self, + session, + cls_, + repository, + galaxy_session, + api_keys, + repository_review, + role, + group, + password_reset_token, + user_group_association, + user_role_association, + ): obj = cls_() - obj.repository = repository - obj.user = user + obj.email = get_unique_value() + obj.password = 'a' + obj.active_repositories.append(repository) + obj.galaxy_sessions.append(galaxy_session) + obj.api_keys.append(api_keys) + obj.reset_tokens.append(password_reset_token) + obj.groups.append(user_group_association) + obj.roles.append(user_role_association) + obj.repository_reviews.append(repository_review) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.active_repositories == [repository] + assert stored_obj.galaxy_sessions == [galaxy_session] + assert stored_obj.api_keys == [api_keys] + assert stored_obj.reset_tokens == [password_reset_token] + assert stored_obj.groups == [user_group_association] + assert stored_obj.roles == [user_role_association] + assert stored_obj.repository_reviews == [repository_review] + + +class TestUserGroupAssociation(BaseTest): + + def test_table(self, cls_): + assert cls_.table.name == 'user_group_association' + + def test_columns(self, session, cls_, user, group): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + obj = cls_(user, group) + obj.create_time = create_time + obj.update_time = update_time + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.id == obj_id + assert stored_obj.user_id == user.id + assert stored_obj.group_id == group.id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time + + def test_relationships(self, session, cls_, user, group): + obj = cls_(user, group) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.repository.id == repository.id assert stored_obj.user.id == user.id + assert stored_obj.group.id == group.id -class TestRepositoryCategoryAssociation(BaseTest): +class TestUserRoleAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'repository_category_association' + assert cls_.table.name == 'user_role_association' - def test_columns(self, session, cls_, repository, category): - obj = cls_(repository=repository, category=category) + def test_columns(self, session, cls_, user, role): + create_time = datetime.now() + update_time = create_time + timedelta(hours=1) + obj = cls_(user, role) + obj.create_time = create_time + obj.update_time = update_time with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.id == obj_id - assert stored_obj.repository_id == repository.id - assert stored_obj.category_id == category.id + assert stored_obj.user_id == user.id + assert stored_obj.role_id == role.id + assert stored_obj.create_time == create_time + assert stored_obj.update_time == update_time - def test_relationships(self, session, cls_, repository, category): - obj = cls_(repository=repository, category=category) + def test_relationships(self, session, cls_, user, role): + obj = cls_(user, role) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) - assert stored_obj.repository.id == repository.id - assert stored_obj.category.id == category.id + assert stored_obj.user.id == user.id + assert stored_obj.role.id == role.id # Misc. helper fixtures. From 7416cdbbe72981d17c36e954fcda1568395e52e8 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 12:33:19 -0400 Subject: [PATCH 10/55] Map Component declaratively --- lib/tool_shed/webapp/model/__init__.py | 8 +++++++- lib/tool_shed/webapp/model/mapping.py | 9 +-------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index cbb3948fd8b9..700c027e5485 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -13,6 +13,7 @@ ForeignKey, Integer, String, + TEXT, ) from sqlalchemy.orm import ( registry, @@ -450,7 +451,12 @@ def __init__(self, repository_review_id=None, component_id=None, comment=None, p self.deleted = deleted -class Component(_HasTable): +class Component(Base, _HasTable): + __tablename__ = 'component' + + id = Column(Integer, primary_key=True) + name = Column(TrimmedString(255)) + description = Column(TEXT) def __init__(self, name=None, description=None): self.name = name diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index ef2051443f0f..430522c4c755 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -153,11 +153,6 @@ Column("rating", Integer), Column("deleted", Boolean, index=True, default=False)) -Component.table = Table("component", metadata, - Column("id", Integer, primary_key=True), - Column("name", TrimmedString(255)), - Column("description", TEXT)) - RepositoryRatingAssociation.table = Table("repository_rating_association", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -285,9 +280,7 @@ mapper_registry.map_imperatively(ComponentReview, ComponentReview.table, properties=dict(repository_review=relation(RepositoryReview), component=relation(Component, - primaryjoin=(ComponentReview.table.c.component_id == Component.table.c.id)))) - -mapper_registry.map_imperatively(Component, Component.table) + primaryjoin=(ComponentReview.table.c.component_id == Component.id)))) mapper_registry.map_imperatively(RepositoryRatingAssociation, RepositoryRatingAssociation.table, properties=dict(repository=relation(Repository), user=relation(User))) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index f715159f9087..a7fc83ce6158 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -82,7 +82,7 @@ def test_relationships(self, session, cls_, repository_category_association): class TestComponent(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'component' + assert cls_.__tablename__ == 'component' def test_columns(self, session, cls_): name, description = 'a', 'b' From d66f521b7497f3dc6dd67a9ac43d9746c3ab1dc1 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 12:40:52 -0400 Subject: [PATCH 11/55] Map ComponentReview declarative; drop primaryjoin attr --- lib/tool_shed/webapp/model/__init__.py | 19 ++++++++++++++++++- lib/tool_shed/webapp/model/mapping.py | 23 +++-------------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 700c027e5485..eb8704100488 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -8,6 +8,7 @@ from typing import Any, Mapping, TYPE_CHECKING from sqlalchemy import ( + Boolean, Column, DateTime, ForeignKey, @@ -436,7 +437,23 @@ def __init__(self, repository_id=None, changeset_revision=None, user_id=None, ra self.deleted = deleted -class ComponentReview(Dictifiable, _HasTable): +class ComponentReview(Base, Dictifiable, _HasTable): + __tablename__ = 'component_review' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + repository_review_id = Column(Integer, ForeignKey("repository_review.id"), index=True) + component_id = Column(Integer, ForeignKey("component.id"), index=True) + comment = Column(TEXT) + private = Column(Boolean, default=False) + approved = Column(TrimmedString(255)) + rating = Column(Integer) + deleted = Column(Boolean, index=True, default=False) + + repository_review = relationship('RepositoryReview') + component = relationship('Component') + dict_collection_visible_keys = ['id', 'repository_review_id', 'component_id', 'private', 'approved', 'rating', 'deleted'] dict_element_visible_keys = ['id', 'repository_review_id', 'component_id', 'private', 'approved', 'rating', 'deleted'] approved_states = Bunch(NO='no', YES='yes', NA='not_applicable') diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 430522c4c755..505371913c04 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -13,7 +13,7 @@ from galaxy.model.custom_types import MutableJSONType, TrimmedString from galaxy.model.orm.engine_factory import build_engine from galaxy.model.orm.now import now -from tool_shed.webapp.model import APIKeys, Category, Component, ComponentReview +from tool_shed.webapp.model import APIKeys, Category, ComponentReview from tool_shed.webapp.model import GalaxySession, Group, GroupRoleAssociation from tool_shed.webapp.model import mapper_registry from tool_shed.webapp.model import PasswordResetToken, Repository, RepositoryCategoryAssociation @@ -141,18 +141,6 @@ Column("rating", Integer, index=True), Column("deleted", Boolean, index=True, default=False)) -ComponentReview.table = Table("component_review", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("repository_review_id", Integer, ForeignKey("repository_review.id"), index=True), - Column("component_id", Integer, ForeignKey("component.id"), index=True), - Column("comment", TEXT), - Column("private", Boolean, default=False), - Column("approved", TrimmedString(255)), - Column("rating", Integer), - Column("deleted", Boolean, index=True, default=False)) - RepositoryRatingAssociation.table = Table("repository_rating_association", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -273,14 +261,9 @@ backref='review'), user=relation(User, backref="repository_reviews"), component_reviews=relation(ComponentReview, - primaryjoin=((RepositoryReview.table.c.id == ComponentReview.table.c.repository_review_id) & (ComponentReview.table.c.deleted == false()))), + primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false()))), private_component_reviews=relation(ComponentReview, - primaryjoin=((RepositoryReview.table.c.id == ComponentReview.table.c.repository_review_id) & (ComponentReview.table.c.deleted == false()) & (ComponentReview.table.c.private == true()))))) - -mapper_registry.map_imperatively(ComponentReview, ComponentReview.table, - properties=dict(repository_review=relation(RepositoryReview), - component=relation(Component, - primaryjoin=(ComponentReview.table.c.component_id == Component.id)))) + primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false()) & (ComponentReview.private == true()))))) mapper_registry.map_imperatively(RepositoryRatingAssociation, RepositoryRatingAssociation.table, properties=dict(repository=relation(Repository), user=relation(User))) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index a7fc83ce6158..8e0214485d9c 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -98,7 +98,7 @@ def test_columns(self, session, cls_): class TestComponentReview(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'component_review' + assert cls_.__tablename__ == 'component_review' def test_columns(self, session, cls_, repository_review, component): create_time = datetime.now() From 682c35b6314c280c618511cf27eb4bb0fb40253a Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 12:50:03 -0400 Subject: [PATCH 12/55] Map GalaxySession declaratively --- lib/tool_shed/webapp/model/__init__.py | 18 +++++++++++++++++- lib/tool_shed/webapp/model/mapping.py | 18 +----------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index eb8704100488..7b10b38c8a67 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -196,7 +196,23 @@ def __init__(self, repository, role): self.role = role -class GalaxySession(_HasTable): +class GalaxySession(Base, _HasTable): + __tablename__ = 'galaxy_session' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + user_id = Column(Integer, ForeignKey("galaxy_user.id"), index=True, nullable=True) + remote_host = Column(String(255)) + remote_addr = Column(String(255)) + referer = Column(TEXT) + # unique 128 bit random number coerced to a string + session_key = Column(TrimmedString(255), index=True, unique=True) + is_valid = Column(Boolean, default=False) + # saves a reference to the previous session so we have a way to chain them together + prev_session_id = Column(Integer) + last_action = Column(DateTime) + user = relationship('User', back_populates='galaxy_sessions') def __init__(self, id=None, diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 505371913c04..2474be5b5cbb 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -82,19 +82,6 @@ Column("create_time", DateTime, default=now), Column("update_time", DateTime, default=now, onupdate=now)) -GalaxySession.table = Table("galaxy_session", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True, nullable=True), - Column("remote_host", String(255)), - Column("remote_addr", String(255)), - Column("referer", TEXT), - Column("session_key", TrimmedString(255), index=True, unique=True), # unique 128 bit random number coerced to a string - Column("is_valid", Boolean, default=False), - Column("prev_session_id", Integer), # saves a reference to the previous session so we have a way to chain them together - Column("last_action", DateTime)) - Repository.table = Table("repository", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -174,7 +161,7 @@ mapper_registry.map_imperatively(User, User.table, properties=dict( active_repositories=relation(Repository, primaryjoin=((Repository.table.c.user_id == User.table.c.id) & (not_(Repository.table.c.deleted))), order_by=(Repository.table.c.name)), - galaxy_sessions=relation(GalaxySession, order_by=desc(GalaxySession.table.c.update_time)), + galaxy_sessions=relation(GalaxySession, order_by=desc(GalaxySession.update_time)), api_keys=relation(APIKeys, backref="user", order_by=desc(APIKeys.create_time)), reset_tokens=relation(PasswordResetToken, back_populates='user'), )) @@ -213,9 +200,6 @@ group=relation(Group, backref="roles"), role=relation(Role))) -mapper_registry.map_imperatively(GalaxySession, GalaxySession.table, - properties=dict(user=relation(User))) - mapper_registry.map_imperatively(Tag, Tag.table, properties=dict(children=relation(Tag, backref=backref('parent', remote_side=[Tag.table.c.id])))) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 8e0214485d9c..eea40bffb87f 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -147,7 +147,7 @@ def test_relationships(self, session, cls_, repository_review, component): class TestGalaxySession(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'galaxy_session' + assert cls_.__tablename__ == 'galaxy_session' def test_columns(self, session, cls_, user, galaxy_session): From 9f19588186b30b8212b59f314ad50df514a63b60 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 12:57:06 -0400 Subject: [PATCH 13/55] Map Group declaratively; mark mapping bug to fix --- lib/tool_shed/webapp/model/__init__.py | 12 +++++++++++- lib/tool_shed/webapp/model/mapping.py | 16 +++------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 7b10b38c8a67..d63d630bc232 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -137,7 +137,17 @@ def __init__(self, user, token=None): self.expiration_time = now() + timedelta(hours=24) -class Group(Dictifiable, _HasTable): +class Group(Base, Dictifiable, _HasTable): + __tablename__ = 'galaxy_group' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + name = Column(String(255), index=True, unique=True) + deleted = Column(Boolean, index=True, default=False) + roles = relationship('GroupRoleAssociation', back_populates='group') + users = relationship('UserGroupAssociation') # TODO: fix bug: incorrect usage of backref/duplicate rel users/members; add back_populates + dict_collection_visible_keys = ['id', 'name'] dict_element_visible_keys = ['id', 'name'] diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 2474be5b5cbb..f0288de24a48 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -38,13 +38,6 @@ Column("deleted", Boolean, index=True, default=False), Column("purged", Boolean, index=True, default=False)) -Group.table = Table("galaxy_group", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("name", String(255), index=True, unique=True), - Column("deleted", Boolean, index=True, default=False)) - Role.table = Table("role", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -166,9 +159,6 @@ reset_tokens=relation(PasswordResetToken, back_populates='user'), )) -mapper_registry.map_imperatively(Group, Group.table, - properties=dict(users=relation(UserGroupAssociation))) - mapper_registry.map_imperatively(Role, Role.table, properties=dict( repositories=relation(RepositoryRoleAssociation, @@ -176,7 +166,7 @@ users=relation(UserRoleAssociation, primaryjoin=((Role.table.c.id == UserRoleAssociation.table.c.role_id) & (UserRoleAssociation.table.c.user_id == User.table.c.id))), groups=relation(GroupRoleAssociation, - primaryjoin=((Role.table.c.id == GroupRoleAssociation.table.c.role_id) & (GroupRoleAssociation.table.c.group_id == Group.table.c.id))))) + primaryjoin=((Role.table.c.id == GroupRoleAssociation.table.c.role_id) & (GroupRoleAssociation.table.c.group_id == Group.id))))) mapper_registry.map_imperatively(RepositoryRoleAssociation, RepositoryRoleAssociation.table, properties=dict( @@ -185,7 +175,7 @@ mapper_registry.map_imperatively(UserGroupAssociation, UserGroupAssociation.table, properties=dict(user=relation(User, backref="groups"), - group=relation(Group, backref="members"))) + group=relation(Group, backref="members"))) # TODO fix bug: members should be users; check codebase for references mapper_registry.map_imperatively(UserRoleAssociation, UserRoleAssociation.table, properties=dict( @@ -197,7 +187,7 @@ mapper_registry.map_imperatively(GroupRoleAssociation, GroupRoleAssociation.table, properties=dict( - group=relation(Group, backref="roles"), + group=relation(Group, back_populates="roles"), role=relation(Role))) mapper_registry.map_imperatively(Tag, Tag.table, diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index eea40bffb87f..fa226fbf2706 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -197,7 +197,7 @@ def test_relationships(self, session, cls_, user): class TestGroup(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'galaxy_group' + assert cls_.__tablename__ == 'galaxy_group' def test_columns(self, session, cls_): create_time = datetime.now() From be34e0506821fa520d42900361b8fb7d2a262ce4 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 13:04:49 -0400 Subject: [PATCH 14/55] Map GroupRoleAssociation declaratively, mark bug --- lib/tool_shed/webapp/model/__init__.py | 12 +++++++++++- lib/tool_shed/webapp/model/mapping.py | 14 +------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index d63d630bc232..f85587c4fe2c 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -194,7 +194,17 @@ def __init__(self, user, role): self.role = role -class GroupRoleAssociation(_HasTable): +class GroupRoleAssociation(Base, _HasTable): + __tablename__ = 'group_role_association' + + id = Column(Integer, primary_key=True) + group_id = Column(Integer, ForeignKey("galaxy_group.id"), index=True) + role_id = Column(Integer, ForeignKey("role.id"), index=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + group = relationship('Group', back_populates='roles') + role = relationship('Role') # TODO add back_populates='groups' + def __init__(self, group, role): self.group = group self.role = role diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index f0288de24a48..75b117589d2c 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -61,13 +61,6 @@ Column("create_time", DateTime, default=now), Column("update_time", DateTime, default=now, onupdate=now)) -GroupRoleAssociation.table = Table("group_role_association", metadata, - Column("id", Integer, primary_key=True), - Column("group_id", Integer, ForeignKey("galaxy_group.id"), index=True), - Column("role_id", Integer, ForeignKey("role.id"), index=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now)) - RepositoryRoleAssociation.table = Table("repository_role_association", metadata, Column("id", Integer, primary_key=True), Column("repository_id", Integer, ForeignKey("repository.id"), index=True), @@ -166,7 +159,7 @@ users=relation(UserRoleAssociation, primaryjoin=((Role.table.c.id == UserRoleAssociation.table.c.role_id) & (UserRoleAssociation.table.c.user_id == User.table.c.id))), groups=relation(GroupRoleAssociation, - primaryjoin=((Role.table.c.id == GroupRoleAssociation.table.c.role_id) & (GroupRoleAssociation.table.c.group_id == Group.id))))) + primaryjoin=((Role.table.c.id == GroupRoleAssociation.role_id) & (GroupRoleAssociation.group_id == Group.id))))) mapper_registry.map_imperatively(RepositoryRoleAssociation, RepositoryRoleAssociation.table, properties=dict( @@ -185,11 +178,6 @@ primaryjoin=((User.table.c.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.table.c.id) & not_(Role.table.c.name == User.table.c.email))), role=relation(Role))) -mapper_registry.map_imperatively(GroupRoleAssociation, GroupRoleAssociation.table, - properties=dict( - group=relation(Group, back_populates="roles"), - role=relation(Role))) - mapper_registry.map_imperatively(Tag, Tag.table, properties=dict(children=relation(Tag, backref=backref('parent', remote_side=[Tag.table.c.id])))) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index fa226fbf2706..fc6cdc47b6bf 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -240,7 +240,7 @@ def test_relationships( class TestGroupRoleAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'group_role_association' + assert cls_.__tablename__ == 'group_role_association' def test_columns(self, session, cls_, group, role): obj = cls_(group, role) From b26a5304e49d089969d9ac03d32e835babf26d7e Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 13:25:52 -0400 Subject: [PATCH 15/55] Map Role declaratively; mark todos --- lib/tool_shed/webapp/model/__init__.py | 15 ++++++++++++++- lib/tool_shed/webapp/model/mapping.py | 22 ++-------------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index f85587c4fe2c..090ff1943cc5 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -156,7 +156,20 @@ def __init__(self, name=None): self.deleted = False -class Role(Dictifiable, _HasTable): +class Role(Base, Dictifiable, _HasTable): + __tablename__ = 'role' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + name = Column(String(255), index=True, unique=True) + description = Column(TEXT) + type = Column(String(40), index=True) + deleted = Column(Boolean, index=True, default=False) + repositories = relationship('RepositoryRoleAssociation') # TODO add back_populates + groups = relationship('GroupRoleAssociation') # TODO ADD back_populates='role') + users = relationship('UserRoleAssociation') # TODO ADD back_populates='role') + dict_collection_visible_keys = ['id', 'name'] dict_element_visible_keys = ['id', 'name', 'description', 'type'] private_id = None diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 75b117589d2c..faf7f580ac8d 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -14,7 +14,7 @@ from galaxy.model.orm.engine_factory import build_engine from galaxy.model.orm.now import now from tool_shed.webapp.model import APIKeys, Category, ComponentReview -from tool_shed.webapp.model import GalaxySession, Group, GroupRoleAssociation +from tool_shed.webapp.model import GalaxySession, Group from tool_shed.webapp.model import mapper_registry from tool_shed.webapp.model import PasswordResetToken, Repository, RepositoryCategoryAssociation from tool_shed.webapp.model import RepositoryMetadata, RepositoryRatingAssociation @@ -38,15 +38,6 @@ Column("deleted", Boolean, index=True, default=False), Column("purged", Boolean, index=True, default=False)) -Role.table = Table("role", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("name", String(255), index=True, unique=True), - Column("description", TEXT), - Column("type", String(40), index=True), - Column("deleted", Boolean, index=True, default=False)) - UserGroupAssociation.table = Table("user_group_association", metadata, Column("id", Integer, primary_key=True), Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), @@ -152,15 +143,6 @@ reset_tokens=relation(PasswordResetToken, back_populates='user'), )) -mapper_registry.map_imperatively(Role, Role.table, - properties=dict( - repositories=relation(RepositoryRoleAssociation, - primaryjoin=((Role.table.c.id == RepositoryRoleAssociation.table.c.role_id) & (RepositoryRoleAssociation.table.c.repository_id == Repository.table.c.id))), - users=relation(UserRoleAssociation, - primaryjoin=((Role.table.c.id == UserRoleAssociation.table.c.role_id) & (UserRoleAssociation.table.c.user_id == User.table.c.id))), - groups=relation(GroupRoleAssociation, - primaryjoin=((Role.table.c.id == GroupRoleAssociation.role_id) & (GroupRoleAssociation.group_id == Group.id))))) - mapper_registry.map_imperatively(RepositoryRoleAssociation, RepositoryRoleAssociation.table, properties=dict( repository=relation(Repository), @@ -175,7 +157,7 @@ user=relation(User, backref="roles"), non_private_roles=relation(User, backref="non_private_roles", - primaryjoin=((User.table.c.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.table.c.id) & not_(Role.table.c.name == User.table.c.email))), + primaryjoin=((User.table.c.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.id) & not_(Role.name == User.table.c.email))), role=relation(Role))) mapper_registry.map_imperatively(Tag, Tag.table, diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index fc6cdc47b6bf..3c91857a466e 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -606,7 +606,7 @@ def test_relationships(self, session, cls_, repository, role): class TestRole(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'role' + assert cls_.__tablename__ == 'role' def test_columns(self, session, cls_): name, description, type_, deleted = get_unique_value(), 'b', cls_.types.SYSTEM, True From bd9a8054e3b70d2647afb252af2d2294bd122937 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 14:30:28 -0400 Subject: [PATCH 16/55] Map Tag declaratively --- lib/tool_shed/webapp/model/__init__.py | 14 +++++++++++++- lib/tool_shed/webapp/model/mapping.py | 16 +++------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 090ff1943cc5..02eab8c5bd38 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -15,6 +15,7 @@ Integer, String, TEXT, + UniqueConstraint, ) from sqlalchemy.orm import ( registry, @@ -565,7 +566,18 @@ def __init__(self, repository=None, category=None): self.category = category -class Tag(_HasTable): +class Tag(Base, _HasTable): + __tablename__ = 'tag' + __table_args__ = ( + UniqueConstraint('name'), + ) + + id = Column(Integer, primary_key=True) + type = Column(Integer) + parent_id = Column(Integer, ForeignKey('tag.id')) + name = Column(TrimmedString(255)) + children = relationship('Tag', back_populates='parent') + parent = relationship('Tag', back_populates='children', remote_side=[id]) def __init__(self, id=None, type=None, parent_id=None, name=None): self.id = id diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index faf7f580ac8d..5b9c479ba7e6 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -4,8 +4,8 @@ """ import logging -from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, not_, String, Table, TEXT, true, UniqueConstraint -from sqlalchemy.orm import backref, relation +from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, not_, String, Table, TEXT, true +from sqlalchemy.orm import relation import tool_shed.webapp.model import tool_shed.webapp.util.shed_statistics as shed_statistics @@ -19,7 +19,7 @@ from tool_shed.webapp.model import PasswordResetToken, Repository, RepositoryCategoryAssociation from tool_shed.webapp.model import RepositoryMetadata, RepositoryRatingAssociation from tool_shed.webapp.model import RepositoryReview, RepositoryRoleAssociation, Role -from tool_shed.webapp.model import Tag, User, UserGroupAssociation, UserRoleAssociation +from tool_shed.webapp.model import User, UserGroupAssociation, UserRoleAssociation from tool_shed.webapp.security import CommunityRBACAgent log = logging.getLogger(__name__) @@ -127,13 +127,6 @@ Column("description", TEXT), Column("deleted", Boolean, index=True, default=False)) -Tag.table = Table("tag", metadata, - Column("id", Integer, primary_key=True), - Column("type", Integer), - Column("parent_id", Integer, ForeignKey("tag.id")), - Column("name", TrimmedString(255)), - UniqueConstraint("name")) - # With the tables defined we can define the mappers and setup the relationships between the model objects. mapper_registry.map_imperatively(User, User.table, properties=dict( @@ -160,9 +153,6 @@ primaryjoin=((User.table.c.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.id) & not_(Role.name == User.table.c.email))), role=relation(Role))) -mapper_registry.map_imperatively(Tag, Tag.table, - properties=dict(children=relation(Tag, backref=backref('parent', remote_side=[Tag.table.c.id])))) - mapper_registry.map_imperatively(Category, Category.table, properties=dict(repositories=relation(RepositoryCategoryAssociation, secondary=Repository.table, diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 3c91857a466e..50ea3f0f2e2f 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -655,7 +655,7 @@ def test_relationships( class TestTag(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'tag' + assert cls_.__tablename__ == 'tag' assert has_unique_constraint(cls_.table, ('name',)) def test_columns(self, session, cls_): From 27d948c43b61f187287a196ba1859a9b564909c7 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 14:50:07 -0400 Subject: [PATCH 17/55] Map User declaratively --- lib/tool_shed/webapp/model/__init__.py | 28 ++++++++++++++++++- lib/tool_shed/webapp/model/mapping.py | 33 +++++------------------ test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 02eab8c5bd38..7ec342313bd6 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -11,8 +11,10 @@ Boolean, Column, DateTime, + desc, ForeignKey, Integer, + not_, String, TEXT, UniqueConstraint, @@ -75,7 +77,31 @@ class APIKeys(Base, _HasTable): key = Column(TrimmedString(32), index=True, unique=True) -class User(Dictifiable, _HasTable): +class User(Base, Dictifiable, _HasTable): + __tablename__ = 'galaxy_user' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + email = Column(TrimmedString(255), nullable=False) + username = Column(String(255), index=True) + password = Column(TrimmedString(40), nullable=False) + external = Column(Boolean, default=False) + new_repo_alert = Column(Boolean, default=False) + deleted = Column(Boolean, index=True, default=False) + purged = Column(Boolean, index=True, default=False) + active_repositories = relationship('Repository', + primaryjoin=(lambda: (Repository.user_id == User.id) & (not_(Repository.deleted))), # type: ignore + order_by=lambda: desc(Repository.name)) # type: ignore + galaxy_sessions = relationship('GalaxySession', + # back_populates='user', # TODO add back + order_by=lambda: desc(GalaxySession.update_time)) # type: ignore + api_keys = relationship( + 'APIKeys', + backref="user", # TODO >> back_populates + order_by=lambda: desc(APIKeys.create_time)) # type: ignore + reset_tokens = relationship('PasswordResetToken', back_populates='user') + dict_collection_visible_keys = ['id', 'username'] dict_element_visible_keys = ['id', 'username'] bootstrap_admin_user = False diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 5b9c479ba7e6..dd7971de697d 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -4,7 +4,7 @@ """ import logging -from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, not_, String, Table, TEXT, true +from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, not_, Table, TEXT, true from sqlalchemy.orm import relation import tool_shed.webapp.model @@ -13,10 +13,10 @@ from galaxy.model.custom_types import MutableJSONType, TrimmedString from galaxy.model.orm.engine_factory import build_engine from galaxy.model.orm.now import now -from tool_shed.webapp.model import APIKeys, Category, ComponentReview -from tool_shed.webapp.model import GalaxySession, Group +from tool_shed.webapp.model import Category, ComponentReview +from tool_shed.webapp.model import Group from tool_shed.webapp.model import mapper_registry -from tool_shed.webapp.model import PasswordResetToken, Repository, RepositoryCategoryAssociation +from tool_shed.webapp.model import Repository, RepositoryCategoryAssociation from tool_shed.webapp.model import RepositoryMetadata, RepositoryRatingAssociation from tool_shed.webapp.model import RepositoryReview, RepositoryRoleAssociation, Role from tool_shed.webapp.model import User, UserGroupAssociation, UserRoleAssociation @@ -26,18 +26,6 @@ metadata = mapper_registry.metadata -User.table = Table("galaxy_user", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("email", TrimmedString(255), nullable=False), - Column("username", String(255), index=True), - Column("password", TrimmedString(40), nullable=False), - Column("external", Boolean, default=False), - Column("new_repo_alert", Boolean, default=False), - Column("deleted", Boolean, index=True, default=False), - Column("purged", Boolean, index=True, default=False)) - UserGroupAssociation.table = Table("user_group_association", metadata, Column("id", Integer, primary_key=True), Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), @@ -127,15 +115,6 @@ Column("description", TEXT), Column("deleted", Boolean, index=True, default=False)) -# With the tables defined we can define the mappers and setup the relationships between the model objects. -mapper_registry.map_imperatively(User, User.table, - properties=dict( - active_repositories=relation(Repository, primaryjoin=((Repository.table.c.user_id == User.table.c.id) & (not_(Repository.table.c.deleted))), order_by=(Repository.table.c.name)), - galaxy_sessions=relation(GalaxySession, order_by=desc(GalaxySession.update_time)), - api_keys=relation(APIKeys, backref="user", order_by=desc(APIKeys.create_time)), - reset_tokens=relation(PasswordResetToken, back_populates='user'), - )) - mapper_registry.map_imperatively(RepositoryRoleAssociation, RepositoryRoleAssociation.table, properties=dict( repository=relation(Repository), @@ -150,7 +129,7 @@ user=relation(User, backref="roles"), non_private_roles=relation(User, backref="non_private_roles", - primaryjoin=((User.table.c.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.id) & not_(Role.name == User.table.c.email))), + primaryjoin=((User.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.id) & not_(Role.name == User.email))), role=relation(Role))) mapper_registry.map_imperatively(Category, Category.table, @@ -175,7 +154,7 @@ reviewers=relation(User, secondary=RepositoryReview.table, primaryjoin=(Repository.table.c.id == RepositoryReview.table.c.repository_id), - secondaryjoin=(RepositoryReview.table.c.user_id == User.table.c.id)))) + secondaryjoin=(RepositoryReview.table.c.user_id == User.id)))) mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict(repository=relation(Repository), diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 50ea3f0f2e2f..f2a523f90af8 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -695,7 +695,7 @@ def add_association(assoc_object, assoc_attribute): class TestUser(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'galaxy_user' + assert cls_.__tablename__ == 'galaxy_user' def test_columns(self, session, cls_): create_time = datetime.now() From c998681f3109238f1a458114b5939d53cea889a1 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 14:55:45 -0400 Subject: [PATCH 18/55] Map RepositoryCategoryAssociation declaratively --- lib/tool_shed/webapp/model/__init__.py | 10 +++++++++- lib/tool_shed/webapp/model/mapping.py | 14 ++------------ test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 7ec342313bd6..807fd53659b3 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -585,7 +585,15 @@ def __init__(self, name=None, description=None, deleted=False): self.deleted = deleted -class RepositoryCategoryAssociation(_HasTable): +class RepositoryCategoryAssociation(Base, _HasTable): + __tablename__ = 'repository_category_association' + + id = Column(Integer, primary_key=True) + repository_id = Column(Integer, ForeignKey('repository.id'), index=True) + category_id = Column(Integer, ForeignKey('category.id'), index=True) + # TODO add back_populates + category = relationship('Category') + repository = relationship('Repository') def __init__(self, repository=None, category=None): self.repository = repository diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index dd7971de697d..2cc28366ed88 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -102,11 +102,6 @@ Column("rating", Integer, index=True), Column("comment", TEXT)) -RepositoryCategoryAssociation.table = Table("repository_category_association", metadata, - Column("id", Integer, primary_key=True), - Column("repository_id", Integer, ForeignKey("repository.id"), index=True), - Column("category_id", Integer, ForeignKey("category.id"), index=True)) - Category.table = Table("category", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -135,8 +130,8 @@ mapper_registry.map_imperatively(Category, Category.table, properties=dict(repositories=relation(RepositoryCategoryAssociation, secondary=Repository.table, - primaryjoin=(Category.table.c.id == RepositoryCategoryAssociation.table.c.category_id), - secondaryjoin=(RepositoryCategoryAssociation.table.c.repository_id == Repository.table.c.id)))) + primaryjoin=(Category.table.c.id == RepositoryCategoryAssociation.category_id), + secondaryjoin=(RepositoryCategoryAssociation.repository_id == Repository.table.c.id)))) mapper_registry.map_imperatively(Repository, Repository.table, properties=dict( @@ -181,11 +176,6 @@ mapper_registry.map_imperatively(RepositoryRatingAssociation, RepositoryRatingAssociation.table, properties=dict(repository=relation(Repository), user=relation(User))) -mapper_registry.map_imperatively(RepositoryCategoryAssociation, RepositoryCategoryAssociation.table, - properties=dict( - category=relation(Category), - repository=relation(Repository))) - class ToolShedModelMapping(SharedModelMapping): security_agent: CommunityRBACAgent diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index f2a523f90af8..fcfc119bf47f 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -388,7 +388,7 @@ def test_relationships( class TestRepositoryCategoryAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'repository_category_association' + assert cls_.__tablename__ == 'repository_category_association' def test_columns(self, session, cls_, repository, category): obj = cls_(repository=repository, category=category) From 9f07b8cd71d5c50875ff78839135277005c16470 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 17:08:15 -0400 Subject: [PATCH 19/55] Drop unused model stubs --- lib/tool_shed/webapp/model/__init__.py | 87 -------------------------- 1 file changed, 87 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 807fd53659b3..5a22dbf1ad52 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -623,93 +623,6 @@ def __str__(self): return "Tag(id=%s, type=%i, parent_id=%s, name=%s)" % (self.id, self.type, self.parent_id, self.name) -class ItemTagAssociation(_HasTable): - - def __init__(self, id=None, user=None, item_id=None, tag_id=None, user_tname=None, value=None): - self.id = id - self.user = user - self.item_id = item_id - self.tag_id = tag_id - self.user_tname = user_tname - self.value = None - self.user_value = None - - -class PostJobAction(_HasTable): - - def __init__(self, action_type, workflow_step, output_name=None, action_arguments=None): - self.action_type = action_type - self.output_name = output_name - self.action_arguments = action_arguments - self.workflow_step = workflow_step - - -class StoredWorkflowAnnotationAssociation(_HasTable): - pass - - -class WorkflowStepAnnotationAssociation(_HasTable): - pass - - -class Workflow(_HasTable): - - def __init__(self): - self.user = None - self.name = None - self.has_cycles = None - self.has_errors = None - self.steps = [] - - -class WorkflowStep(_HasTable): - - def __init__(self): - self.id = None - self.type = None - self.name = None - self.tool_id = None - self.tool_inputs = None - self.tool_errors = None - self.position = None - self.inputs = [] - self.config = None - self.label = None - - def get_or_add_input(self, input_name): - for step_input in self.inputs: - if step_input.name == input_name: - return step_input - - step_input = WorkflowStepInput() - step_input.workflow_step = self - step_input.name = input_name - self.inputs.append(step_input) - return step_input - - @property - def input_connections(self): - connections = [_ for step_input in self.inputs for _ in step_input.connections] - return connections - - -class WorkflowStepInput(_HasTable): - - def __init__(self): - self.id = None - self.name = None - self.connections = [] - - -class WorkflowStepConnection: - - def __init__(self): - self.output_step = None - self.output_name = None - self.input_step = None - self.input_name = None - - # Utility methods def sort_by_attr(seq, attr): """ From 31af81dad7b9eddfe7ab3f1dca0afb26fb6952e0 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 17:25:26 -0400 Subject: [PATCH 20/55] Map RepositoryRatingAssociation declaratively --- lib/tool_shed/webapp/model/__init__.py | 13 ++++++++++++- lib/tool_shed/webapp/model/mapping.py | 14 +------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 5a22dbf1ad52..f44fc390590e 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -569,7 +569,18 @@ def set_item(self, item): """ Set association's item. """ -class RepositoryRatingAssociation(ItemRatingAssociation, _HasTable): +class RepositoryRatingAssociation(Base, ItemRatingAssociation, _HasTable): + __tablename__ = 'repository_rating_association' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + repository_id = Column(Integer, ForeignKey("repository.id"), index=True) + user_id = Column(Integer, ForeignKey("galaxy_user.id"), index=True) + rating = Column(Integer, index=True) + comment = Column(TEXT) + repository = relationship('Repository') # TODO add back + user = relationship('User') # TODO add back def set_item(self, repository): self.repository = repository diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 2cc28366ed88..9aa45923d4c0 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -93,15 +93,6 @@ Column("rating", Integer, index=True), Column("deleted", Boolean, index=True, default=False)) -RepositoryRatingAssociation.table = Table("repository_rating_association", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("repository_id", Integer, ForeignKey("repository.id"), index=True), - Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), - Column("rating", Integer, index=True), - Column("comment", TEXT)) - Category.table = Table("category", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -136,7 +127,7 @@ mapper_registry.map_imperatively(Repository, Repository.table, properties=dict( categories=relation(RepositoryCategoryAssociation), - ratings=relation(RepositoryRatingAssociation, order_by=desc(RepositoryRatingAssociation.table.c.update_time), backref="repositories"), + ratings=relation(RepositoryRatingAssociation, order_by=desc(RepositoryRatingAssociation.update_time), backref="repositories"), user=relation(User), downloadable_revisions=relation(RepositoryMetadata, primaryjoin=((Repository.table.c.id == RepositoryMetadata.table.c.repository_id) & (RepositoryMetadata.table.c.downloadable == true())), @@ -173,9 +164,6 @@ private_component_reviews=relation(ComponentReview, primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false()) & (ComponentReview.private == true()))))) -mapper_registry.map_imperatively(RepositoryRatingAssociation, RepositoryRatingAssociation.table, - properties=dict(repository=relation(Repository), user=relation(User))) - class ToolShedModelMapping(SharedModelMapping): security_agent: CommunityRBACAgent diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index fcfc119bf47f..fb791a2debc3 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -478,7 +478,7 @@ def test_relationships(self, session, cls_, repository, repository_review): class TestRepositoryRatingAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'repository_rating_association' + assert cls_.__tablename__ == 'repository_rating_association' def test_columns(self, session, cls_, repository, user): create_time = datetime.now() From f824ed200ad63b1ed98ac7eec8ab54fef5acdbdd Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 17:35:51 -0400 Subject: [PATCH 21/55] Fix SAWarning: add back_populates for Repository/User --- lib/tool_shed/webapp/model/mapping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 9aa45923d4c0..45c3e23d051d 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -128,7 +128,7 @@ properties=dict( categories=relation(RepositoryCategoryAssociation), ratings=relation(RepositoryRatingAssociation, order_by=desc(RepositoryRatingAssociation.update_time), backref="repositories"), - user=relation(User), + user=relation(User, back_populates='active_repositories'), downloadable_revisions=relation(RepositoryMetadata, primaryjoin=((Repository.table.c.id == RepositoryMetadata.table.c.repository_id) & (RepositoryMetadata.table.c.downloadable == true())), order_by=desc(RepositoryMetadata.table.c.update_time)), From 7ddb27779ec3761cf01a4934ada92f6a29b0fa21 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 17:41:55 -0400 Subject: [PATCH 22/55] Add back_populates to User/GalaxySession --- lib/tool_shed/webapp/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index f44fc390590e..c9ba91327763 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -94,7 +94,7 @@ class User(Base, Dictifiable, _HasTable): primaryjoin=(lambda: (Repository.user_id == User.id) & (not_(Repository.deleted))), # type: ignore order_by=lambda: desc(Repository.name)) # type: ignore galaxy_sessions = relationship('GalaxySession', - # back_populates='user', # TODO add back + back_populates='user', order_by=lambda: desc(GalaxySession.update_time)) # type: ignore api_keys = relationship( 'APIKeys', From ddf1135edae9cd0519aa60519be6065c86eb3c62 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 17:44:23 -0400 Subject: [PATCH 23/55] Replace backref w/back_populates for User/APIKeys --- lib/tool_shed/webapp/model/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index c9ba91327763..eb2127a3668a 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -75,6 +75,7 @@ class APIKeys(Base, _HasTable): create_time = Column(DateTime, default=now) user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) key = Column(TrimmedString(32), index=True, unique=True) + user = relationship('User', back_populates='api_keys') class User(Base, Dictifiable, _HasTable): @@ -98,7 +99,7 @@ class User(Base, Dictifiable, _HasTable): order_by=lambda: desc(GalaxySession.update_time)) # type: ignore api_keys = relationship( 'APIKeys', - backref="user", # TODO >> back_populates + back_populates='user', order_by=lambda: desc(APIKeys.create_time)) # type: ignore reset_tokens = relationship('PasswordResetToken', back_populates='user') From 500dc77bb3ccbc35aa8ddb06b46009b0a6b50fb9 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 17:56:16 -0400 Subject: [PATCH 24/55] Fix bug on UserGroupAssoc mapping (see note) This is the same bug as the one fixed on the main model in https://github.com/galaxyproject/galaxy/commit/174d8b53ac4d48ac6950c579539537da42a6a2e8 --- lib/tool_shed/grids/admin_grids.py | 4 ++-- lib/tool_shed/webapp/model/__init__.py | 2 +- lib/tool_shed/webapp/model/mapping.py | 2 +- lib/tool_shed/webapp/security/__init__.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/tool_shed/grids/admin_grids.py b/lib/tool_shed/grids/admin_grids.py index e686ed16eedd..8e3ac87665ec 100644 --- a/lib/tool_shed/grids/admin_grids.py +++ b/lib/tool_shed/grids/admin_grids.py @@ -259,8 +259,8 @@ def get_value(self, trans, grid, group): class UsersColumn(grids.GridColumn): def get_value(self, trans, grid, group): - if group.members: - return len(group.members) + if group.users: + return len(group.users) return 0 title = "Groups" diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index eb2127a3668a..8981754636d0 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -174,7 +174,7 @@ class Group(Base, Dictifiable, _HasTable): name = Column(String(255), index=True, unique=True) deleted = Column(Boolean, index=True, default=False) roles = relationship('GroupRoleAssociation', back_populates='group') - users = relationship('UserGroupAssociation') # TODO: fix bug: incorrect usage of backref/duplicate rel users/members; add back_populates + users = relationship('UserGroupAssociation', back_populates='group') dict_collection_visible_keys = ['id', 'name'] dict_element_visible_keys = ['id', 'name'] diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 45c3e23d051d..e5e3492081fd 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -108,7 +108,7 @@ mapper_registry.map_imperatively(UserGroupAssociation, UserGroupAssociation.table, properties=dict(user=relation(User, backref="groups"), - group=relation(Group, backref="members"))) # TODO fix bug: members should be users; check codebase for references + group=relation(Group, back_populates="users"))) mapper_registry.map_imperatively(UserRoleAssociation, UserRoleAssociation.table, properties=dict( diff --git a/lib/tool_shed/webapp/security/__init__.py b/lib/tool_shed/webapp/security/__init__.py index f794ffad19b5..c5ba9486aa5d 100644 --- a/lib/tool_shed/webapp/security/__init__.py +++ b/lib/tool_shed/webapp/security/__init__.py @@ -234,7 +234,7 @@ def user_can_administer_repository(self, user, repository): # of a group that is associated with the role. for gra in role.groups: group = gra.group - for uga in group.members: + for uga in group.users: member = uga.user if member.id == user.id: return True From 5773c80ce4fca45311c80e81d50f4407775c2d75 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 18:04:04 -0400 Subject: [PATCH 25/55] Map UserGroupAssociation declaratively --- lib/tool_shed/webapp/model/__init__.py | 13 ++++++++++++- lib/tool_shed/webapp/model/mapping.py | 14 +------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 8981754636d0..a645fe3a83f4 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -102,6 +102,7 @@ class User(Base, Dictifiable, _HasTable): back_populates='user', order_by=lambda: desc(APIKeys.create_time)) # type: ignore reset_tokens = relationship('PasswordResetToken', back_populates='user') + groups = relationship('UserGroupAssociation', back_populates='user') dict_collection_visible_keys = ['id', 'username'] dict_element_visible_keys = ['id', 'username'] @@ -223,7 +224,17 @@ def is_repository_admin_role(self): return False -class UserGroupAssociation(_HasTable): +class UserGroupAssociation(Base, _HasTable): + __tablename__ = 'user_group_association' + + id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) + group_id = Column(Integer, ForeignKey('galaxy_group.id'), index=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + user = relationship('User', back_populates='groups') + group = relationship('Group', back_populates='users') + def __init__(self, user, group): self.user = user self.group = group diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index e5e3492081fd..6db0bf58b8dc 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -14,25 +14,17 @@ from galaxy.model.orm.engine_factory import build_engine from galaxy.model.orm.now import now from tool_shed.webapp.model import Category, ComponentReview -from tool_shed.webapp.model import Group from tool_shed.webapp.model import mapper_registry from tool_shed.webapp.model import Repository, RepositoryCategoryAssociation from tool_shed.webapp.model import RepositoryMetadata, RepositoryRatingAssociation from tool_shed.webapp.model import RepositoryReview, RepositoryRoleAssociation, Role -from tool_shed.webapp.model import User, UserGroupAssociation, UserRoleAssociation +from tool_shed.webapp.model import User, UserRoleAssociation from tool_shed.webapp.security import CommunityRBACAgent log = logging.getLogger(__name__) metadata = mapper_registry.metadata -UserGroupAssociation.table = Table("user_group_association", metadata, - Column("id", Integer, primary_key=True), - Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), - Column("group_id", Integer, ForeignKey("galaxy_group.id"), index=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now)) - UserRoleAssociation.table = Table("user_role_association", metadata, Column("id", Integer, primary_key=True), Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), @@ -106,10 +98,6 @@ repository=relation(Repository), role=relation(Role))) -mapper_registry.map_imperatively(UserGroupAssociation, UserGroupAssociation.table, - properties=dict(user=relation(User, backref="groups"), - group=relation(Group, back_populates="users"))) - mapper_registry.map_imperatively(UserRoleAssociation, UserRoleAssociation.table, properties=dict( user=relation(User, backref="roles"), diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index fb791a2debc3..9d5c1e3f48da 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -771,7 +771,7 @@ def test_relationships( class TestUserGroupAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'user_group_association' + assert cls_.__tablename__ == 'user_group_association' def test_columns(self, session, cls_, user, group): create_time = datetime.now() From de45aacc0833db1440a8e85010718323baae5b09 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 18:08:10 -0400 Subject: [PATCH 26/55] Fix SAWarning: add back_populates for Role/RepositoryRoleAssoc --- lib/tool_shed/webapp/model/__init__.py | 2 +- lib/tool_shed/webapp/model/mapping.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index a645fe3a83f4..965065c27603 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -195,7 +195,7 @@ class Role(Base, Dictifiable, _HasTable): description = Column(TEXT) type = Column(String(40), index=True) deleted = Column(Boolean, index=True, default=False) - repositories = relationship('RepositoryRoleAssociation') # TODO add back_populates + repositories = relationship('RepositoryRoleAssociation', back_populates='role') groups = relationship('GroupRoleAssociation') # TODO ADD back_populates='role') users = relationship('UserRoleAssociation') # TODO ADD back_populates='role') diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 6db0bf58b8dc..2a775a716f72 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -96,7 +96,7 @@ mapper_registry.map_imperatively(RepositoryRoleAssociation, RepositoryRoleAssociation.table, properties=dict( repository=relation(Repository), - role=relation(Role))) + role=relation(Role, back_populates='repositories'))) mapper_registry.map_imperatively(UserRoleAssociation, UserRoleAssociation.table, properties=dict( From e6d4f0aec506311301da4b92ba076d702b466b3c Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 18:10:04 -0400 Subject: [PATCH 27/55] Fix SAWarning: add back_populates for Role/GroupRoleAssoc --- lib/tool_shed/webapp/model/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 965065c27603..92ee68f1a03e 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -196,7 +196,7 @@ class Role(Base, Dictifiable, _HasTable): type = Column(String(40), index=True) deleted = Column(Boolean, index=True, default=False) repositories = relationship('RepositoryRoleAssociation', back_populates='role') - groups = relationship('GroupRoleAssociation') # TODO ADD back_populates='role') + groups = relationship('GroupRoleAssociation', back_populates='role') users = relationship('UserRoleAssociation') # TODO ADD back_populates='role') dict_collection_visible_keys = ['id', 'name'] @@ -255,7 +255,7 @@ class GroupRoleAssociation(Base, _HasTable): create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) group = relationship('Group', back_populates='roles') - role = relationship('Role') # TODO add back_populates='groups' + role = relationship('Role', back_populates='groups') def __init__(self, group, role): self.group = group From 9cb6e7fd02c480cae2e5627c50b589e5cd8b251a Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 19:18:42 -0400 Subject: [PATCH 28/55] Add back_populates for User/Repository --- lib/tool_shed/webapp/model/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 92ee68f1a03e..a6bccef95999 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -93,6 +93,7 @@ class User(Base, Dictifiable, _HasTable): purged = Column(Boolean, index=True, default=False) active_repositories = relationship('Repository', primaryjoin=(lambda: (Repository.user_id == User.id) & (not_(Repository.deleted))), # type: ignore + back_populates='user', order_by=lambda: desc(Repository.name)) # type: ignore galaxy_sessions = relationship('GalaxySession', back_populates='user', From 9632bc42a87e78d5af89e35ac64d7b5b947586bb Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 19:20:44 -0400 Subject: [PATCH 29/55] Fix SAWarning; add back_populates for Role/UserRoleAssoc --- lib/tool_shed/webapp/model/__init__.py | 2 +- lib/tool_shed/webapp/model/mapping.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index a6bccef95999..3a9f82a19c65 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -198,7 +198,7 @@ class Role(Base, Dictifiable, _HasTable): deleted = Column(Boolean, index=True, default=False) repositories = relationship('RepositoryRoleAssociation', back_populates='role') groups = relationship('GroupRoleAssociation', back_populates='role') - users = relationship('UserRoleAssociation') # TODO ADD back_populates='role') + users = relationship('UserRoleAssociation', back_populates='role') dict_collection_visible_keys = ['id', 'name'] dict_element_visible_keys = ['id', 'name', 'description', 'type'] diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 2a775a716f72..bf11dcac37d3 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -104,7 +104,7 @@ non_private_roles=relation(User, backref="non_private_roles", primaryjoin=((User.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.id) & not_(Role.name == User.email))), - role=relation(Role))) + role=relation(Role, back_populates='users'))) mapper_registry.map_imperatively(Category, Category.table, properties=dict(repositories=relation(RepositoryCategoryAssociation, From cd5c04f135ccbd952e7f103e596baf4e936b9924 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 19:26:27 -0400 Subject: [PATCH 30/55] Fix SAWarning: add back_populates for Component/RepositoryReview --- lib/tool_shed/webapp/model/__init__.py | 3 +-- lib/tool_shed/webapp/model/mapping.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 3a9f82a19c65..02f973930228 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -539,8 +539,7 @@ class ComponentReview(Base, Dictifiable, _HasTable): approved = Column(TrimmedString(255)) rating = Column(Integer) deleted = Column(Boolean, index=True, default=False) - - repository_review = relationship('RepositoryReview') + repository_review = relationship('RepositoryReview', back_populates='component_reviews') component = relationship('Component') dict_collection_visible_keys = ['id', 'repository_review_id', 'component_id', 'private', 'approved', 'rating', 'deleted'] diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index bf11dcac37d3..cc623522ea88 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -148,7 +148,8 @@ backref='review'), user=relation(User, backref="repository_reviews"), component_reviews=relation(ComponentReview, - primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false()))), + primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false())), + back_populates='repository_review'), private_component_reviews=relation(ComponentReview, primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false()) & (ComponentReview.private == true()))))) From 2579fe551f0c92d4db9f8e9e6e758da7c5e84baa Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 19:30:55 -0400 Subject: [PATCH 31/55] Fix 2 SAWArnings, incorrect attribute naming - Add back_populates for RepositoryRatingAssoc/Repository - Rename backref to repository (it's singular, not plural, because it's the 'one' side of a one-to-many betwee assoc and Repository - Replace backref with back_populates --- lib/tool_shed/webapp/model/__init__.py | 2 +- lib/tool_shed/webapp/model/mapping.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 02f973930228..4a24720a7858 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -591,7 +591,7 @@ class RepositoryRatingAssociation(Base, ItemRatingAssociation, _HasTable): user_id = Column(Integer, ForeignKey("galaxy_user.id"), index=True) rating = Column(Integer, index=True) comment = Column(TEXT) - repository = relationship('Repository') # TODO add back + repository = relationship('Repository', back_populates='ratings') user = relationship('User') # TODO add back def set_item(self, repository): diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index cc623522ea88..2a24044bc176 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -115,7 +115,7 @@ mapper_registry.map_imperatively(Repository, Repository.table, properties=dict( categories=relation(RepositoryCategoryAssociation), - ratings=relation(RepositoryRatingAssociation, order_by=desc(RepositoryRatingAssociation.update_time), backref="repositories"), + ratings=relation(RepositoryRatingAssociation, order_by=desc(RepositoryRatingAssociation.update_time), back_populates='repository'), user=relation(User, back_populates='active_repositories'), downloadable_revisions=relation(RepositoryMetadata, primaryjoin=((Repository.table.c.id == RepositoryMetadata.table.c.repository_id) & (RepositoryMetadata.table.c.downloadable == true())), From 1eabf712cbefc92adc9f86d3f012335ea31fd4d4 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 19:40:13 -0400 Subject: [PATCH 32/55] Fix SAWarning: add back_populates for RepositoryCategoryAssoc/Category --- lib/tool_shed/webapp/model/__init__.py | 4 ++-- lib/tool_shed/webapp/model/mapping.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 4a24720a7858..3952105ef0fd 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -592,7 +592,7 @@ class RepositoryRatingAssociation(Base, ItemRatingAssociation, _HasTable): rating = Column(Integer, index=True) comment = Column(TEXT) repository = relationship('Repository', back_populates='ratings') - user = relationship('User') # TODO add back + user = relationship('User') def set_item(self, repository): self.repository = repository @@ -615,7 +615,7 @@ class RepositoryCategoryAssociation(Base, _HasTable): repository_id = Column(Integer, ForeignKey('repository.id'), index=True) category_id = Column(Integer, ForeignKey('category.id'), index=True) # TODO add back_populates - category = relationship('Category') + category = relationship('Category', back_populates='repositories') repository = relationship('Repository') def __init__(self, repository=None, category=None): diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 2a24044bc176..50884d8617ad 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -110,7 +110,8 @@ properties=dict(repositories=relation(RepositoryCategoryAssociation, secondary=Repository.table, primaryjoin=(Category.table.c.id == RepositoryCategoryAssociation.category_id), - secondaryjoin=(RepositoryCategoryAssociation.repository_id == Repository.table.c.id)))) + secondaryjoin=(RepositoryCategoryAssociation.repository_id == Repository.table.c.id), + back_populates='category'))) mapper_registry.map_imperatively(Repository, Repository.table, properties=dict( From b35d042540ca40e8f33f1745b0f64be04afdffaa Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 19:43:08 -0400 Subject: [PATCH 33/55] Add back_populates for RepositoryCategoryAssoc/Repository --- lib/tool_shed/webapp/model/__init__.py | 2 +- lib/tool_shed/webapp/model/mapping.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 3952105ef0fd..d6185ec050c9 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -616,7 +616,7 @@ class RepositoryCategoryAssociation(Base, _HasTable): category_id = Column(Integer, ForeignKey('category.id'), index=True) # TODO add back_populates category = relationship('Category', back_populates='repositories') - repository = relationship('Repository') + repository = relationship('Repository', back_populates='categories') def __init__(self, repository=None, category=None): self.repository = repository diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 50884d8617ad..f011a9121881 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -115,7 +115,7 @@ mapper_registry.map_imperatively(Repository, Repository.table, properties=dict( - categories=relation(RepositoryCategoryAssociation), + categories=relation(RepositoryCategoryAssociation, back_populates='repository'), ratings=relation(RepositoryRatingAssociation, order_by=desc(RepositoryRatingAssociation.update_time), back_populates='repository'), user=relation(User, back_populates='active_repositories'), downloadable_revisions=relation(RepositoryMetadata, From b2170fabc90ee46040d00134790a7e73c4040cd5 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 19:53:12 -0400 Subject: [PATCH 34/55] Fix bug, 2 SAWarnings (see note); add test The mapping between Category and RepositoryCategoryAssociation doesn't have to use primaryjoin, secondaryjoin + secondary relationship attributes. This triggered at least 2 warnings; also the mapping itself did not work correctly: the test demonstrated that assigning a category to a repository was not persisted. Fixing the problem fixed the test too. --- lib/tool_shed/webapp/model/__init__.py | 1 - lib/tool_shed/webapp/model/mapping.py | 3 --- test/unit/shed_unit/model/test_mapping.py | 8 +++++++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index d6185ec050c9..e912b0bc6e5e 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -614,7 +614,6 @@ class RepositoryCategoryAssociation(Base, _HasTable): id = Column(Integer, primary_key=True) repository_id = Column(Integer, ForeignKey('repository.id'), index=True) category_id = Column(Integer, ForeignKey('category.id'), index=True) - # TODO add back_populates category = relationship('Category', back_populates='repositories') repository = relationship('Repository', back_populates='categories') diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index f011a9121881..e130007c8859 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -108,9 +108,6 @@ mapper_registry.map_imperatively(Category, Category.table, properties=dict(repositories=relation(RepositoryCategoryAssociation, - secondary=Repository.table, - primaryjoin=(Category.table.c.id == RepositoryCategoryAssociation.category_id), - secondaryjoin=(RepositoryCategoryAssociation.repository_id == Repository.table.c.id), back_populates='category'))) mapper_registry.map_imperatively(Repository, Repository.table, diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 9d5c1e3f48da..e24e535c1a47 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -76,7 +76,13 @@ def test_columns(self, session, cls_): assert stored_obj.deleted == deleted def test_relationships(self, session, cls_, repository_category_association): - pass # TODO: might be broken + obj = cls_() + obj.name = get_unique_value() + obj.repositories.append(repository_category_association) + + with dbcleanup(session, obj) as obj_id: + stored_obj = get_stored_obj(session, cls_, obj_id) + assert stored_obj.repositories == [repository_category_association] class TestComponent(BaseTest): From a5ca55d98e6c6850fc363574ffb0dd6a01f36af8 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 20:12:23 -0400 Subject: [PATCH 35/55] Map Category declaratively --- lib/tool_shed/webapp/model/__init__.py | 12 +++++++++++- lib/tool_shed/webapp/model/mapping.py | 14 +------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index e912b0bc6e5e..e197d6def46d 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -598,7 +598,17 @@ def set_item(self, repository): self.repository = repository -class Category(Dictifiable, _HasTable): +class Category(Base, Dictifiable, _HasTable): + __tablename__ = 'category' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + name = Column(TrimmedString(255), index=True, unique=True) + description = Column(TEXT) + deleted = Column(Boolean, index=True, default=False) + repositories = relationship('RepositoryCategoryAssociation', back_populates='category') + dict_collection_visible_keys = ['id', 'name', 'description', 'deleted'] dict_element_visible_keys = ['id', 'name', 'description', 'deleted'] diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index e130007c8859..c8e1ef4ff905 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -13,7 +13,7 @@ from galaxy.model.custom_types import MutableJSONType, TrimmedString from galaxy.model.orm.engine_factory import build_engine from galaxy.model.orm.now import now -from tool_shed.webapp.model import Category, ComponentReview +from tool_shed.webapp.model import ComponentReview from tool_shed.webapp.model import mapper_registry from tool_shed.webapp.model import Repository, RepositoryCategoryAssociation from tool_shed.webapp.model import RepositoryMetadata, RepositoryRatingAssociation @@ -85,14 +85,6 @@ Column("rating", Integer, index=True), Column("deleted", Boolean, index=True, default=False)) -Category.table = Table("category", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("name", TrimmedString(255), index=True, unique=True), - Column("description", TEXT), - Column("deleted", Boolean, index=True, default=False)) - mapper_registry.map_imperatively(RepositoryRoleAssociation, RepositoryRoleAssociation.table, properties=dict( repository=relation(Repository), @@ -106,10 +98,6 @@ primaryjoin=((User.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.id) & not_(Role.name == User.email))), role=relation(Role, back_populates='users'))) -mapper_registry.map_imperatively(Category, Category.table, - properties=dict(repositories=relation(RepositoryCategoryAssociation, - back_populates='category'))) - mapper_registry.map_imperatively(Repository, Repository.table, properties=dict( categories=relation(RepositoryCategoryAssociation, back_populates='repository'), diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index e24e535c1a47..bca3ee53fb1e 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -52,7 +52,7 @@ def test_relationships(self, session, cls_, user): class TestCategory(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'category' + assert cls_.__tablename__ == 'category' def test_columns(self, session, cls_): create_time = datetime.now() From f943da435fdfb570eb001fa3e9d3550207d93013 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 20:18:35 -0400 Subject: [PATCH 36/55] Map RepositoryRoleAssoc; fix SAWarning (back_populates) --- lib/tool_shed/webapp/model/__init__.py | 12 +++++++++++- lib/tool_shed/webapp/model/mapping.py | 14 +------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index e197d6def46d..7530718534eb 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -263,7 +263,17 @@ def __init__(self, group, role): self.role = role -class RepositoryRoleAssociation(_HasTable): +class RepositoryRoleAssociation(Base, _HasTable): + __tablename__ = 'repository_role_association' + + id = Column(Integer, primary_key=True) + repository_id = Column(Integer, ForeignKey("repository.id"), index=True) + role_id = Column(Integer, ForeignKey("role.id"), index=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + repository = relationship('Repository', back_populates='roles') + role = relationship('Role', back_populates='repositories') + def __init__(self, repository, role): self.repository = repository self.role = role diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index c8e1ef4ff905..634d7f298c58 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -32,13 +32,6 @@ Column("create_time", DateTime, default=now), Column("update_time", DateTime, default=now, onupdate=now)) -RepositoryRoleAssociation.table = Table("repository_role_association", metadata, - Column("id", Integer, primary_key=True), - Column("repository_id", Integer, ForeignKey("repository.id"), index=True), - Column("role_id", Integer, ForeignKey("role.id"), index=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now)) - Repository.table = Table("repository", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -85,11 +78,6 @@ Column("rating", Integer, index=True), Column("deleted", Boolean, index=True, default=False)) -mapper_registry.map_imperatively(RepositoryRoleAssociation, RepositoryRoleAssociation.table, - properties=dict( - repository=relation(Repository), - role=relation(Role, back_populates='repositories'))) - mapper_registry.map_imperatively(UserRoleAssociation, UserRoleAssociation.table, properties=dict( user=relation(User, backref="roles"), @@ -108,7 +96,7 @@ order_by=desc(RepositoryMetadata.table.c.update_time)), metadata_revisions=relation(RepositoryMetadata, order_by=desc(RepositoryMetadata.table.c.update_time)), - roles=relation(RepositoryRoleAssociation), + roles=relation(RepositoryRoleAssociation, back_populates='repository'), reviews=relation(RepositoryReview, primaryjoin=(Repository.table.c.id == RepositoryReview.table.c.repository_id)), reviewers=relation(User, diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index bca3ee53fb1e..fc2902eec9c0 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -583,7 +583,7 @@ def test_relationships(self, session, cls_, repository, user, repository_metadat class TestRepositoryRoleAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'repository_role_association' + assert cls_.__tablename__ == 'repository_role_association' def test_columns(self, session, cls_, repository, role): create_time = datetime.now() From d54697a006e81e478c8a932f51f4a07d0e838fca Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 20:33:47 -0400 Subject: [PATCH 37/55] Fix bug, 2 SAWarnings for non_private_roles (see note) Same bug as in: https://github.com/galaxyproject/galaxy/commit/143bb98c26d58e51f12b6bf5f0361e976f6b1853 https://github.com/galaxyproject/galaxy/commit/285bb51cad66b76bb32cbb8046e64ef04b62ff02 --- lib/tool_shed/webapp/model/__init__.py | 8 +++++++ lib/tool_shed/webapp/model/mapping.py | 5 +--- test/unit/shed_unit/model/test_mapping.py | 28 +++++++++++++++++++++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 7530718534eb..af6aa8e39536 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -108,6 +108,14 @@ class User(Base, Dictifiable, _HasTable): dict_collection_visible_keys = ['id', 'username'] dict_element_visible_keys = ['id', 'username'] bootstrap_admin_user = False + non_private_roles = relationship( + 'UserRoleAssociation', + viewonly=True, + primaryjoin=(lambda: + (User.id == UserRoleAssociation.user_id) # type: ignore + & (UserRoleAssociation.role_id == Role.id) # type: ignore + & not_(Role.name == User.email)) # type: ignore + ) def __init__(self, email=None, password=None): self.email = email diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 634d7f298c58..643c74b2751b 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -4,7 +4,7 @@ """ import logging -from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, not_, Table, TEXT, true +from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, Table, TEXT, true from sqlalchemy.orm import relation import tool_shed.webapp.model @@ -81,9 +81,6 @@ mapper_registry.map_imperatively(UserRoleAssociation, UserRoleAssociation.table, properties=dict( user=relation(User, backref="roles"), - non_private_roles=relation(User, - backref="non_private_roles", - primaryjoin=((User.id == UserRoleAssociation.table.c.user_id) & (UserRoleAssociation.table.c.role_id == Role.id) & not_(Role.name == User.email))), role=relation(Role, back_populates='users'))) mapper_registry.map_imperatively(Repository, Repository.table, diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index fc2902eec9c0..0a2e78444c17 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -751,6 +751,8 @@ def test_relationships( password_reset_token, user_group_association, user_role_association, + role_factory, + user_role_association_factory ): obj = cls_() obj.email = get_unique_value() @@ -760,9 +762,16 @@ def test_relationships( obj.api_keys.append(api_keys) obj.reset_tokens.append(password_reset_token) obj.groups.append(user_group_association) - obj.roles.append(user_role_association) obj.repository_reviews.append(repository_review) + _private_role = role_factory(name=obj.email) + private_user_role = user_role_association_factory(obj, _private_role) + obj.roles.append(private_user_role) + + _non_private_role = role_factory(name='a') + non_private_user_role = user_role_association_factory(obj, _non_private_role) + obj.roles.append(non_private_user_role) + with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.active_repositories == [repository] @@ -770,8 +779,9 @@ def test_relationships( assert stored_obj.api_keys == [api_keys] assert stored_obj.reset_tokens == [password_reset_token] assert stored_obj.groups == [user_group_association] - assert stored_obj.roles == [user_role_association] assert stored_obj.repository_reviews == [repository_review] + assert are_same_entity_collections(stored_obj.roles, [private_user_role, non_private_user_role]) + assert stored_obj.non_private_roles == [non_private_user_role] class TestUserGroupAssociation(BaseTest): @@ -975,6 +985,13 @@ def make_instance(*args, **kwds): return make_instance +@pytest.fixture +def role_factory(model): + def make_instance(*args, **kwds): + return model.Role(*args, **kwds) + return make_instance + + @pytest.fixture def user_factory(model): def make_instance(*args, **kwds): @@ -985,6 +1002,13 @@ def make_instance(*args, **kwds): return make_instance +@pytest.fixture +def user_role_association_factory(model): + def make_instance(*args, **kwds): + return model.UserRoleAssociation(*args, **kwds) + return make_instance + + # Test utilities def dbcleanup_wrapper(session, obj, where_clause=None): From ad2301565c9c5b837aed601c31cddefeeaacbb37 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 20:39:59 -0400 Subject: [PATCH 38/55] Map UserRoleAssociation declaratively --- lib/tool_shed/webapp/model/__init__.py | 13 ++++++++++++- lib/tool_shed/webapp/model/mapping.py | 16 ++-------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index af6aa8e39536..a82cd6ab16dd 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -108,6 +108,7 @@ class User(Base, Dictifiable, _HasTable): dict_collection_visible_keys = ['id', 'username'] dict_element_visible_keys = ['id', 'username'] bootstrap_admin_user = False + roles = relationship('UserRoleAssociation', back_populates='user') non_private_roles = relationship( 'UserRoleAssociation', viewonly=True, @@ -249,7 +250,17 @@ def __init__(self, user, group): self.group = group -class UserRoleAssociation(_HasTable): +class UserRoleAssociation(Base, _HasTable): + __tablename__ = 'user_role_association' + + id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) + role_id = Column(Integer, ForeignKey('role.id'), index=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + user = relationship('User', back_populates='roles') + role = relationship('Role', back_populates='users') + def __init__(self, user, role): self.user = user self.role = role diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 643c74b2751b..ff32d59d8f0f 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -17,21 +17,14 @@ from tool_shed.webapp.model import mapper_registry from tool_shed.webapp.model import Repository, RepositoryCategoryAssociation from tool_shed.webapp.model import RepositoryMetadata, RepositoryRatingAssociation -from tool_shed.webapp.model import RepositoryReview, RepositoryRoleAssociation, Role -from tool_shed.webapp.model import User, UserRoleAssociation +from tool_shed.webapp.model import RepositoryReview, RepositoryRoleAssociation +from tool_shed.webapp.model import User from tool_shed.webapp.security import CommunityRBACAgent log = logging.getLogger(__name__) metadata = mapper_registry.metadata -UserRoleAssociation.table = Table("user_role_association", metadata, - Column("id", Integer, primary_key=True), - Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), - Column("role_id", Integer, ForeignKey("role.id"), index=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now)) - Repository.table = Table("repository", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -78,11 +71,6 @@ Column("rating", Integer, index=True), Column("deleted", Boolean, index=True, default=False)) -mapper_registry.map_imperatively(UserRoleAssociation, UserRoleAssociation.table, - properties=dict( - user=relation(User, backref="roles"), - role=relation(Role, back_populates='users'))) - mapper_registry.map_imperatively(Repository, Repository.table, properties=dict( categories=relation(RepositoryCategoryAssociation, back_populates='repository'), diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 0a2e78444c17..9c920c6cc3ec 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -816,7 +816,7 @@ def test_relationships(self, session, cls_, user, group): class TestUserRoleAssociation(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'user_role_association' + assert cls_.__tablename__ == 'user_role_association' def test_columns(self, session, cls_, user, role): create_time = datetime.now() From d6ee49604047a93ab3a72a956602ff0cccae0fb0 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 21:30:08 -0400 Subject: [PATCH 39/55] Fix SAWarning: remove unused overlapping relationship: review --- lib/tool_shed/webapp/model/mapping.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index ff32d59d8f0f..a3ab16b71922 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -103,8 +103,7 @@ # reset on a repository! repository_metadata=relation(RepositoryMetadata, foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], - primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), - backref='review'), + primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision))), user=relation(User, backref="repository_reviews"), component_reviews=relation(ComponentReview, primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false())), From e49c315a4f97ce0e58e8dc01d8516dcc8baf8d5f Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 22:54:57 -0400 Subject: [PATCH 40/55] Add back_populates, remove primaryjoin from Repo/RepoReview --- lib/tool_shed/webapp/model/mapping.py | 6 ++---- test/unit/shed_unit/model/test_mapping.py | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index a3ab16b71922..f68526a5545a 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -82,8 +82,7 @@ metadata_revisions=relation(RepositoryMetadata, order_by=desc(RepositoryMetadata.table.c.update_time)), roles=relation(RepositoryRoleAssociation, back_populates='repository'), - reviews=relation(RepositoryReview, - primaryjoin=(Repository.table.c.id == RepositoryReview.table.c.repository_id)), + reviews=relation(RepositoryReview, back_populates='repository'), reviewers=relation(User, secondary=RepositoryReview.table, primaryjoin=(Repository.table.c.id == RepositoryReview.table.c.repository_id), @@ -96,8 +95,7 @@ primaryjoin=((RepositoryMetadata.table.c.repository_id == RepositoryReview.table.c.repository_id) & (RepositoryMetadata.table.c.changeset_revision == RepositoryReview.table.c.changeset_revision))))) mapper_registry.map_imperatively(RepositoryReview, RepositoryReview.table, - properties=dict(repository=relation(Repository, - primaryjoin=(RepositoryReview.table.c.repository_id == Repository.table.c.id)), + properties=dict(repository=relation(Repository, back_populates='reviews'), # Take care when using the mapper below! It should be used only when a new review is being created for a repository change set revision. # Keep in mind that repository_metadata records can be removed from the database for certain change set revisions when metadata is being # reset on a repository! diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 9c920c6cc3ec..6ac7b52fefe9 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -368,7 +368,7 @@ def test_relationships( metadata1 = repository_metadata_factory() metadata2 = repository_metadata_factory() user = user_factory() - reviewer = user_factory() + # reviewer = user_factory() obj = cls_() obj.user = user @@ -378,7 +378,7 @@ def test_relationships( obj.metadata_revisions.append(metadata2) obj.roles.append(repository_role_association) obj.reviews.append(repository_review) - obj.reviewers.append(reviewer) + # obj.reviewers.append(reviewer) # TODO with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) @@ -386,7 +386,7 @@ def test_relationships( assert stored_obj.categories == [repository_category_association] assert stored_obj.ratings == [repository_rating_association] assert stored_obj.roles == [repository_role_association] - # assert stored_obj.reviews == [repository_review] # TODO broken + assert stored_obj.reviews == [repository_review] # assert stored_obj.reviewers == [reviewer] # TODO # TODO: metadata_revisions, downloadable_revisions From 3191bce2da8be94a9598d3034f9d8e0f1a989862 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 22:56:52 -0400 Subject: [PATCH 41/55] Replace backref w/back_populates on User/RepoReview --- lib/tool_shed/webapp/model/__init__.py | 1 + lib/tool_shed/webapp/model/mapping.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index a82cd6ab16dd..97dd36e75e63 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -117,6 +117,7 @@ class User(Base, Dictifiable, _HasTable): & (UserRoleAssociation.role_id == Role.id) # type: ignore & not_(Role.name == User.email)) # type: ignore ) + repository_reviews = relationship('RepositoryReview', back_populates='user') def __init__(self, email=None, password=None): self.email = email diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index f68526a5545a..d9294a709c3d 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -102,7 +102,7 @@ repository_metadata=relation(RepositoryMetadata, foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision))), - user=relation(User, backref="repository_reviews"), + user=relation(User, back_populates="repository_reviews"), component_reviews=relation(ComponentReview, primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false())), back_populates='repository_review'), From 667237a3444a16d1446ab676e977945f762abfc6 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Fri, 8 Oct 2021 22:59:42 -0400 Subject: [PATCH 42/55] Fix SAWarning: mark overlapping relationship as viewonly --- lib/tool_shed/webapp/model/mapping.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index d9294a709c3d..95d790a496e0 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -78,6 +78,7 @@ user=relation(User, back_populates='active_repositories'), downloadable_revisions=relation(RepositoryMetadata, primaryjoin=((Repository.table.c.id == RepositoryMetadata.table.c.repository_id) & (RepositoryMetadata.table.c.downloadable == true())), + viewonly=True, order_by=desc(RepositoryMetadata.table.c.update_time)), metadata_revisions=relation(RepositoryMetadata, order_by=desc(RepositoryMetadata.table.c.update_time)), From 7acbaada0080cbdff6322689ee95fd388d298223 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Oct 2021 19:25:08 -0400 Subject: [PATCH 43/55] Fix 4 SAWarnings; simplify mapping for Repo.reviewers; fix test 1. Add viewonly=True: we don't want this relationship to write to the db 2. Remove redandant relationship attributes (the joins are derived automatically based on existing foreign keys) 3. Fix test --- lib/tool_shed/webapp/model/mapping.py | 5 +--- test/unit/shed_unit/model/test_mapping.py | 29 ++++++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 95d790a496e0..0062175a0898 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -84,10 +84,7 @@ order_by=desc(RepositoryMetadata.table.c.update_time)), roles=relation(RepositoryRoleAssociation, back_populates='repository'), reviews=relation(RepositoryReview, back_populates='repository'), - reviewers=relation(User, - secondary=RepositoryReview.table, - primaryjoin=(Repository.table.c.id == RepositoryReview.table.c.repository_id), - secondaryjoin=(RepositoryReview.table.c.user_id == User.id)))) + reviewers=relation(User, secondary=RepositoryReview.table, viewonly=True))) mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict(repository=relation(Repository), diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 6ac7b52fefe9..173c8e73864e 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -362,13 +362,12 @@ def test_relationships( repository_rating_association, repository_metadata_factory, repository_role_association, - repository_review, + repository_review_factory, + user, user_factory, ): metadata1 = repository_metadata_factory() metadata2 = repository_metadata_factory() - user = user_factory() - # reviewer = user_factory() obj = cls_() obj.user = user @@ -377,8 +376,16 @@ def test_relationships( obj.downloadable_revisions.append(metadata1) obj.metadata_revisions.append(metadata2) obj.roles.append(repository_role_association) - obj.reviews.append(repository_review) - # obj.reviewers.append(reviewer) # TODO + + reviewer1 = user_factory() + review1 = repository_review_factory() + review1.user = reviewer1 + review1.repository = obj + + reviewer2 = user_factory() + review2 = repository_review_factory() + review2.user = reviewer2 + review2.repository = obj with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) @@ -386,9 +393,8 @@ def test_relationships( assert stored_obj.categories == [repository_category_association] assert stored_obj.ratings == [repository_rating_association] assert stored_obj.roles == [repository_role_association] - assert stored_obj.reviews == [repository_review] - # assert stored_obj.reviewers == [reviewer] # TODO - # TODO: metadata_revisions, downloadable_revisions + assert are_same_entity_collections(stored_obj.reviews, [review1, review2]) + assert are_same_entity_collections(stored_obj.reviewers, [reviewer1, reviewer2]) class TestRepositoryCategoryAssociation(BaseTest): @@ -985,6 +991,13 @@ def make_instance(*args, **kwds): return make_instance +@pytest.fixture +def repository_review_factory(model): + def make_instance(*args, **kwds): + return model.RepositoryReview(*args, **kwds) + return make_instance + + @pytest.fixture def role_factory(model): def make_instance(*args, **kwds): From 3330206ed50ed378134731b263a96495814fcbf4 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Oct 2021 19:38:09 -0400 Subject: [PATCH 44/55] Fix Repository tests --- test/unit/shed_unit/model/test_mapping.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 173c8e73864e..3f392bdd9bd8 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -366,15 +366,10 @@ def test_relationships( user, user_factory, ): - metadata1 = repository_metadata_factory() - metadata2 = repository_metadata_factory() - obj = cls_() obj.user = user obj.categories.append(repository_category_association) obj.ratings.append(repository_rating_association) - obj.downloadable_revisions.append(metadata1) - obj.metadata_revisions.append(metadata2) obj.roles.append(repository_role_association) reviewer1 = user_factory() @@ -387,6 +382,16 @@ def test_relationships( review2.user = reviewer2 review2.repository = obj + metadata1 = repository_metadata_factory() + metadata1.repository = obj + metadata1.downloadable = False + + metadata2 = repository_metadata_factory() + metadata2.repository = obj + metadata2.downloadable = True + + session.add_all([metadata1, metadata2]) + with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.user.id == user.id @@ -395,6 +400,10 @@ def test_relationships( assert stored_obj.roles == [repository_role_association] assert are_same_entity_collections(stored_obj.reviews, [review1, review2]) assert are_same_entity_collections(stored_obj.reviewers, [reviewer1, reviewer2]) + assert are_same_entity_collections(stored_obj.metadata_revisions, [metadata1, metadata2]) + assert stored_obj.downloadable_revisions == [metadata2] + + delete_from_database(session, [reviewer1, reviewer2, review1, review2, metadata1, metadata2]) class TestRepositoryCategoryAssociation(BaseTest): From 940380eb6e3ba00bdcb103c159345f690c892ce2 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Oct 2021 20:04:45 -0400 Subject: [PATCH 45/55] Map Repository declaratively --- lib/tool_shed/webapp/model/__init__.py | 36 ++++++++++++++++++-- lib/tool_shed/webapp/model/mapping.py | 41 +++-------------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 97dd36e75e63..8cb487189e36 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -17,6 +17,7 @@ not_, String, TEXT, + true, UniqueConstraint, ) from sqlalchemy.orm import ( @@ -27,7 +28,7 @@ import tool_shed.repository_types.util as rt_util from galaxy import util -from galaxy.model.custom_types import TrimmedString +from galaxy.model.custom_types import MutableJSONType, TrimmedString from galaxy.model.orm.now import now from galaxy.security.validate_user_input import validate_password_str from galaxy.util import unique_id @@ -340,7 +341,38 @@ def __init__(self, self.last_action = last_action or datetime.now() -class Repository(Dictifiable, _HasTable): +class Repository(Base, Dictifiable, _HasTable): + __tablename__ = 'repository' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + name = Column(TrimmedString(255), index=True) + type = Column(TrimmedString(255), index=True) + remote_repository_url = Column(TrimmedString(255)) + homepage_url = Column(TrimmedString(255)) + description = Column(TEXT) + long_description = Column(TEXT) + user_id = Column(Integer, ForeignKey("galaxy_user.id"), index=True) + private = Column(Boolean, default=False) + deleted = Column(Boolean, index=True, default=False) + email_alerts = Column(MutableJSONType, nullable=True) + times_downloaded = Column(Integer) + deprecated = Column(Boolean, default=False) + categories = relationship('RepositoryCategoryAssociation', back_populates='repository') + ratings = relationship('RepositoryRatingAssociation', + order_by=lambda: desc(RepositoryRatingAssociation.update_time), back_populates='repository') # type: ignore + user = relationship('User', back_populates='active_repositories') + downloadable_revisions = relationship('RepositoryMetadata', + primaryjoin=lambda: (Repository.id == RepositoryMetadata.repository_id) & (RepositoryMetadata.downloadable == true()), # type: ignore + viewonly=True, + order_by=lambda: desc(RepositoryMetadata.update_time)) # type: ignore + metadata_revisions = relationship('RepositoryMetadata', + order_by=lambda: desc(RepositoryMetadata.update_time)) # type: ignore + roles = relationship('RepositoryRoleAssociation', back_populates='repository') + reviews = relationship('RepositoryReview', back_populates='repository') + reviewers = relationship('User', secondary=lambda: RepositoryReview.table, viewonly=True) # type: ignore + dict_collection_visible_keys = ['id', 'name', 'type', 'remote_repository_url', 'homepage_url', 'description', 'user_id', 'private', 'deleted', 'times_downloaded', 'deprecated', 'create_time'] dict_element_visible_keys = ['id', 'name', 'type', 'remote_repository_url', 'homepage_url', 'description', 'long_description', 'user_id', 'private', diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 0062175a0898..1cb64bd348d0 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -4,7 +4,7 @@ """ import logging -from sqlalchemy import Boolean, Column, DateTime, desc, false, ForeignKey, Integer, Table, TEXT, true +from sqlalchemy import Boolean, Column, DateTime, false, ForeignKey, Integer, Table, true from sqlalchemy.orm import relation import tool_shed.webapp.model @@ -15,9 +15,9 @@ from galaxy.model.orm.now import now from tool_shed.webapp.model import ComponentReview from tool_shed.webapp.model import mapper_registry -from tool_shed.webapp.model import Repository, RepositoryCategoryAssociation -from tool_shed.webapp.model import RepositoryMetadata, RepositoryRatingAssociation -from tool_shed.webapp.model import RepositoryReview, RepositoryRoleAssociation +from tool_shed.webapp.model import Repository +from tool_shed.webapp.model import RepositoryMetadata +from tool_shed.webapp.model import RepositoryReview from tool_shed.webapp.model import User from tool_shed.webapp.security import CommunityRBACAgent @@ -25,23 +25,6 @@ metadata = mapper_registry.metadata -Repository.table = Table("repository", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("name", TrimmedString(255), index=True), - Column("type", TrimmedString(255), index=True), - Column("remote_repository_url", TrimmedString(255)), - Column("homepage_url", TrimmedString(255)), - Column("description", TEXT), - Column("long_description", TEXT), - Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True), - Column("private", Boolean, default=False), - Column("deleted", Boolean, index=True, default=False), - Column("email_alerts", MutableJSONType, nullable=True), - Column("times_downloaded", Integer), - Column("deprecated", Boolean, default=False)) - RepositoryMetadata.table = Table("repository_metadata", metadata, Column("id", Integer, primary_key=True), Column("create_time", DateTime, default=now), @@ -71,21 +54,6 @@ Column("rating", Integer, index=True), Column("deleted", Boolean, index=True, default=False)) -mapper_registry.map_imperatively(Repository, Repository.table, - properties=dict( - categories=relation(RepositoryCategoryAssociation, back_populates='repository'), - ratings=relation(RepositoryRatingAssociation, order_by=desc(RepositoryRatingAssociation.update_time), back_populates='repository'), - user=relation(User, back_populates='active_repositories'), - downloadable_revisions=relation(RepositoryMetadata, - primaryjoin=((Repository.table.c.id == RepositoryMetadata.table.c.repository_id) & (RepositoryMetadata.table.c.downloadable == true())), - viewonly=True, - order_by=desc(RepositoryMetadata.table.c.update_time)), - metadata_revisions=relation(RepositoryMetadata, - order_by=desc(RepositoryMetadata.table.c.update_time)), - roles=relation(RepositoryRoleAssociation, back_populates='repository'), - reviews=relation(RepositoryReview, back_populates='repository'), - reviewers=relation(User, secondary=RepositoryReview.table, viewonly=True))) - mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict(repository=relation(Repository), reviews=relation(RepositoryReview, @@ -129,7 +97,6 @@ def init(file_path, url, engine_options=None, create_tables=False) -> ToolShedMo result.create_tables = create_tables - # Load local tool shed security policy result.security_agent = CommunityRBACAgent(result) result.shed_counter = shed_statistics.ShedCounter(result) return result diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 3f392bdd9bd8..c79226d91fe2 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -298,7 +298,7 @@ def test_columns_and_relationships(self, session, cls_, user): class TestRepository(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'repository' + assert cls_.__tablename__ == 'repository' def test_columns(self, session, cls_, user): create_time = datetime.now() From bebeebc9cabd7e433528550c9f43deb8c2a98df3 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Oct 2021 23:02:21 -0400 Subject: [PATCH 46/55] Fix RepositoryMetadata review/reviews. For real this time (see note) There were 2 relationships defined on RepositoryMetadata: review and reviews. The former (review) must have been added unintentionally via backref. It was not referenced in the codebase, and its name was incorrect too: that relationship holds a list of reviews. However, the foreign_key attribute was CORRECT, whereas the correct relationship (reviews) had an incorrect foreign_key attribute, which resulted in an incorrect set of reviews (the size of the set was at most 1, which is incorrect). For an example of correct usage, see https://docs.sqlalchemy.org/en/14/orm/relationship_api.html?highlight=foreign_keys#sqlalchemy.orm.relationship.params.foreign_keys This commit corrects the foreign_keys attribute and fixes the unit test that verifies correct behavior. Victory at last! --- lib/tool_shed/webapp/model/mapping.py | 15 +++++---- test/unit/shed_unit/model/test_mapping.py | 38 +++++++++++++++++++++-- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 1cb64bd348d0..9ec93acdfa42 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -54,11 +54,12 @@ Column("rating", Integer, index=True), Column("deleted", Boolean, index=True, default=False)) -mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, - properties=dict(repository=relation(Repository), - reviews=relation(RepositoryReview, - foreign_keys=[RepositoryMetadata.table.c.repository_id, RepositoryMetadata.table.c.changeset_revision], - primaryjoin=((RepositoryMetadata.table.c.repository_id == RepositoryReview.table.c.repository_id) & (RepositoryMetadata.table.c.changeset_revision == RepositoryReview.table.c.changeset_revision))))) +mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict( + repository=relation(Repository), + reviews=relation(RepositoryReview, + foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], + primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), + back_populates='repository_metadata'))) mapper_registry.map_imperatively(RepositoryReview, RepositoryReview.table, properties=dict(repository=relation(Repository, back_populates='reviews'), @@ -67,7 +68,9 @@ # reset on a repository! repository_metadata=relation(RepositoryMetadata, foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], - primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision))), + primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), + back_populates='reviews', + ), user=relation(User, back_populates="repository_reviews"), component_reviews=relation(ComponentReview, primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false())), diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index c79226d91fe2..5a0497f7f7d7 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -486,14 +486,48 @@ def test_columns(self, session, cls_, repository): assert stored_obj.includes_tool_dependencies == includes_tool_dependencies assert stored_obj.includes_workflows == includes_workflows - def test_relationships(self, session, cls_, repository, repository_review): + def test_relationships( + self, + session, + cls_, + repository, + repository_review_factory, + user, + ): + obj = cls_() obj.repository = repository + obj.changeset_revision = 'nonempty' + + # create 3 reviews + review1 = repository_review_factory() + review2 = repository_review_factory() + review3 = repository_review_factory() + + # set the same user for all reviews + review1.user = user + review2.user = user + review3.user = user + + # set the same repository for all reviews + review1.repository = obj.repository + review2.repository = obj.repository + review3.repository = obj.repository + + # set the same changeset for reviews 1,2 + review1.changeset_revision = obj.changeset_revision + review2.changeset_revision = obj.changeset_revision + review3.changeset_revision = 'something else' # this won't be in reviews for this metadata + + # add to session + session.add(review1) + session.add(review2) + session.add(review3) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.repository.id == repository.id - # TODO broken: has both review and reviews due to mistake in backref + assert are_same_entity_collections(stored_obj.reviews, [review1, review2]) class TestRepositoryRatingAssociation(BaseTest): From 3253d6eb5dfd77c11389440a5405eb5cfa9c83da Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Oct 2021 23:20:16 -0400 Subject: [PATCH 47/55] Fix 2 SAWarnings: RepoMetadat.reviews should be viewonly --- lib/tool_shed/webapp/model/mapping.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 9ec93acdfa42..d3195773ce4c 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -57,6 +57,7 @@ mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict( repository=relation(Repository), reviews=relation(RepositoryReview, + viewonly=True, foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), back_populates='repository_metadata'))) From dcc9730579d49b809ad2f1d740179b9a77fc06ee Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Oct 2021 23:23:31 -0400 Subject: [PATCH 48/55] Fix SAWarning: add back_populates to Repo/RepoMetadata --- lib/tool_shed/webapp/model/__init__.py | 3 ++- lib/tool_shed/webapp/model/mapping.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 8cb487189e36..e794f490361e 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -368,7 +368,8 @@ class Repository(Base, Dictifiable, _HasTable): viewonly=True, order_by=lambda: desc(RepositoryMetadata.update_time)) # type: ignore metadata_revisions = relationship('RepositoryMetadata', - order_by=lambda: desc(RepositoryMetadata.update_time)) # type: ignore + order_by=lambda: desc(RepositoryMetadata.update_time), # type: ignore + back_populates='repository') roles = relationship('RepositoryRoleAssociation', back_populates='repository') reviews = relationship('RepositoryReview', back_populates='repository') reviewers = relationship('User', secondary=lambda: RepositoryReview.table, viewonly=True) # type: ignore diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index d3195773ce4c..1ece6d738b76 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -55,7 +55,7 @@ Column("deleted", Boolean, index=True, default=False)) mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict( - repository=relation(Repository), + repository=relation(Repository, back_populates='metadata_revisions'), reviews=relation(RepositoryReview, viewonly=True, foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], From 1b52676c19c312d2396665d2b3953b4fb6e47c5f Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Mon, 11 Oct 2021 23:25:06 -0400 Subject: [PATCH 49/55] Fix SAWarning: RepoReview.repo_metadata should be viewonly --- lib/tool_shed/webapp/model/mapping.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 1ece6d738b76..b350cc168d1f 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -68,6 +68,7 @@ # Keep in mind that repository_metadata records can be removed from the database for certain change set revisions when metadata is being # reset on a repository! repository_metadata=relation(RepositoryMetadata, + viewonly=True, foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), back_populates='reviews', From 3505c81f4caf62ef6ea22fcfe58a29d91d1f25f3 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Oct 2021 10:34:27 -0400 Subject: [PATCH 50/55] Fix RepositoryReview.repository_metadata testing --- test/unit/shed_unit/model/test_mapping.py | 25 +++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 5a0497f7f7d7..1da930990e82 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -605,28 +605,45 @@ def test_columns(self, session, cls_, repository, user): assert stored_obj.rating == rating assert stored_obj.deleted == deleted - def test_relationships(self, session, cls_, repository, user, repository_metadata, component_review_factory): + def test_relationships( + self, + session, + cls_, + repository, + user, + repository_metadata_factory, + component_review_factory + ): component_review1 = component_review_factory() component_review1.deleted = False component_review2 = component_review_factory() component_review2.deleted = False component_review2.private = True obj = cls_() + changeset = 'nonempty' + obj.changeset_revision = changeset obj.repository = repository obj.user = user obj.component_reviews.append(component_review1) obj.private_component_reviews.append(component_review2) - # obj.repository_metadata.append(repository_metadata) # TODO may be broken + + metadata1 = repository_metadata_factory() + metadata2 = repository_metadata_factory() + metadata1.repository = repository + metadata2.repository = repository + metadata1.changeset_revision = changeset + metadata2.changeset_revision = 'something else' + session.add_all([metadata1, metadata2]) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) assert stored_obj.repository.id == repository.id assert stored_obj.user.id == user.id - # assert stored_obj.repository_metadata == [repository_metadata] # TODO + assert stored_obj.repository_metadata == metadata1 assert are_same_entity_collections(stored_obj.component_reviews, [component_review1, component_review2]) assert stored_obj.private_component_reviews == [component_review2] - delete_from_database(session, [component_review1, component_review2]) + delete_from_database(session, [component_review1, component_review2, metadata1, metadata2]) class TestRepositoryRoleAssociation(BaseTest): From bf027dcd6c7fc9dd72d10c0f51d560a54b017fe7 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Oct 2021 10:49:47 -0400 Subject: [PATCH 51/55] Map RepositoryReview declaratively --- lib/tool_shed/webapp/model/__init__.py | 35 +++++++++++++++++++-- lib/tool_shed/webapp/model/mapping.py | 37 ++--------------------- test/unit/shed_unit/model/test_mapping.py | 2 +- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index e794f490361e..f71a9f394df0 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -12,6 +12,7 @@ Column, DateTime, desc, + false, ForeignKey, Integer, not_, @@ -372,7 +373,7 @@ class Repository(Base, Dictifiable, _HasTable): back_populates='repository') roles = relationship('RepositoryRoleAssociation', back_populates='repository') reviews = relationship('RepositoryReview', back_populates='repository') - reviewers = relationship('User', secondary=lambda: RepositoryReview.table, viewonly=True) # type: ignore + reviewers = relationship('User', secondary=lambda: RepositoryReview.__table__, viewonly=True) # type: ignore dict_collection_visible_keys = ['id', 'name', 'type', 'remote_repository_url', 'homepage_url', 'description', 'user_id', 'private', 'deleted', 'times_downloaded', 'deprecated', 'create_time'] @@ -576,7 +577,37 @@ def repository_dependencies(self): return [] -class RepositoryReview(Dictifiable, _HasTable): +class RepositoryReview(Base, Dictifiable, _HasTable): + __tablename__ = 'repository_review' + + id = Column(Integer, primary_key=True) + create_time = Column(DateTime, default=now) + update_time = Column(DateTime, default=now, onupdate=now) + repository_id = Column(Integer, ForeignKey('repository.id'), index=True) + changeset_revision = Column(TrimmedString(255), index=True) + user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True, nullable=False) + approved = Column(TrimmedString(255)) + rating = Column(Integer, index=True) + deleted = Column(Boolean, index=True, default=False) + repository = relationship('Repository', back_populates='reviews') + # Take care when using the mapper below! It should be used only when a new review is being created for a repository change set revision. + # Keep in mind that repository_metadata records can be removed from the database for certain change set revisions when metadata is being + # reset on a repository! + repository_metadata = relationship('RepositoryMetadata', + viewonly=True, + foreign_keys=lambda: [RepositoryReview.repository_id, RepositoryReview.changeset_revision], # type: ignore + primaryjoin=lambda: ((RepositoryReview.repository_id == RepositoryMetadata.repository_id) # type: ignore + & (RepositoryReview.changeset_revision == RepositoryMetadata.changeset_revision)), # type: ignore + back_populates='reviews') + user = relationship('User', back_populates='repository_reviews') + component_reviews = relationship('ComponentReview', + primaryjoin=lambda: ((RepositoryReview.id == ComponentReview.repository_review_id) # type: ignore + & (ComponentReview.deleted == false())), # type: ignore + back_populates='repository_review') + private_component_reviews = relationship('ComponentReview', + primaryjoin=lambda: ((RepositoryReview.id == ComponentReview.repository_review_id) # type: ignore + & (ComponentReview.deleted == false()) & (ComponentReview.private == true()))) # type: ignore + dict_collection_visible_keys = ['id', 'repository_id', 'changeset_revision', 'user_id', 'rating', 'deleted'] dict_element_visible_keys = ['id', 'repository_id', 'changeset_revision', 'user_id', 'rating', 'deleted'] approved_states = Bunch(NO='no', YES='yes') diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index b350cc168d1f..50d27f877004 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -4,7 +4,7 @@ """ import logging -from sqlalchemy import Boolean, Column, DateTime, false, ForeignKey, Integer, Table, true +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, Table from sqlalchemy.orm import relation import tool_shed.webapp.model @@ -13,12 +13,10 @@ from galaxy.model.custom_types import MutableJSONType, TrimmedString from galaxy.model.orm.engine_factory import build_engine from galaxy.model.orm.now import now -from tool_shed.webapp.model import ComponentReview from tool_shed.webapp.model import mapper_registry from tool_shed.webapp.model import Repository from tool_shed.webapp.model import RepositoryMetadata from tool_shed.webapp.model import RepositoryReview -from tool_shed.webapp.model import User from tool_shed.webapp.security import CommunityRBACAgent log = logging.getLogger(__name__) @@ -43,43 +41,14 @@ Column("includes_tool_dependencies", Boolean, default=False, index=True), Column("includes_workflows", Boolean, default=False, index=True)) -RepositoryReview.table = Table("repository_review", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("repository_id", Integer, ForeignKey("repository.id"), index=True), - Column("changeset_revision", TrimmedString(255), index=True), - Column("user_id", Integer, ForeignKey("galaxy_user.id"), index=True, nullable=False), - Column("approved", TrimmedString(255)), - Column("rating", Integer, index=True), - Column("deleted", Boolean, index=True, default=False)) - mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict( repository=relation(Repository, back_populates='metadata_revisions'), reviews=relation(RepositoryReview, viewonly=True, - foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], - primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), + foreign_keys=[RepositoryReview.repository_id, RepositoryReview.changeset_revision], + primaryjoin=((RepositoryReview.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), back_populates='repository_metadata'))) -mapper_registry.map_imperatively(RepositoryReview, RepositoryReview.table, - properties=dict(repository=relation(Repository, back_populates='reviews'), - # Take care when using the mapper below! It should be used only when a new review is being created for a repository change set revision. - # Keep in mind that repository_metadata records can be removed from the database for certain change set revisions when metadata is being - # reset on a repository! - repository_metadata=relation(RepositoryMetadata, - viewonly=True, - foreign_keys=[RepositoryReview.table.c.repository_id, RepositoryReview.table.c.changeset_revision], - primaryjoin=((RepositoryReview.table.c.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.table.c.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), - back_populates='reviews', - ), - user=relation(User, back_populates="repository_reviews"), - component_reviews=relation(ComponentReview, - primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false())), - back_populates='repository_review'), - private_component_reviews=relation(ComponentReview, - primaryjoin=((RepositoryReview.table.c.id == ComponentReview.repository_review_id) & (ComponentReview.deleted == false()) & (ComponentReview.private == true()))))) - class ToolShedModelMapping(SharedModelMapping): security_agent: CommunityRBACAgent diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index 1da930990e82..a2abf9d726bd 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -573,7 +573,7 @@ def test_relationships(self, session, cls_, repository, user): class TestRepositoryReview(BaseTest): def test_table(self, cls_): - assert cls_.table.name == 'repository_review' + assert cls_.__tablename__ == 'repository_review' def test_columns(self, session, cls_, repository, user): create_time = datetime.now() From 37b349ce92c6e995b70e9b377e068c7458612ce6 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Oct 2021 14:57:05 -0400 Subject: [PATCH 52/55] Fix last SAWarning for TS mapping --- lib/tool_shed/webapp/model/__init__.py | 4 ++++ test/unit/shed_unit/model/test_mapping.py | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index f71a9f394df0..f9012800773e 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -600,11 +600,15 @@ class RepositoryReview(Base, Dictifiable, _HasTable): & (RepositoryReview.changeset_revision == RepositoryMetadata.changeset_revision)), # type: ignore back_populates='reviews') user = relationship('User', back_populates='repository_reviews') + component_reviews = relationship('ComponentReview', + viewonly=True, primaryjoin=lambda: ((RepositoryReview.id == ComponentReview.repository_review_id) # type: ignore & (ComponentReview.deleted == false())), # type: ignore back_populates='repository_review') + private_component_reviews = relationship('ComponentReview', + viewonly=True, primaryjoin=lambda: ((RepositoryReview.id == ComponentReview.repository_review_id) # type: ignore & (ComponentReview.deleted == false()) & (ComponentReview.private == true()))) # type: ignore diff --git a/test/unit/shed_unit/model/test_mapping.py b/test/unit/shed_unit/model/test_mapping.py index a2abf9d726bd..d820bc9c34a6 100644 --- a/test/unit/shed_unit/model/test_mapping.py +++ b/test/unit/shed_unit/model/test_mapping.py @@ -614,18 +614,11 @@ def test_relationships( repository_metadata_factory, component_review_factory ): - component_review1 = component_review_factory() - component_review1.deleted = False - component_review2 = component_review_factory() - component_review2.deleted = False - component_review2.private = True obj = cls_() changeset = 'nonempty' obj.changeset_revision = changeset obj.repository = repository obj.user = user - obj.component_reviews.append(component_review1) - obj.private_component_reviews.append(component_review2) metadata1 = repository_metadata_factory() metadata2 = repository_metadata_factory() @@ -633,7 +626,17 @@ def test_relationships( metadata2.repository = repository metadata1.changeset_revision = changeset metadata2.changeset_revision = 'something else' - session.add_all([metadata1, metadata2]) + + component_review1 = component_review_factory() + component_review1.repository_review = obj + component_review1.deleted = False + + component_review2 = component_review_factory() + component_review2.repository_review = obj + component_review2.deleted = False + component_review2.private = True + + session.add_all([metadata1, metadata2, component_review1, component_review2]) with dbcleanup(session, obj) as obj_id: stored_obj = get_stored_obj(session, cls_, obj_id) From 185a99f17ff6f35204eaa864c869d2871ac1d0d6 Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Oct 2021 19:27:24 -0400 Subject: [PATCH 53/55] Move RepositoryMetadata out of mapping.py Do not remap declaratively. The model contains '.metadata' attr; a declaratively-mapped class cannot have a .metadata attribute (it is used by SQLAlchemy's DeclarativeBase). Given that TS is currently (mostly) in maintenance code, it is reasonable to leave this as is rather than handle all references to this attribute in the codebase. --- lib/tool_shed/webapp/model/__init__.py | 127 ++++++++++++++++--------- lib/tool_shed/webapp/model/mapping.py | 34 ------- 2 files changed, 81 insertions(+), 80 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index f9012800773e..fe9929ec277d 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -17,6 +17,7 @@ Integer, not_, String, + Table, TEXT, true, UniqueConstraint, @@ -48,8 +49,6 @@ WEAK_HG_REPO_CACHE: Mapping['Repository', Any] = weakref.WeakKeyDictionary() if TYPE_CHECKING: - from sqlalchemy.schema import Table - class _HasTable: table: Table else: @@ -533,50 +532,6 @@ def to_dict(self, view='collection', value_mapper=None): return rval -class RepositoryMetadata(Dictifiable, _HasTable): - dict_collection_visible_keys = ['id', 'repository_id', 'numeric_revision', 'changeset_revision', 'malicious', 'downloadable', 'missing_test_components', - 'has_repository_dependencies', 'includes_datatypes', 'includes_tools', 'includes_tool_dependencies', - 'includes_tools_for_display_in_tool_panel', 'includes_workflows'] - dict_element_visible_keys = ['id', 'repository_id', 'numeric_revision', 'changeset_revision', 'malicious', 'downloadable', 'missing_test_components', - 'has_repository_dependencies', 'includes_datatypes', 'includes_tools', 'includes_tool_dependencies', - 'includes_tools_for_display_in_tool_panel', 'includes_workflows', 'repository_dependencies'] - - def __init__(self, id=None, repository_id=None, numeric_revision=None, changeset_revision=None, metadata=None, tool_versions=None, malicious=False, - downloadable=False, missing_test_components=None, tools_functionally_correct=False, test_install_error=False, - has_repository_dependencies=False, includes_datatypes=False, includes_tools=False, includes_tool_dependencies=False, - includes_workflows=False): - self.id = id - self.repository_id = repository_id - self.numeric_revision = numeric_revision - self.changeset_revision = changeset_revision - self.metadata = metadata - self.tool_versions = tool_versions - self.malicious = malicious - self.downloadable = downloadable - self.missing_test_components = missing_test_components - self.has_repository_dependencies = has_repository_dependencies - # We don't consider the special case has_repository_dependencies_only_if_compiling_contained_td here. - self.includes_datatypes = includes_datatypes - self.includes_tools = includes_tools - self.includes_tool_dependencies = includes_tool_dependencies - self.includes_workflows = includes_workflows - - @property - def includes_tools_for_display_in_tool_panel(self): - if self.metadata: - tool_dicts = self.metadata.get('tools', []) - for tool_dict in tool_dicts: - if tool_dict.get('add_to_tool_panel', True): - return True - return False - - @property - def repository_dependencies(self): - if self.has_repository_dependencies: - return [repository_dependency for repository_dependency in self.metadata['repository_dependencies']['repository_dependencies']] - return [] - - class RepositoryReview(Base, Dictifiable, _HasTable): __tablename__ = 'repository_review' @@ -753,6 +708,86 @@ def __str__(self): return "Tag(id=%s, type=%i, parent_id=%s, name=%s)" % (self.id, self.type, self.parent_id, self.name) +# The RepositoryMetadata model is mapped imperatively (for details see discussion in PR #12064). +# TLDR: a declaratively-mapped class cannot have a .metadata attribute (it is used by SQLAlchemy's DeclarativeBase). + +class RepositoryMetadata(Dictifiable, _HasTable): + # Once the class has been mapped, all Column items in this table will be available + # as instrumented class attributes on RepositoryMetadata. + table = Table('repository_metadata', mapper_registry.metadata, + Column('id', Integer, primary_key=True), + Column('create_time', DateTime, default=now), + Column('update_time', DateTime, default=now, onupdate=now), + Column('repository_id', Integer, ForeignKey('repository.id'), index=True), + Column('changeset_revision', TrimmedString(255), index=True), + Column('numeric_revision', Integer, index=True), + Column('metadata', MutableJSONType, nullable=True), + Column('tool_versions', MutableJSONType, nullable=True), + Column('malicious', Boolean, default=False), + Column('downloadable', Boolean, default=True), + Column('missing_test_components', Boolean, default=False, index=True), + Column('has_repository_dependencies', Boolean, default=False, index=True), + Column('includes_datatypes', Boolean, default=False, index=True), + Column('includes_tools', Boolean, default=False, index=True), + Column('includes_tool_dependencies', Boolean, default=False, index=True), + Column('includes_workflows', Boolean, default=False, index=True)) + + dict_collection_visible_keys = ['id', 'repository_id', 'numeric_revision', 'changeset_revision', 'malicious', 'downloadable', 'missing_test_components', + 'has_repository_dependencies', 'includes_datatypes', 'includes_tools', 'includes_tool_dependencies', + 'includes_tools_for_display_in_tool_panel', 'includes_workflows'] + dict_element_visible_keys = ['id', 'repository_id', 'numeric_revision', 'changeset_revision', 'malicious', 'downloadable', 'missing_test_components', + 'has_repository_dependencies', 'includes_datatypes', 'includes_tools', 'includes_tool_dependencies', + 'includes_tools_for_display_in_tool_panel', 'includes_workflows', 'repository_dependencies'] + + def __init__(self, id=None, repository_id=None, numeric_revision=None, changeset_revision=None, metadata=None, tool_versions=None, malicious=False, + downloadable=False, missing_test_components=None, tools_functionally_correct=False, test_install_error=False, + has_repository_dependencies=False, includes_datatypes=False, includes_tools=False, includes_tool_dependencies=False, + includes_workflows=False): + self.id = id + self.repository_id = repository_id + self.numeric_revision = numeric_revision + self.changeset_revision = changeset_revision + self.metadata = metadata + self.tool_versions = tool_versions + self.malicious = malicious + self.downloadable = downloadable + self.missing_test_components = missing_test_components + self.has_repository_dependencies = has_repository_dependencies + # We don't consider the special case has_repository_dependencies_only_if_compiling_contained_td here. + self.includes_datatypes = includes_datatypes + self.includes_tools = includes_tools + self.includes_tool_dependencies = includes_tool_dependencies + self.includes_workflows = includes_workflows + + @property + def includes_tools_for_display_in_tool_panel(self): + if self.metadata: + tool_dicts = self.metadata.get('tools', []) + for tool_dict in tool_dicts: + if tool_dict.get('add_to_tool_panel', True): + return True + return False + + @property + def repository_dependencies(self): + if self.has_repository_dependencies: + return [repository_dependency for repository_dependency in self.metadata['repository_dependencies']['repository_dependencies']] + return [] + + +# After the map_imperatively statement has been executed, the members of the +# properties dictionary (repository, reviews) will be available as instrumented +# class attributes on RepositoryMetadata. +mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict( + repository=relationship(Repository, back_populates='metadata_revisions'), + reviews=relationship(RepositoryReview, + viewonly=True, + foreign_keys=lambda: [RepositoryReview.repository_id, RepositoryReview.changeset_revision], # type: ignore + primaryjoin=lambda: ((RepositoryReview.repository_id == RepositoryMetadata.repository_id) # type: ignore + & (RepositoryReview.changeset_revision == RepositoryMetadata.changeset_revision)), # type: ignore + back_populates='repository_metadata'))) + + # Utility methods def sort_by_attr(seq, attr): """ diff --git a/lib/tool_shed/webapp/model/mapping.py b/lib/tool_shed/webapp/model/mapping.py index 50d27f877004..e7dc76b0cb35 100644 --- a/lib/tool_shed/webapp/model/mapping.py +++ b/lib/tool_shed/webapp/model/mapping.py @@ -4,51 +4,17 @@ """ import logging -from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, Table -from sqlalchemy.orm import relation - import tool_shed.webapp.model import tool_shed.webapp.util.shed_statistics as shed_statistics from galaxy.model.base import SharedModelMapping -from galaxy.model.custom_types import MutableJSONType, TrimmedString from galaxy.model.orm.engine_factory import build_engine -from galaxy.model.orm.now import now from tool_shed.webapp.model import mapper_registry -from tool_shed.webapp.model import Repository -from tool_shed.webapp.model import RepositoryMetadata -from tool_shed.webapp.model import RepositoryReview from tool_shed.webapp.security import CommunityRBACAgent log = logging.getLogger(__name__) metadata = mapper_registry.metadata -RepositoryMetadata.table = Table("repository_metadata", metadata, - Column("id", Integer, primary_key=True), - Column("create_time", DateTime, default=now), - Column("update_time", DateTime, default=now, onupdate=now), - Column("repository_id", Integer, ForeignKey("repository.id"), index=True), - Column("changeset_revision", TrimmedString(255), index=True), - Column("numeric_revision", Integer, index=True), - Column("metadata", MutableJSONType, nullable=True), - Column("tool_versions", MutableJSONType, nullable=True), - Column("malicious", Boolean, default=False), - Column("downloadable", Boolean, default=True), - Column("missing_test_components", Boolean, default=False, index=True), - Column("has_repository_dependencies", Boolean, default=False, index=True), - Column("includes_datatypes", Boolean, default=False, index=True), - Column("includes_tools", Boolean, default=False, index=True), - Column("includes_tool_dependencies", Boolean, default=False, index=True), - Column("includes_workflows", Boolean, default=False, index=True)) - -mapper_registry.map_imperatively(RepositoryMetadata, RepositoryMetadata.table, properties=dict( - repository=relation(Repository, back_populates='metadata_revisions'), - reviews=relation(RepositoryReview, - viewonly=True, - foreign_keys=[RepositoryReview.repository_id, RepositoryReview.changeset_revision], - primaryjoin=((RepositoryReview.repository_id == RepositoryMetadata.table.c.repository_id) & (RepositoryReview.changeset_revision == RepositoryMetadata.table.c.changeset_revision)), - back_populates='repository_metadata'))) - class ToolShedModelMapping(SharedModelMapping): security_agent: CommunityRBACAgent From 13f7b4cb8bc98e848db8373302cace26ae1e077b Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Oct 2021 19:34:29 -0400 Subject: [PATCH 54/55] Drop datatype from ForeignKey (redundant; less code to read) --- lib/tool_shed/webapp/model/__init__.py | 44 +++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index fe9929ec277d..85dc658c041a 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -74,7 +74,7 @@ class APIKeys(Base, _HasTable): id = Column(Integer, primary_key=True) create_time = Column(DateTime, default=now) - user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) + user_id = Column(ForeignKey('galaxy_user.id'), index=True) key = Column(TrimmedString(32), index=True, unique=True) user = relationship('User', back_populates='api_keys') @@ -166,7 +166,7 @@ class PasswordResetToken(Base, _HasTable): token = Column(String(32), primary_key=True, unique=True, index=True) expiration_time = Column(DateTime) - user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) + user_id = Column(ForeignKey('galaxy_user.id'), index=True) user = relationship('User', back_populates='reset_tokens') def __init__(self, user, token=None): @@ -240,8 +240,8 @@ class UserGroupAssociation(Base, _HasTable): __tablename__ = 'user_group_association' id = Column(Integer, primary_key=True) - user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) - group_id = Column(Integer, ForeignKey('galaxy_group.id'), index=True) + user_id = Column(ForeignKey('galaxy_user.id'), index=True) + group_id = Column(ForeignKey('galaxy_group.id'), index=True) create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) user = relationship('User', back_populates='groups') @@ -256,8 +256,8 @@ class UserRoleAssociation(Base, _HasTable): __tablename__ = 'user_role_association' id = Column(Integer, primary_key=True) - user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True) - role_id = Column(Integer, ForeignKey('role.id'), index=True) + user_id = Column(ForeignKey('galaxy_user.id'), index=True) + role_id = Column(ForeignKey('role.id'), index=True) create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) user = relationship('User', back_populates='roles') @@ -272,8 +272,8 @@ class GroupRoleAssociation(Base, _HasTable): __tablename__ = 'group_role_association' id = Column(Integer, primary_key=True) - group_id = Column(Integer, ForeignKey("galaxy_group.id"), index=True) - role_id = Column(Integer, ForeignKey("role.id"), index=True) + group_id = Column(ForeignKey("galaxy_group.id"), index=True) + role_id = Column(ForeignKey("role.id"), index=True) create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) group = relationship('Group', back_populates='roles') @@ -288,8 +288,8 @@ class RepositoryRoleAssociation(Base, _HasTable): __tablename__ = 'repository_role_association' id = Column(Integer, primary_key=True) - repository_id = Column(Integer, ForeignKey("repository.id"), index=True) - role_id = Column(Integer, ForeignKey("role.id"), index=True) + repository_id = Column(ForeignKey("repository.id"), index=True) + role_id = Column(ForeignKey("role.id"), index=True) create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) repository = relationship('Repository', back_populates='roles') @@ -306,7 +306,7 @@ class GalaxySession(Base, _HasTable): id = Column(Integer, primary_key=True) create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) - user_id = Column(Integer, ForeignKey("galaxy_user.id"), index=True, nullable=True) + user_id = Column(ForeignKey("galaxy_user.id"), index=True, nullable=True) remote_host = Column(String(255)) remote_addr = Column(String(255)) referer = Column(TEXT) @@ -353,7 +353,7 @@ class Repository(Base, Dictifiable, _HasTable): homepage_url = Column(TrimmedString(255)) description = Column(TEXT) long_description = Column(TEXT) - user_id = Column(Integer, ForeignKey("galaxy_user.id"), index=True) + user_id = Column(ForeignKey("galaxy_user.id"), index=True) private = Column(Boolean, default=False) deleted = Column(Boolean, index=True, default=False) email_alerts = Column(MutableJSONType, nullable=True) @@ -538,9 +538,9 @@ class RepositoryReview(Base, Dictifiable, _HasTable): id = Column(Integer, primary_key=True) create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) - repository_id = Column(Integer, ForeignKey('repository.id'), index=True) + repository_id = Column(ForeignKey('repository.id'), index=True) changeset_revision = Column(TrimmedString(255), index=True) - user_id = Column(Integer, ForeignKey('galaxy_user.id'), index=True, nullable=False) + user_id = Column(ForeignKey('galaxy_user.id'), index=True, nullable=False) approved = Column(TrimmedString(255)) rating = Column(Integer, index=True) deleted = Column(Boolean, index=True, default=False) @@ -585,8 +585,8 @@ class ComponentReview(Base, Dictifiable, _HasTable): id = Column(Integer, primary_key=True) create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) - repository_review_id = Column(Integer, ForeignKey("repository_review.id"), index=True) - component_id = Column(Integer, ForeignKey("component.id"), index=True) + repository_review_id = Column(ForeignKey("repository_review.id"), index=True) + component_id = Column(ForeignKey("component.id"), index=True) comment = Column(TEXT) private = Column(Boolean, default=False) approved = Column(TrimmedString(255)) @@ -640,8 +640,8 @@ class RepositoryRatingAssociation(Base, ItemRatingAssociation, _HasTable): id = Column(Integer, primary_key=True) create_time = Column(DateTime, default=now) update_time = Column(DateTime, default=now, onupdate=now) - repository_id = Column(Integer, ForeignKey("repository.id"), index=True) - user_id = Column(Integer, ForeignKey("galaxy_user.id"), index=True) + repository_id = Column(ForeignKey("repository.id"), index=True) + user_id = Column(ForeignKey("galaxy_user.id"), index=True) rating = Column(Integer, index=True) comment = Column(TEXT) repository = relationship('Repository', back_populates='ratings') @@ -675,8 +675,8 @@ class RepositoryCategoryAssociation(Base, _HasTable): __tablename__ = 'repository_category_association' id = Column(Integer, primary_key=True) - repository_id = Column(Integer, ForeignKey('repository.id'), index=True) - category_id = Column(Integer, ForeignKey('category.id'), index=True) + repository_id = Column(ForeignKey('repository.id'), index=True) + category_id = Column(ForeignKey('category.id'), index=True) category = relationship('Category', back_populates='repositories') repository = relationship('Repository', back_populates='categories') @@ -693,7 +693,7 @@ class Tag(Base, _HasTable): id = Column(Integer, primary_key=True) type = Column(Integer) - parent_id = Column(Integer, ForeignKey('tag.id')) + parent_id = Column(ForeignKey('tag.id')) name = Column(TrimmedString(255)) children = relationship('Tag', back_populates='parent') parent = relationship('Tag', back_populates='children', remote_side=[id]) @@ -718,7 +718,7 @@ class RepositoryMetadata(Dictifiable, _HasTable): Column('id', Integer, primary_key=True), Column('create_time', DateTime, default=now), Column('update_time', DateTime, default=now, onupdate=now), - Column('repository_id', Integer, ForeignKey('repository.id'), index=True), + Column('repository_id', ForeignKey('repository.id'), index=True), Column('changeset_revision', TrimmedString(255), index=True), Column('numeric_revision', Integer, index=True), Column('metadata', MutableJSONType, nullable=True), From 4fe97b6ad09560c3a168bf554371dab3cb7f4c2d Mon Sep 17 00:00:00 2001 From: Sergey Golitsynskiy Date: Tue, 12 Oct 2021 19:58:06 -0400 Subject: [PATCH 55/55] Cleanup up constructors --- lib/tool_shed/webapp/model/__init__.py | 73 +++++--------------------- 1 file changed, 13 insertions(+), 60 deletions(-) diff --git a/lib/tool_shed/webapp/model/__init__.py b/lib/tool_shed/webapp/model/__init__.py index 85dc658c041a..13287839fafd 100644 --- a/lib/tool_shed/webapp/model/__init__.py +++ b/lib/tool_shed/webapp/model/__init__.py @@ -126,7 +126,6 @@ def __init__(self, email=None, password=None): self.external = False self.deleted = False self.purged = False - self.username = None self.new_repo_alert = False def all_roles(self): @@ -220,7 +219,7 @@ class Role(Base, Dictifiable, _HasTable): ADMIN='admin', SHARING='sharing') - def __init__(self, name="", description="", type="system", deleted=False): + def __init__(self, name=None, description=None, type=types.SYSTEM, deleted=False): self.name = name self.description = description self.type = type @@ -318,27 +317,10 @@ class GalaxySession(Base, _HasTable): last_action = Column(DateTime) user = relationship('User', back_populates='galaxy_sessions') - def __init__(self, - id=None, - user=None, - remote_host=None, - remote_addr=None, - referer=None, - current_history=None, - session_key=None, - is_valid=False, - prev_session_id=None, - last_action=None): - self.id = id - self.user = user - self.remote_host = remote_host - self.remote_addr = remote_addr - self.referer = referer - self.current_history = current_history - self.session_key = session_key + def __init__(self, is_valid=False, **kwd): + super().__init__(**kwd) self.is_valid = is_valid - self.prev_session_id = prev_session_id - self.last_action = last_action or datetime.now() + self.last_action = self.last_action or datetime.now() class Repository(Base, Dictifiable, _HasTable): @@ -384,24 +366,12 @@ class Repository(Base, Dictifiable, _HasTable): MARKED_FOR_ADDITION='a', NOT_TRACKED='?') - def __init__(self, id=None, name=None, type=None, remote_repository_url=None, homepage_url=None, - description=None, long_description=None, user_id=None, private=False, - deleted=None, email_alerts=None, times_downloaded=0, deprecated=False, - create_time=None): - self.id = id - self.name = name or "Unnamed repository" - self.type = type - self.remote_repository_url = remote_repository_url - self.homepage_url = homepage_url - self.description = description - self.long_description = long_description - self.user_id = user_id + def __init__(self, private=False, times_downloaded=0, deprecated=False, **kwd): + super().__init__(**kwd) self.private = private - self.deleted = deleted - self.email_alerts = email_alerts self.times_downloaded = times_downloaded self.deprecated = deprecated - self.create_time = create_time + self.name = self.name or "Unnamed repository" @property def hg_repo(self): @@ -571,11 +541,8 @@ class RepositoryReview(Base, Dictifiable, _HasTable): dict_element_visible_keys = ['id', 'repository_id', 'changeset_revision', 'user_id', 'rating', 'deleted'] approved_states = Bunch(NO='no', YES='yes') - def __init__(self, repository_id=None, changeset_revision=None, user_id=None, rating=None, deleted=False): - self.repository_id = repository_id - self.changeset_revision = changeset_revision - self.user_id = user_id - self.rating = rating + def __init__(self, deleted=False, **kwd): + super().__init__(**kwd) self.deleted = deleted @@ -599,13 +566,10 @@ class ComponentReview(Base, Dictifiable, _HasTable): dict_element_visible_keys = ['id', 'repository_review_id', 'component_id', 'private', 'approved', 'rating', 'deleted'] approved_states = Bunch(NO='no', YES='yes', NA='not_applicable') - def __init__(self, repository_review_id=None, component_id=None, comment=None, private=False, approved=False, rating=None, deleted=False): - self.repository_review_id = repository_review_id - self.component_id = component_id - self.comment = comment + def __init__(self, private=False, approved=False, deleted=False, **kwd): + super().__init__(**kwd) self.private = private self.approved = approved - self.rating = rating self.deleted = deleted @@ -616,10 +580,6 @@ class Component(Base, _HasTable): name = Column(TrimmedString(255)) description = Column(TEXT) - def __init__(self, name=None, description=None): - self.name = name - self.description = description - class ItemRatingAssociation(_HasTable): @@ -665,9 +625,8 @@ class Category(Base, Dictifiable, _HasTable): dict_collection_visible_keys = ['id', 'name', 'description', 'deleted'] dict_element_visible_keys = ['id', 'name', 'description', 'deleted'] - def __init__(self, name=None, description=None, deleted=False): - self.name = name - self.description = description + def __init__(self, deleted=False, **kwd): + super().__init__(**kwd) self.deleted = deleted @@ -698,12 +657,6 @@ class Tag(Base, _HasTable): children = relationship('Tag', back_populates='parent') parent = relationship('Tag', back_populates='children', remote_side=[id]) - def __init__(self, id=None, type=None, parent_id=None, name=None): - self.id = id - self.type = type - self.parent_id = parent_id - self.name = name - def __str__(self): return "Tag(id=%s, type=%i, parent_id=%s, name=%s)" % (self.id, self.type, self.parent_id, self.name)