Skip to content

Commit

Permalink
Restore repository update in the tool shed 2.0.
Browse files Browse the repository at this point in the history
I guess somehow this critical API endpoint had no tests.
  • Loading branch information
jmchilton committed Aug 6, 2024
1 parent e0b6532 commit dbe6489
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 16 deletions.
10 changes: 8 additions & 2 deletions lib/tool_shed/managers/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,7 @@ def get_repository_metadata_for_management(
trans: ProvidesUserContext, encoded_repository_id: str, changeset_revision: str
) -> RepositoryMetadata:
repository = get_repository_in_tool_shed(trans.app, encoded_repository_id)
if not can_manage_repo(trans, repository):
raise InsufficientPermissionsException("Cannot manage target repository")
ensure_can_manage(trans, repository, "Cannot manage target repository")
revisions = [r for r in repository.metadata_revisions if r.changeset_revision == changeset_revision]
if len(revisions) != 1:
raise ObjectNotFound()
Expand Down Expand Up @@ -582,6 +581,13 @@ def upload_tar_and_set_metadata(
return message



def ensure_can_manage(trans: ProvidesUserContext, repository: Repository, error_mesage: Optional[str] = None) -> None:
if not can_manage_repo(trans, repository):
message = message or "You do not have permission to update this repository."
raise InsufficientPermissionsException(message)


def _get_repository_by_name_and_owner(session: scoped_session, name: str, owner: str, user_model):
stmt = (
select(Repository)
Expand Down
21 changes: 19 additions & 2 deletions lib/tool_shed/test/base/populators.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Category,
CreateCategoryRequest,
CreateRepositoryRequest,
DetailedRepository,
from_legacy_install_info,
GetInstallInfoRequest,
GetOrderedInstallableRevisionsRequest,
Expand All @@ -41,6 +42,7 @@
ResetMetadataOnRepositoryResponse,
ToolSearchRequest,
ToolSearchResults,
UpdateRepositoryRequest,
Version,
)
from .api_util import (
Expand Down Expand Up @@ -209,6 +211,18 @@ def upload_revision_raw(
)
return response

def update_raw(self, repository: HasRepositoryId, request: UpdateRepositoryRequest) -> requests.Response:
repository_id = self._repository_id(repository)
put_response = self._api_interactor.put(
f"repositories/{repository_id}", json=request.model_dump(exclude_unset=True, by_alias=True)
)
return put_response

def update(self, repository: HasRepositoryId, request: UpdateRepositoryRequest) -> Repository:
response = self.update_raw(repository, request)
api_asserts.assert_status_code_is_ok(response)
return Repository(**response.json())

def upload_revision(
self, repository: HasRepositoryId, path: Traversable, commit_message: str = DEFAULT_COMMIT_MESSAGE
) -> RepositoryUpdate:
Expand Down Expand Up @@ -358,11 +372,14 @@ def unset_deprecated(self, repository: HasRepositoryId):
delete_response = self._api_interactor.delete(f"repositories/{repository_id}/deprecated")
delete_response.raise_for_status()

def is_deprecated(self, repository: HasRepositoryId) -> bool:
def get_repository(self, repository: HasRepositoryId) -> DetailedRepository:
repository_id = self._repository_id(repository)
repository_response = self._api_interactor.get(f"repositories/{repository_id}")
repository_response.raise_for_status()
return Repository(**repository_response.json()).deprecated
return DetailedRepository(**repository_response.json())

def is_deprecated(self, repository: HasRepositoryId) -> bool:
return self.get_repository(repository).deprecated

def get_metadata(self, repository: HasRepositoryId, downloadable_only=True) -> RepositoryMetadata:
repository_id = self._repository_id(repository)
Expand Down
34 changes: 33 additions & 1 deletion lib/tool_shed/test/functional/test_shed_repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
HasRepositoryId,
repo_tars,
)
from tool_shed_client.schema import RepositoryRevisionMetadata
from tool_shed_client.schema import (
RepositoryRevisionMetadata,
UpdateRepositoryRequest,
)
from ..base.api import (
ShedApiTestCase,
skip_if_api_v1,
Expand Down Expand Up @@ -49,6 +52,35 @@ def test_update_repository(self):
)
assert repository_update.is_ok

def test_update_repository_info(self):
populator = self.populator
prefix = "testupdateinfo"
category_id = populator.new_category(prefix=prefix).id
repository = populator.new_repository(category_id, prefix=prefix)
repository_id = repository.id
request = UpdateRepositoryRequest(homepage_url="https://www.google.com")
update = populator.update(repository_id, request)
assert update.homepage_url == "https://www.google.com"
assert populator.get_repository(repository_id).homepage_url == "https://www.google.com"

def test_update_category(self):
old_category_id = populator.new_category(prefix=prefix).id
new_category_id = populator.new_category(prefix=prefix).id

populator.assert_category_has_n_repositories(old_category_id, 0)
populator.assert_category_has_n_repositories(new_category_id, 0)

repository = populator.new_repository(old_category_id, prefix=prefix)

populator.assert_category_has_n_repositories(old_category_id, 1)
populator.assert_category_has_n_repositories(new_category_id, 0)

request = UpdateRepositoryRequest(category_ids=new_category_id)
update = populator.update(repository_id, request)

populator.assert_category_has_n_repositories(old_category_id, 0)
populator.assert_category_has_n_repositories(new_category_id, 1)

# used by getRepository in TS client.
def test_metadata_simple(self):
populator = self.populator
Expand Down
16 changes: 13 additions & 3 deletions lib/tool_shed/util/repository_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,17 +432,27 @@ def change_repository_name_in_hgrc_file(hgrc_file: str, new_name: str) -> None:
def update_repository(trans: "ProvidesUserContext", id: str, **kwds) -> Tuple[Optional["Repository"], Optional[str]]:
"""Update an existing ToolShed repository"""
app = trans.app
message = None
flush_needed = False
sa_session = app.model.session
repository = sa_session.get(app.model.Repository, app.security.decode_id(id))
if repository is None:
return None, "Unknown repository ID"

if not (trans.user_is_admin or trans.app.security_agent.user_can_administer_repository(trans.user, repository)):
if not (trans.user_is_admin or app.security_agent.user_can_administer_repository(trans.user, repository)):
message = "You are not the owner of this repository, so you cannot administer it."
return None, message

return update_validated_repository(trans, repository, **kwds)


def update_validated_repository(
trans: "ProvidesUserContext", repository: "Repository", **kwds
) -> Tuple[Optional["Repository"], Optional[str]]:
"""Update an existing ToolShed repository metadata once permissions have been checked."""
app = trans.app
sa_session = app.model.session
message = None
flush_needed = False

# Allowlist properties that can be changed via this method
for key in ("type", "description", "long_description", "remote_repository_url", "homepage_url"):
# If that key is available, not None and different than what's in the model
Expand Down
43 changes: 35 additions & 8 deletions lib/tool_shed/webapp/api2/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
)
from starlette.datastructures import UploadFile as StarletteUploadFile

