Skip to content

Commit

Permalink
TRS models.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmchilton committed Jan 19, 2023
1 parent 333b3ab commit 79b096c
Show file tree
Hide file tree
Showing 10 changed files with 679 additions and 2 deletions.
52 changes: 52 additions & 0 deletions lib/galaxy/config/schemas/tool_shed_config_schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,58 @@ mapping:
desc: |
Address to join mailing list
ga4gh_service_id:
type: str
required: false
desc: |
Service ID for GA4GH services (exposed via the service-info endpoint for the Galaxy DRS API).
If unset, one will be generated using the URL the target API requests are made against.
For more information on GA4GH service definitions - check out
https://github.com/ga4gh-discovery/ga4gh-service-registry
and https://editor.swagger.io/?url=https://raw.githubusercontent.com/ga4gh-discovery/ga4gh-service-registry/develop/service-registry.yaml
This value should likely reflect your service's URL. For instance for usegalaxy.org
this value should be org.usegalaxy. Particular Galaxy implementations will treat this
value as a prefix and append the service type to this ID. For instance for the DRS
service "id" (available via the DRS API) for the above configuration value would be
org.usegalaxy.drs.
ga4gh_service_organization_name:
type: str
required: false
desc: |
Service name for host organization (exposed via the service-info endpoint for the Galaxy DRS API).
If unset, one will be generated using ga4gh_service_id.
For more information on GA4GH service definitions - check out
https://github.com/ga4gh-discovery/ga4gh-service-registry
and https://editor.swagger.io/?url=https://raw.githubusercontent.com/ga4gh-discovery/ga4gh-service-registry/develop/service-registry.yaml
ga4gh_service_organization_url:
type: str
required: False
desc: |
Organization URL for host organization (exposed via the service-info endpoint for the Galaxy DRS API).
If unset, one will be generated using the URL the target API requests are made against.
For more information on GA4GH service definitions - check out
https://github.com/ga4gh-discovery/ga4gh-service-registry
and https://editor.swagger.io/?url=https://raw.githubusercontent.com/ga4gh-discovery/ga4gh-service-registry/develop/service-registry.yaml
ga4gh_service_environment:
type: str
required: False
desc: |
Service environment (exposed via the service-info endpoint for the Galaxy DRS API) for
implemented GA4GH services.
Suggested values are prod, test, dev, staging.
For more information on GA4GH service definitions - check out
https://github.com/ga4gh-discovery/ga4gh-service-registry
and https://editor.swagger.io/?url=https://raw.githubusercontent.com/ga4gh-discovery/ga4gh-service-registry/develop/service-registry.yaml
use_heartbeat:
type: bool
default: true
Expand Down
9 changes: 8 additions & 1 deletion lib/galaxy/webapps/base/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,14 @@ def get_error_response_for_request(request: Request, exc: MessageException) -> J
if "ga4gh" in path:
# When serving GA4GH APIs use limited exceptions to conform their expected
# error schema. Tailored to DRS currently.
content = {"status_code": status_code, "msg": error_dict["err_msg"]}
message = error_dict["err_msg"]
if "drs" in path:
content = {"status_code": status_code, "msg": message}
elif "trs" in path:
content = {"code": status_code, "message": message}
else:
# unknown schema - just yield the most useful error message
content = error_dict
else:
content = error_dict

Expand Down
153 changes: 153 additions & 0 deletions lib/tool_shed/managers/trs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from typing import (
Any,
cast,
Dict,
List,
Optional,
Tuple,
)

from starlette.datastructures import URL

from galaxy.exceptions import ObjectNotFound
from galaxy.util.tool_shed.common_util import remove_protocol_and_user_from_clone_url
from galaxy.version import VERSION
from tool_shed.context import ProvidesRepositoriesContext
from tool_shed.structured_app import ToolShedApp
from tool_shed.util.metadata_util import get_current_repository_metadata_for_changeset_revision
from tool_shed.webapp.model import (
Repository,
RepositoryMetadata,
)
from tool_shed_client.schema.trs import (
DescriptorType,
Tool,
ToolClass,
ToolVersion,
)
from tool_shed_client.schema.trs_service_info import (
Organization,
Service,
ServiceType,
)
from tool_shed_client.trs_util import decode_identifier
from .repositories import guid_to_repository

TRS_SERVICE_NAME = "Tool Shed TRS API"
TRS_SERVICE_DESCRIPTION = "Serves tool shed repository tools according to the GA4GH TRS specification"


