Skip to content

Commit

Permalink
Do not initialize a full repository registry in ToolShed 2.0.
Browse files Browse the repository at this point in the history
It seems like it is only used by repository_grids - which I think are unused by the new tool shed. I don't recall writing any client side grid framework code and my PR description did claim it was mako free.
  • Loading branch information
jmchilton committed Aug 5, 2024
1 parent efe76b5 commit fd2425f
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 31 deletions.
4 changes: 1 addition & 3 deletions lib/tool_shed/managers/categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ def index(self, trans: ProvidesUserContext, deleted: bool) -> List[Dict[str, Any

def to_dict(self, category: Category) -> Dict[str, Any]:
category_dict = category.to_dict(view="collection", value_mapper=get_value_mapper(self.app))
category_dict["repositories"] = self.app.repository_registry.viewable_repositories_and_suites_by_category.get(
category.name, 0
)
category_dict["repositories"] = category.active_repository_count()
category_dict["url"] = web.url_for(
controller="categories", action="show", id=self.app.security.encode_id(category.id)
)
Expand Down
104 changes: 96 additions & 8 deletions lib/tool_shed/repository_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
or_,
select,
)
from typing_extensions import Protocol

import tool_shed.repository_types.util as rt_util
from tool_shed.util import (
Expand All @@ -18,11 +19,77 @@
Repository,
RepositoryMetadata,
)
from .structured_app import ToolShedApp

log = logging.getLogger(__name__)


class Registry:
class RegistryInterface(Protocol):

# only used in legacy controllers - can drop when drop old tool shed webapp
def add_category_entry(self, category): ...

def add_entry(self, repository):
# used when creating a repository
# Used in legacy add_repository_registry_entry API endpoint - already deemed not worth pulling over to 2.0
# used in legacy undelete_repository admin controller
# used in legacy deprecate controller
...

def edit_category_entry(self, old_name, new_name):
# used in legacy admin controller
...

def is_valid(self, repository) -> bool:
# probably not used outside this class but also not hurting anything
...

def remove_category_entry(self, category): ...

def remove_entry(self, repository): ...


# stop gap to implement the same general interface as repository_registry but do nothing,
# I don't think this stuff is needed - outside potentially old test cases?
class NullRepositoryRegistry(RegistryInterface):

def __init__(self, app: ToolShedApp):
self.app = app

# all of these are only used by repository_grids - which I think the tool shed 2.0
# does not use at all - but they are part of the "public interface" consumed by
# the full registry.
self.certified_level_one_viewable_repositories_and_suites_by_category: dict = {}
self.certified_level_one_viewable_suites_by_category: dict = {}
self.viewable_repositories_and_suites_by_category: dict = {}
self.viewable_suites_by_category: dict = {}
self.viewable_valid_repositories_and_suites_by_category: dict = {}
self.viewable_valid_suites_by_category: dict = {}

def add_category_entry(self, category):
# only used in legacy controllers - can drop when drop old tool shed webapp
pass

def add_entry(self, repository):
# used when creating a repository, and maybe more?
pass

def edit_category_entry(self, old_name, new_name):
pass

def is_valid(self, repository) -> bool:
if repository and not repository.deleted and not repository.deprecated and repository.downloadable_revisions:
return True
return False

def remove_category_entry(self, category):
pass

def remove_entry(self, repository):
pass


class Registry(RegistryInterface):
def __init__(self, app):
log.debug("Loading the repository registry...")
self.app = app
Expand All @@ -32,17 +99,30 @@ def __init__(self, app):
self.certified_level_one_suite_tuples = []
# These category dictionaries contain entries where the key is the category and the value is the integer count
# of viewable repositories within that category.

# only used internally to class and by repository_grids
self.certified_level_one_viewable_repositories_and_suites_by_category = {}
# only used internally to class and by repository_grids
self.certified_level_one_viewable_suites_by_category = {}
self.certified_level_two_repository_and_suite_tuples = []
self.certified_level_two_suite_tuples = []
self.certified_level_two_viewable_repositories_and_suites_by_category = {}
self.certified_level_two_viewable_suites_by_category = {}

# not even used in legacy shed code...
# self.certified_level_two_repository_and_suite_tuples = []
# self.certified_level_two_suite_tuples = []
# self.certified_level_two_viewable_repositories_and_suites_by_category = {}
# self.certified_level_two_viewable_suites_by_category = {}

# only used internally to class
self.repository_and_suite_tuples = []
# only used internally to class
self.suite_tuples = []

# only used internally to class and by repository_grids
self.viewable_repositories_and_suites_by_category = {}
# only used internally to class and by repository_grids
self.viewable_suites_by_category = {}
# only used internally to class and by repository_grids
self.viewable_valid_repositories_and_suites_by_category = {}
# only used internally to class and by repository_grids
self.viewable_valid_suites_by_category = {}
self.load()

Expand Down Expand Up @@ -152,6 +232,7 @@ def edit_category_entry(self, old_name, new_name):
self.certified_level_one_viewable_suites_by_category[new_name] = 0

def get_certified_level_one_clause_list(self):
# only used internally to class
clause_list = []
for repository in get_repositories(self.sa_session):
certified_level_one_tuple = self.get_certified_level_one_tuple(repository)
Expand All @@ -169,6 +250,7 @@ def get_certified_level_one_tuple(self, repository):
"""
Return True if the latest installable changeset_revision of the received repository is level one certified.
"""
# only used internally to class
if repository is None:
return (None, False)
if repository.deleted or repository.deprecated:
Expand All @@ -190,6 +272,7 @@ def get_certified_level_one_tuple(self, repository):
return (None, False)

def is_level_one_certified(self, repository_metadata):
# only used internally to class
if repository_metadata:
repository = repository_metadata.repository
if repository:
Expand All @@ -206,12 +289,13 @@ def is_level_one_certified(self, repository_metadata):
return tuple in self.certified_level_one_repository_and_suite_tuples
return False

def is_valid(self, repository):
def is_valid(self, repository) -> bool:
if repository and not repository.deleted and not repository.deprecated and repository.downloadable_revisions:
return True
return False

def load_certified_level_one_repository_and_suite_tuple(self, repository):
# only used internally to class
# The received repository has been determined to be level one certified.
name = str(repository.name)
owner = str(repository.user.username)
Expand All @@ -226,6 +310,7 @@ def load_certified_level_one_repository_and_suite_tuple(self, repository):
self.certified_level_one_repository_and_suite_tuples.append(certified_level_one_tuple)

def load_repository_and_suite_tuple(self, repository):
# only used internally to class
name = str(repository.name)
owner = str(repository.user.username)
for repository_metadata in repository.metadata_revisions:
Expand All @@ -238,6 +323,7 @@ def load_repository_and_suite_tuple(self, repository):
self.suite_tuples.append(tuple)

def load_repository_and_suite_tuples(self):
# only used internally to class
# Load self.certified_level_one_repository_and_suite_tuples and self.certified_level_one_suite_tuples.
clauses = self.get_certified_level_one_clause_list()
for repository in get_certified_repositories_with_user(self.sa_session, clauses, model.User):
Expand All @@ -247,11 +333,12 @@ def load_repository_and_suite_tuples(self):
self.load_repository_and_suite_tuple(repository)

def load_viewable_repositories_and_suites_by_category(self):
# only used internally to class
# Clear all dictionaries just in case they were previously loaded.
self.certified_level_one_viewable_repositories_and_suites_by_category = {}
self.certified_level_one_viewable_suites_by_category = {}
self.certified_level_two_viewable_repositories_and_suites_by_category = {}
self.certified_level_two_viewable_suites_by_category = {}
# self.certified_level_two_viewable_repositories_and_suites_by_category = {}
# self.certified_level_two_viewable_suites_by_category = {}
self.viewable_repositories_and_suites_by_category = {}
self.viewable_suites_by_category = {}
self.viewable_valid_repositories_and_suites_by_category = {}
Expand Down Expand Up @@ -363,6 +450,7 @@ def remove_entry(self, repository):

@property
def sa_session(self):
# only used internally to class
return self.app.model.session

def unload_certified_level_one_repository_and_suite_tuple(self, repository):
Expand Down
4 changes: 2 additions & 2 deletions lib/tool_shed/structured_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

if TYPE_CHECKING:
from tool_shed.managers.model_cache import ModelCache
from tool_shed.repository_registry import Registry as RepositoryRegistry
from tool_shed.repository_registry import RegistryInterface
from tool_shed.repository_types.registry import Registry as RepositoryTypesRegistry
from tool_shed.util.hgweb_config import HgWebConfigManager
from tool_shed.webapp.model import mapping
Expand All @@ -14,7 +14,7 @@
class ToolShedApp(BasicSharedApp):
repository_types_registry: "RepositoryTypesRegistry"
model: "mapping.ToolShedModelMapping"
repository_registry: "RepositoryRegistry"
repository_registry: "RegistryInterface"
hgweb_config_manager: "HgWebConfigManager"
security_agent: "CommunityRBACAgent"
model_cache: "ModelCache"
5 changes: 4 additions & 1 deletion lib/tool_shed/webapp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ def __init__(self, **kwd) -> None:
self.hgweb_config_manager.hgweb_config_dir = self.config.hgweb_config_dir
self.hgweb_config_manager.hgweb_repo_prefix = self.config.hgweb_repo_prefix
# Initialize the repository registry.
self.repository_registry = tool_shed.repository_registry.Registry(self)
if config.SHED_API_VERSION != "v2":
self.repository_registry = tool_shed.repository_registry.Registry(self)
else:
self.repository_registry = tool_shed.repository_registry.NullRepositoryRegistry(self)
# Configure Sentry client if configured
self.configure_sentry_client()
# used for cachebusting -- refactor this into a *SINGLE* UniverseApplication base.
Expand Down
3 changes: 1 addition & 2 deletions lib/tool_shed/webapp/buildapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
GalaxyWebTransaction,
)
from galaxy.webapps.util import wrap_if_allowed