from galaxy.exceptions import InsufficientPermissionsException
from galaxy.exceptions import (
ActionInputError,
InsufficientPermissionsException,
)
from galaxy.model.base import transaction
from galaxy.webapps.galaxy.api import as_form
from tool_shed.context import SessionRequestContext
Expand All @@ -28,6 +31,7 @@
can_update_repo,
check_updates,
create_repository,
ensure_can_manage,
get_install_info,
get_ordered_installable_revisions,
get_repository_metadata_dict,
Expand All @@ -42,7 +46,10 @@
upload_tar_and_set_metadata,
)
from tool_shed.structured_app import ToolShedApp
from tool_shed.util.repository_util import get_repository_in_tool_shed
from tool_shed.util.repository_util import (
get_repository_in_tool_shed,
update_validated_repository,
)
from tool_shed_client.schema import (
CreateRepositoryRequest,
DetailedRepository,
Expand All @@ -57,6 +64,7 @@
RepositoryUpdateRequest,
ResetMetadataOnRepositoryRequest,
ResetMetadataOnRepositoryResponse,
UpdateRepositoryRequest,
ValidRepostiroyUpdateMessage,
)
from . import (
Expand Down Expand Up @@ -293,6 +301,28 @@ def show(
repository = get_repository_in_tool_shed(self.app, encoded_repository_id)
return to_detailed_model(self.app, repository)

@router.put(
"/api/repositories/{encoded_repository_id}",
operation_id="repositories__update_repository",
)
def update_repository(
self,
trans: SessionRequestContext = DependsOnTrans,
encoded_repository_id: str = RepositoryIdPathParam,
request: UpdateRepositoryRequest = Body(...),
) -> DetailedRepository:
repository = get_repository_in_tool_shed(self.app, encoded_repository_id)
ensure_can_manage(trans, repository)

# may want to set some of these to null, so we're using the exclude_unset feature
# to just serialize the ones we want to use to a dictionary.
update_dictionary = request.model_dump(exclude_unset=True)
repo_result, message = update_validated_repository(trans, repository, **update_dictionary)
if repo_result is None:
raise ActionInputError(message)

return to_detailed_model(self.app, repository)

@router.get(
"/api/repositories/{encoded_repository_id}/permissions",
operation_id="repositories__permissions",
Expand Down Expand Up @@ -323,8 +353,7 @@ def show_allow_push(
encoded_repository_id: str = RepositoryIdPathParam,
) -> List[str]:
repository = get_repository_in_tool_shed(self.app, encoded_repository_id)
if not can_manage_repo(trans, repository):
raise InsufficientPermissionsException("You do not have permission to update this repository.")
ensure_can_manage(trans, repository)
return trans.app.security_agent.usernames_that_can_push(repository)

@router.post(
Expand Down Expand Up @@ -390,8 +419,7 @@ def set_deprecated(
encoded_repository_id: str = RepositoryIdPathParam,
):
repository = get_repository_in_tool_shed(self.app, encoded_repository_id)
if not can_manage_repo(trans, repository):
raise InsufficientPermissionsException("You do not have permission to update this repository.")
ensure_can_manage(trans, repository)
repository.deprecated = True
trans.sa_session.add(repository)
with transaction(trans.sa_session):
Expand All @@ -409,8 +437,7 @@ def unset_deprecated(
encoded_repository_id: str = RepositoryIdPathParam,
):
repository = get_repository_in_tool_shed(self.app, encoded_repository_id)
if not can_manage_repo(trans, repository):
raise InsufficientPermissionsException("You do not have permission to update this repository.")
ensure_can_manage(trans, repository)
repository.deprecated = False
trans.sa_session.add(repository)
with transaction(trans.sa_session):
Expand Down
19 changes: 19 additions & 0 deletions lib/tool_shed_client/schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,25 @@ class CreateRepositoryRequest(BaseModel):
model_config = ConfigDict(populate_by_name=True)


class UpdateRepositoryRequest(BaseModel):
name: Optional[str] = None
synopsis: Optional[str] = None
type_: Optional[RepositoryType] = Field(
None,
alias="type",
title="Type",
)
description: Optional[str] = None
remote_repository_url: Optional[str] = None
homepage_url: Optional[str] = None
category_ids: Optional[Union[List[str], str]] = Field(
None,
alias="category_ids[]",
title="Category IDs",
)
model_config = ConfigDict(populate_by_name=True)


class RepositoryUpdateRequest(BaseModel):
commit_message: Optional[str] = None

Expand Down

0 comments on commit dbe6489

Please sign in to comment.