def service_info(app: ToolShedApp, request_url: URL):
components = request_url.components
hostname = components.hostname
assert hostname
default_organization_id = ".".join(reversed(hostname.split(".")))
config = app.config
organization_id = cast(str, config.ga4gh_service_id or default_organization_id)
organization_name = cast(str, config.ga4gh_service_organization_name or organization_id)
organization_url = cast(str, config.ga4gh_service_organization_url or f"{components.scheme}://{components.netloc}")

organization = Organization(
url=organization_url,
name=organization_name,
)
service_type = ServiceType(
group="org.ga4gh",
artifact="trs",
version="2.1.0",
)
environment = config.ga4gh_service_environment
extra_kwds = {}
if environment:
extra_kwds["environment"] = environment
return Service(
id=organization_id + ".trs",
name=TRS_SERVICE_NAME,
description=TRS_SERVICE_DESCRIPTION,
organization=organization,
type=service_type,
version=VERSION,
**extra_kwds,
)


def tool_classes() -> List[ToolClass]:
return [ToolClass(id="galaxy_tool", name="Galaxy Tool", description="Galaxy XML Tools")]


def trs_tool_id_to_repository(trans: ProvidesRepositoriesContext, trs_tool_id: str) -> Repository:
guid = decode_identifier(trans.repositories_hostname, trs_tool_id)
guid = remove_protocol_and_user_from_clone_url(guid)
return guid_to_repository(trans.app, guid)


def get_repository_metadata_by_tool_version(
app: ToolShedApp, repository: Repository, tool_id: str
) -> Dict[str, RepositoryMetadata]:
versions = {}
for _, changeset in repository.installable_revisions(app):
metadata = get_current_repository_metadata_for_changeset_revision(app, repository, changeset)
tools: Optional[List[Dict[str, Any]]] = metadata.metadata.get("tools")
if not tools:
continue
for tool_metadata in tools:
if tool_metadata["id"] != tool_id:
continue
versions[tool_metadata["version"]] = metadata
return versions


def get_tools_for(repository_metadata: RepositoryMetadata) -> List[Dict[str, Any]]:
tools: Optional[List[Dict[str, Any]]] = repository_metadata.metadata.get("tools")
assert tools
return tools


def trs_tool_id_to_repository_metadata(
trans: ProvidesRepositoriesContext, trs_tool_id: str
) -> Optional[Tuple[Repository, Dict[str, RepositoryMetadata]]]:
tool_guid = decode_identifier(trans.repositories_hostname, trs_tool_id)
tool_guid = remove_protocol_and_user_from_clone_url(tool_guid)
_, tool_id = tool_guid.rsplit("/", 1)
repository = guid_to_repository(trans.app, tool_guid)
app = trans.app
versions: Dict[str, RepositoryMetadata] = get_repository_metadata_by_tool_version(app, repository, tool_id)
if not versions:
return None

return repository, versions


def get_tool(trans: ProvidesRepositoriesContext, trs_tool_id: str) -> Tool:
guid = decode_identifier(trans.repositories_hostname, trs_tool_id)
guid = remove_protocol_and_user_from_clone_url(guid)
repo_metadata = trs_tool_id_to_repository_metadata(trans, trs_tool_id)
if not repo_metadata:
raise ObjectNotFound()
repository, metadata_by_version = repo_metadata

repo_owner = repository.user.username
aliases: List[str] = [guid]
hostname = remove_protocol_and_user_from_clone_url(trans.repositories_hostname)
url = f"https://{hostname}/repos/{repo_owner}/{repository.name}"

versions: List[ToolVersion] = []
for tool_version_str, _ in metadata_by_version.items():
version_url = url # TODO:
tool_version = ToolVersion(
author=[repo_owner],
containerfile=False,
descriptor_type=[DescriptorType.GALAXY],
id=tool_version_str,
url=version_url,
verified=False,
)
versions.append(tool_version)
return Tool(
aliases=aliases,
id=trs_tool_id,
url=url,
toolclass=tool_classes()[0],
organization=repo_owner,
versions=versions,
)
38 changes: 37 additions & 1 deletion lib/tool_shed/test/functional/test_shed_tools.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
from ..base.api import ShedApiTestCase
from tool_shed_client.schema.trs import (
Tool,
ToolClass,
)
from tool_shed_client.trs_util import encode_identifier
from ..base.api import (
ShedApiTestCase,
skip_if_api_v1,
)


class TestShedToolsApi(ShedApiTestCase):
Expand Down Expand Up @@ -32,3 +40,31 @@ def test_tool_search(self):
# but if this tool has been installed a bunch by other tests - it might not be.
tool_search_hit = response.find_search_hit(repository)
assert tool_search_hit

@skip_if_api_v1
def test_trs_service_info(self):
service_info = self.api_interactor.get("ga4gh/trs/v2/service-info")
service_info.raise_for_status()