SHED_API_VERSION = os.environ.get("TOOL_SHED_API_VERSION", "v1")
from .config import SHED_API_VERSION

log = logging.getLogger(__name__)

Expand Down
1 change: 1 addition & 0 deletions lib/tool_shed/webapp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
log = logging.getLogger(__name__)

TOOLSHED_APP_NAME = "tool_shed"
SHED_API_VERSION = os.environ.get("TOOL_SHED_API_VERSION", "v1")


class ToolShedAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin):
Expand Down
2 changes: 1 addition & 1 deletion lib/tool_shed/webapp/fast_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def initialize_fast_app(gx_webapp, tool_shed_app):
app = get_fastapi_instance()
add_exception_handler(app)
add_request_id_middleware(app)
from .buildapp import SHED_API_VERSION
from .config import SHED_API_VERSION

def mount_static(directory: Path):
name = directory.name
Expand Down
18 changes: 18 additions & 0 deletions lib/tool_shed/webapp/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
not_,
String,
Table,
text,
TEXT,
true,
UniqueConstraint,
)
from sqlalchemy.orm import (
Mapped,
mapped_column,
object_session,
registry,
relationship,
)
Expand Down Expand Up @@ -618,6 +620,22 @@ class Category(Base, Dictifiable):
dict_collection_visible_keys = ["id", "name", "description", "deleted"]
dict_element_visible_keys = ["id", "name", "description", "deleted"]

