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 5d73fbd
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 14 deletions.
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
16 changes: 15 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,17 @@ 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, request)
assert update.homepage_url == "https://www.google.com"
assert populator.get_repository(repository).homepage_url == "https://www.google.com"

# used by getRepository in TS client.
def test_metadata_simple(self):
populator = self.populator
Expand Down
17 changes: 13 additions & 4 deletions lib/tool_shed/util/repository_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,17 +432,26 @@ 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
41 changes: 34 additions & 7 deletions lib/tool_shed/webapp/api2/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,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 +60,7 @@
RepositoryUpdateRequest,
ResetMetadataOnRepositoryRequest,
ResetMetadataOnRepositoryResponse,
UpdateRepositoryRequest,
ValidRepostiroyUpdateMessage,
)
from . import (
Expand Down Expand Up @@ -293,6 +297,27 @@ 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,
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 +348,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 +414,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 +432,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 Expand Up @@ -505,3 +527,8 @@ def get_readmes(
) -> dict:
repository = get_repository_in_tool_shed(self.app, encoded_repository_id)
return readmes(self.app, repository, changeset_revision)


def _ensure_can_manage(trans: SessionRequestContext, repository: Repository) -> None:
if not can_manage_repo(trans, repository):
raise InsufficientPermissionsException("You do not have permission to update this repository.")
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 5d73fbd

Please sign in to comment.