@skip_if_api_v1
def test_trs_tool_classes(self):
classes_response = self.api_interactor.get("ga4gh/trs/v2/toolClasses")
classes_response.raise_for_status()
classes = classes_response.json()
assert isinstance(classes, list)
assert len(classes) == 1
class0 = classes[0]
assert ToolClass(**class0)

@skip_if_api_v1
def test_trs_tool_list(self):
populator = self.populator
repository = populator.setup_column_maker_repo(prefix="toolstrsindex")
tool_id = populator.tool_guid(self, repository, "Add_a_column1")
tool_shed_base, encoded_tool_id = encode_identifier(tool_id)
print(encoded_tool_id)
url = f"ga4gh/trs/v2/tools/{encoded_tool_id}"
print(url)
tool_response = self.api_interactor.get(url)
tool_response.raise_for_status()
assert Tool(**tool_response.json())
68 changes: 68 additions & 0 deletions lib/tool_shed/webapp/api2/tools.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
import logging
from typing import List

from fastapi import (
Path,
Request,
)

from tool_shed.context import SessionRequestContext
from tool_shed.managers.tools import search
from tool_shed.managers.trs import (
get_tool,
service_info,
tool_classes,
)
from tool_shed.structured_app import ToolShedApp
from tool_shed.util.shed_index import build_index
from tool_shed_client.schema import BuildSearchIndexResponse
from tool_shed_client.schema.trs import (
Tool,
ToolClass,
ToolVersion,
)
from tool_shed_client.schema.trs_service_info import Service
from . import (
depends,
DependsOnTrans,
Expand All @@ -12,8 +31,16 @@
ToolsIndexQueryParam,
)

log = logging.getLogger(__name__)

router = Router(tags=["tools"])

TOOL_ID_PATH_PARAM: str = Path(
...,
title="GA4GH TRS Tool ID",
description="See also https://ga4gh.github.io/tool-registry-service-schemas/DataModel/#trs-tool-and-trs-tool-version-ids",
)


@router.cbv
class FastAPITools:
Expand Down Expand Up @@ -53,3 +80,44 @@ def build_search_index(self) -> BuildSearchIndexResponse:
repositories_indexed=repos_indexed,
tools_indexed=tools_indexed,
)

@router.get("/api/ga4gh/trs/v2/service-info", operation_id="tools_trs_service_info")
def service_info(self, request: Request) -> Service:
return service_info(self.app, request.url)

@router.get("/api/ga4gh/trs/v2/toolClasses", operation_id="tools__trs_tool_classes")
def tool_classes(self) -> List[ToolClass]:
return tool_classes()

@router.get(
"/api/ga4gh/trs/v2/tools",
operation_id="tools__trs_index",
)
def trs_index(
self,
):
# we probably want to be able to query the database at the
# tool level and such to do this right?
return []

@router.get(
"/api/ga4gh/trs/v2/tools/{tool_id}",
operation_id="tools__trs_get",
)
def trs_get(
self,
trans: SessionRequestContext = DependsOnTrans,
tool_id: str = TOOL_ID_PATH_PARAM,
) -> Tool:
return get_tool(trans, tool_id)

@router.get(
"/api/ga4gh/trs/v2/tools/{tool_id}/versions",
operation_id="tools__trs_get_versions",
)
def trs_get_versions(
self,
trans: SessionRequestContext = DependsOnTrans,
tool_id: str = TOOL_ID_PATH_PARAM,
) -> List[ToolVersion]:
return get_tool(trans, tool_id).versions
13 changes: 13 additions & 0 deletions lib/tool_shed_client/schema/gen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

# must be run from a virtualenv with...
# https://github.com/koxudaxi/datamodel-code-generator
#for model in AccessMethod Checksum DrsObject Error AccessURL ContentsObject DrsService
#do
# datamodel-codegen --url "https://raw.githubusercontent.com/ga4gh/tool-registry-service-schemas/develop/openapi/ga4gh-tool-discovery.yaml" --output "$model.py"
#one

#datamodel-codegen --url "https://raw.githubusercontent.com/ga4gh-discovery/ga4gh-service-info/v1.0.0/service-info.yaml#/components/schemas/Service" --output Service.py

datamodel-codegen --url "https://raw.githubusercontent.com/ga4gh-discovery/ga4gh-service-info/v1.0.0/service-info.yaml#/paths/~1service-info" --output trs_service_info.py
datamodel-codegen --url "https://raw.githubusercontent.com/ga4gh/tool-registry-service-schemas/develop/openapi/openapi.yaml" --output trs.py
Loading

0 comments on commit 79b096c

Please sign in to comment.