def active_repository_count(self, session=None) -> int:
statement = """
SELECT count(*) as count
FROM repository r
INNER JOIN repository_category_association rca on rca.repository_id = r.id
WHERE
rca.category_id = :category_id
AND r.private = false
AND r.deleted = false
and r.deprecated = false
"""
if session is None:
session = object_session(self)
params = {"category_id": self.id}
return session.execute(text(statement), params).scalar()

def __init__(self, deleted=False, **kwd):
super().__init__(**kwd)
self.deleted = deleted
Expand Down
10 changes: 10 additions & 0 deletions test/unit/tool_shed/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
Category,
mapping,
Repository,
RepositoryCategoryAssociation,
User,
)
from tool_shed_client.schema import CreateCategoryRequest
Expand Down Expand Up @@ -195,3 +196,12 @@ def create_category(provides_repositories: ProvidesRepositoriesContext, create:

request = CreateCategoryRequest(**create)
return CategoryManager(provides_repositories.app).create(provides_repositories, request)


def attach_category(provides_repositories: ProvidesRepositoriesContext, repository: Repository, category: Category):
assoc = RepositoryCategoryAssociation(
repository=repository,
category=category,
)
provides_repositories.sa_session.add(assoc)
provides_repositories.sa_session.flush()
16 changes: 2 additions & 14 deletions test/unit/tool_shed/test_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@
ProvidesUserContext,
)
from tool_shed.webapp.graphql.schema import schema
from tool_shed.webapp.model import (
Category,
Repository,
RepositoryCategoryAssociation,
)
from tool_shed.webapp.model import Repository
from ._util import (
attach_category,
create_category,
repository_fixture,
upload_directories_to_repository,
Expand Down Expand Up @@ -110,15 +107,6 @@ def test_simple_repositories(provides_repositories: ProvidesRepositoriesContext,
assert new_repository.name in repository_names


def attach_category(provides_repositories: ProvidesRepositoriesContext, repository: Repository, category: Category):
assoc = RepositoryCategoryAssociation(
repository=repository,
category=category,
)
provides_repositories.sa_session.add(assoc)
provides_repositories.sa_session.flush()


def test_relay_repos_by_category(provides_repositories: ProvidesRepositoriesContext, new_repository: Repository):
name = new_repository.name
category = create_category(provides_repositories, {"name": "test_graphql_relay_categories_1"})
Expand Down
23 changes: 23 additions & 0 deletions test/unit/tool_shed/test_repository_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
User,
)
from ._util import (
attach_category,
create_category,
repository_fixture,
TEST_DATA_FILES,
TestToolShedApp,
Expand Down Expand Up @@ -83,6 +85,27 @@ def test_upload_dry_run_ok(provides_repositories: ProvidesRepositoriesContext, n
assert old_tip == new_tip


def test_category_count(provides_repositories: ProvidesRepositoriesContext, new_repository: Repository):
category = create_category(provides_repositories, {"name": "test_category_count_1"})
attach_category(provides_repositories, new_repository, category)

category = new_repository.categories[0].category
assert category.active_repository_count() == 1

tar_resource = TEST_DATA_FILES.joinpath("column_maker/column_maker.tar")
upload_ok, *_ = upload_tar(
provides_repositories,
new_repository.user.username,
new_repository,
tar_resource,
commit_message="Commit Message",
)
assert upload_ok

category = new_repository.categories[0].category
assert category.active_repository_count() == 1


def test_upload_dry_run_failed(provides_repositories: ProvidesRepositoriesContext, new_repository: Repository):
tar_resource = TEST_DATA_FILES.joinpath("safetar_with_symlink.tar")
upload_ok, message, _, _, _, _ = upload_tar(
Expand Down

0 comments on commit fd2425f

Please sign in to comment.